1. 시작하며
Spring Data JPA를 활용하여 애플리케이션을 개발하다 보면 엔티티(Entity) 설계는 매우 중요한 요소로 다가옵니다. 특히, Entity의 생성자 접근 제한과 같은 세부적인 설계는 시스템의 안정성과 데이터 일관성을 유지하는 데 큰 영향을 미칩니다.
이번 글에서는 회원 엔티티(Entity)를 설계하며 @NoArgsConstructor
와 @AllArgsConstructor
를 AccessLevel.PROTECTED
로 제한한 이유를 다룹니다. 이러한 설계가 가지는 장점과 실제로 어떤 효과를 기대할 수 있는지에 대해 설명합니다.
2. JPA 엔티티 기본 설계
JPA 엔티티는 데이터베이스 테이블과 1:1로 매핑되는 클래스입니다. 아래는 Member
엔티티의 예시입니다.
@Entity
@Getter
@Builder
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Table(indexes = {
@Index(name = "idx_email", columnList = "email")
})
public class Member extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String email;
private String password;
@Column(nullable = false)
private String nickname;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private MemberRole role;
// 연관 관계 설정 생략
}
3. AccessLevel.PROTECTED를 사용하는 이유
Spring Data JPA와 JPA 표준은 엔티티 생성 및 초기화 과정에서 특정 생성자를 요구합니다. 하지만 무분별하게 생성자를 사용하면 데이터의 안정성을 해칠 수 있습니다. 이를 방지하기 위해 @NoArgsConstructor
와 @AllArgsConstructor
를 PROTECTED
로 설정하는 것이 중요합니다.
3.1 @NoArgsConstructor(access = AccessLevel.PROTECTED)
1) JPA 프록시 객체 생성
- JPA는 내부적으로 프록시 객체를 생성하여 엔티티를 관리합니다.
- 이 과정에서 기본 생성자가 반드시 필요합니다.
- 기본 생성자가 없으면
org.hibernate.InstantiationException
에러가 발생합니다.
2) 기본 생성자의 위험성
- 기본 생성자가
public
으로 열려 있으면, 아래와 같이 필수 데이터가 누락된 불완전한 객체를 생성할 수 있습니다.
// 잘못된 사용 예시
Member member = new Member(); // 기본 생성자 호출
- 위 코드처럼 초기화되지 않은 필드로 엔티티를 생성하면, 데이터 무결성을 해칠 위험이 있습니다.
3) 해결책: AccessLevel.PROTECTED
- 기본 생성자를
PROTECTED
로 설정하여 외부에서 직접 호출하지 못하도록 제한합니다. - JPA 내부적으로만 기본 생성자를 호출하게 하여 안전한 엔티티 초기화를 보장합니다.
3.2 @AllArgsConstructor(access = AccessLevel.PROTECTED)
1) 모든 필드 초기화 생성자의 문제점
@AllArgsConstructor
는 모든 필드를 초기화하는 생성자를 자동으로 생성합니다.- 그러나 이 생성자가
public
으로 열려 있으면, 외부에서 임의의 값으로 객체를 생성할 수 있습니다.
// 잘못된 사용 예시
Member member = new Member(null, null, null, null, null);
- 위와 같은 방식은 잘못된 데이터가 생성되고 저장되는 위험을 초래합니다.
2) Builder 패턴과의 연계
@AllArgsConstructor
를PROTECTED
로 설정하면, Builder 패턴을 통한 객체 생성을 강제할 수 있습니다.- Builder 패턴은 필수 값만 설정하고 선택적인 값은 유연하게 처리할 수 있도록 도와줍니다.
Member member = Member.builder()
.email("example@example.com")
.nickname("nickname")
.build();
3) 팩토리 메서드 활용
- 생성자를
PROTECTED
로 제한하면 팩토리 메서드로 엔티티 생성 과정을 제어할 수 있습니다.
public static Member createMember(String email, String password, String nickname) {
return new Member(email, password, nickname, MemberRole.USER);
}
4. 정리: @NoArgsConstructor와 @AllArgsConstructor를 PROTECTED로 설정해야 하는 이유
@NoArgsConstructor | JPA 내부에서만 기본 생성자 호출을 허용하여 불완전한 객체 생성을 방지 |
---|---|
@AllArgsConstructor | Builder 패턴이나 팩토리 메서드를 통해 객체 생성을 통제하고 데이터 무결성 보장 |
5. 결론
엔티티는 데이터베이스와 애플리케이션 간의 데이터 교환을 담당하며, 잘못된 설계는 데이터 무결성을 위협할 수 있습니다.
이번 글에서는 @NoArgsConstructor
와 @AllArgsConstructor
를 AccessLevel.PROTECTED
로 설정하여 데이터 무결성을 유지하고 안전한 객체 생성을 보장하는 방법을 살펴보았습니다.
- 기본 생성자는 JPA 내부에서만 사용되도록 제한하여 불완전한 객체 생성을 방지합니다.
- 모든 필드 초기화 생성자는 Builder 패턴 또는 팩토리 메서드와 연계하여 안전한 데이터 처리를 가능하게 합니다.
'BackEnd > Spring & JPA' 카테고리의 다른 글
[Spring] @RequestPart를 활용하여 JSON + MultipartFile 동시 전송하기 (Feat. 게시판에서 게시물 생성과 첨부 파일 업로드 한번에 처리하기) (0) | 2025.02.13 |
---|---|
[Spring] @ResponseBody VS ResponseEntity<T> (0) | 2025.01.06 |
[Spring] @Controller VS @RestController (0) | 2025.01.06 |
[Spring] 스프링 개념 및 동작 원리 정리 (0) | 2025.01.05 |