Spring

Spring, Entity 구조 정리 / JPA에서 자주 사용되는 어노테이션

greenyellow-s 2025. 3. 17. 10:24
728x90
반응형

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_idcredit_id를 직접 관리하면서 account credit 엔티티는 단순 조회 용도로만 사용하지 위해서이다.

 

이렇게 하면 내부에서 외래키를 직접 할당할 수 있고 accountcredit필드는 참조용이므로, 엔티티를 새로 만들거나 수정할 때 영향을 받지 않는다.

 

(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을 방지하는 방법:

  1. DTO에서 Lazy Loaded 필드를 제외: account와 같은 필드를 DTO에서 제외하면 Lazy Loading을 발생시키지 않습니다.
  2. Fetch Join 사용: account를 포함한 엔티티를 JOIN FETCH로 즉시 로딩하여 Lazy Loading을 방지할 수 있습니다.
  3. @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();
    }
}

 

 

 

728x90
반응형