JPA에서 자주 사용되는 어노테이션
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(name = "report_monthly", indexes = {
@Index(name = "unq__report", columnList = "credit_id, the_day", unique = true),
@Index(name = "idx__report_account", columnList = "account_id", unique = false)
})
@Entity
@EntityListeners(AuditingEntityListener.class)
public class CreditMonthlyReportEntity {
}
✔︎ Entity는 현재 생성되지 않도록 막아둔 상태 (@GeneratedValue)
외부에서 객체를 생성하고 수정하면 일관성이 깨질 수 있기 때문에 Entity 자체로는 현재 데이터를 변경할 수 없도록 설정해 두었다.
@Getter
Getter 패턴을 자동으로 적용해주는 어노테이션
▹ Entity 자체로 데이터를 변경할 수 없도록 @Setter 어노테이션은 사용하지 않는다.
@NoArgsConstructor(access = AccessLevel.PROTECTED)
기본 생성자를 생성하되, 접근 제한을 Protected로 설정하는 어노테이션
▹ 모든 JPA 엔티티는 기본 생성자가 필요하다. 하지만 기본 생성자를 public으로 두면, 외부에서 아무나 객체를 생성할 수 있기 때문에 객체의 일관성이 깨질 수 있다.
따라서 기본 생성자가 필요하지만, 외부에서 직접 호출하지 못하도록 제한하는 방식의 Protected 접근 제어자를 사용한다.
@Builder
Lombok의 빌더 패턴을 자동으로 적용해주는 어노테이션
▹ 객체를 생성할 때 생성자의 매개변수가 많을 경우, 가독성을 높이고 실수를 방지하는 패턴이다.
- @Builder는 객체 생성의 가독성을 높이고, 불변성을 유지하는 데 유용하며
- 특히 JPA 엔티티에서는 @NoArgsConstructor(access = AccessLevel.PROTECTED) 와 함께 사용하는 것이 좋다.
- 불필요한 setter를 없애고, 안전하게 객체를 생성하는 방식으로 사용 가능하다.
@AllArgsConstructor
모든 필드를 매개변수로 받는 생성자를 자동으로 만들어준다.
@Table
엔티티와 매핑할 DB 테이블 명 지정. 인덱스 추가 기능
@Entity
JPA 엔티티로 지정한다는 의미이다.
@EntityListeners(AuditingEntityListener.class)
Auditing 기능을 활성화하여 @CreatedDate , @LastModifiedDate 를 자동 관리해준다.
JPA에서 자주 사용되는 제약조건 어노테이션
@Id
@Size(max = 36)
@UUID
@Column(name = "report_id", updatable = false)
private String reportId;
@NotBlank
@Size(max = 36)
@Column(name = "credit_id")
private String creditId;
@NotNull
@Column(name = "the_day")
private LocalDate theDay;
@NotNull
@Column(name = "account_id")
private Integer accountId;
@Builder.Default
@NotNull
@Column(name = "available_balance", precision = 15, scale = 5)
private BigDecimal availableBalance = BigDecimal.ZERO;
@NotNull
@Column(name = "created_at")
@CreatedDate
private LocalDateTime createdAt;
@NotNull
@Column(name = "updated_at")
@LastModifiedDate
private LocalDateTime updatedAt;
@Id
기본키 지정 어노테이션
@Size
문자열 길이 제한 어노테이션
@UUID
UUID 형식 검증 어노테이션
@Column
DB 컬럼 매핑 및 설정 어노테이션
@NotNull vs @NotBlank
빈 값 들어오지 못하게 하는 제약조건 어노테이션
어노테이션 | Null 허용 | 빈 문자열("") | 공백 문자열 (" ") |
@NotNull | 불가능 ✕ | 가능 ✓ | 가능 ✓ |
@NotBlank | 불가능 ✕ | 불가능 ✕ | 불가능 ✕ |
@NotEmpty | 불가능 ✕ | 불가능 ✕ | 가능 ✓ |
@Builder.Default
기본값을 설정하는 어노테이션
▹ 빌더 사용 시 기본값을 유지하려면 @Builder.Default 를 사용해야한다. 기본값 없이 빌더만 사용하면 필드값이 Null이 될 수 있다.
@JoinColumn 어노테이션을 따로 쓴 이유
@NotBlank
@Size(max = 36)
@Column(name = "credit_id")
private String creditId;
@NotNull
@Column(name = "account_id")
private Integer accountId;
/* Join */
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "account_id", referencedColumnName = "account_id", insertable = false, updatable = false)
private AccountEntity account;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "credit_id", referencedColumnName = "credit_id", insertable = false, updatable = false)
private CreditEntity credit;
}
★ account_id와 credit_id를 직접 관리하면서 account 와 credit 엔티티는 단순 조회 용도로만 사용하지 위해서이다.
이렇게 하면 내부에서 외래키를 직접 할당할 수 있고 account와 credit필드는 참조용이므로, 엔티티를 새로 만들거나 수정할 때 영향을 받지 않는다.
(insertable=false, updatable=false)을 사용하면
- 외래 키를 직접 관리할 수 있고,
- 불필요한 조인을 방지하여 성능 최적화가 가능하며,
- 잘못된 엔티티 변경을 방지할 수 있습니다.
@ManyToOne(FetchType.LAZY)
FetchType.LAZY 는 필요할 때만 조인 테이블을 조회하며, 기본 키는 바로 접근할 수 있어서 불필요한 조인을 피할 수 있다.
▹ 조회되는 모든 행마다 SELECT 하는게 아니라 한번만 가져온다.
★ 실행되는 시점
ModelMapper를 사용하여 엔티티 객체를 DTO로 매핑할 때, 연관된 엔티티가 Lazy Loading으로 설정된 경우, DTO에서 해당 필드를 참조하면 Lazy Loading이 발생하여 추가적인 쿼리가 실행된다.
동작 시점:
- ModelMapper를 호출한 시점에 매핑이 실행됩니다.
- 매핑된 DTO에서 account와 같은 Lazy Loaded 필드를 참조하면, 해당 필드를 로딩하기 위해 쿼리가 실행됩니다.
- 반대로, DTO에서 account 필드를 참조하지 않으면 Lazy Loading 쿼리는 실행되지 않습니다.
- Service
.map(entity -> modelMapper.map(entity, CreditReport.CreditAndAccountDTO.class));
DTO에서 Lazy Loading을 발생시키는 상황:
- DTO가 account를 참조하는 경우, 해당 연관 관계를 로딩하기 위해 Lazy Loading이 트리거됩니다.
- 이 때, CreditAndAccountDTO가 account를 포함하고 있다면, 해당 필드를 매핑하는 과정에서 Lazy Loading이 발생합니다.
- DTO
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public static class CreditAndAccountDTO extends CreditMonthlyReportDTO{
@Schema(description = "계정, Account")
private Account.AccountDTO account;
}
Lazy Loading을 방지하는 방법:
- DTO에서 Lazy Loaded 필드를 제외: account와 같은 필드를 DTO에서 제외하면 Lazy Loading을 발생시키지 않습니다.
- Fetch Join 사용: account를 포함한 엔티티를 JOIN FETCH로 즉시 로딩하여 Lazy Loading을 방지할 수 있습니다.
- @EntityGraph 사용: 엔티티에 @EntityGraph를 적용하여 연관된 엔티티를 즉시 로딩할 수 있습니다.
※ 보통 @Bean으로 등록하여 전역에서 사용할 수 있게 하기도 한다.
import org.modelmapper.ModelMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ModelMapperConfig {
@Bean
public ModelMapper modelMapper() {
return new ModelMapper();
}
}
'Spring' 카테고리의 다른 글
Spring, Lazy Loading 사용하는 방법 / 외래키로 연결된 테이블의 데이터 같이 가져오는 방법(JPA) (0) | 2025.03.18 |
---|---|
Spring, Controller 어노테이션 정리 (0) | 2025.03.17 |
Spring, Entity와 DTO의 차이점 및 구조 파악 / 변환 방법 (0) | 2025.03.13 |
Spring, Spring Boot 기본 구조 (0) | 2025.03.13 |
Spring Boot, Spring Security란? (0) | 2025.03.12 |