Spring을 사용하는 사람은 누구나 @Entity
클래스를 생성해보았을 것이다.
@Entity
어노테이션과 다른 어노테이션 등을 조합하여 아주 간단하게 생성할 수 있는데, 엔티티는 바로 데이터베이스와 연결되어 있기때문에 안전하게 생성하는 것이 아주 중요하다.
그래서 안전하게 잘 생성하는 방법을 알아보자!
1. @Setter
사용하지 않기
왜 Setter 사용을 지양해야 하는가?
Setter를 허용하면 무분별한 엔티티 수정이 가능한 상태가 되어 객체의 안전성을 보장할 수 없다.
특히 @Setter
어노테이션을 사용하면 모든 필드에 대해 setter 함수가 생성되기 때문에 지양해야 한다.
비즈니스 로직에서 엔티티 값 수정이 필요하다면, 상황에 맞게 의미있는 수정 메소드(ex: update()
, changeName()
)를 생성하는 것이 안전하다.
2. @AllArgsConstructor
사용하지 않기
@AllArgsConstructor
어노테이션이란?
클래스의 모든 속성을 받아 객체를 생성하는 생성자를 만드는 Lombok의 어노테이션이다.
왜 사용을 지양해야 하는가?
첫 번째로는 접근할 필요가 없는 필드 값에 대해 접근할 수 있는 안전성의 문제가 발생한다.
두 번째로는 @AllArgsConstructor
을 사용해서 만든 생성자의 매개변수의 순서는 필드 변수의 순서에 영향을 받는다. 필드 변수 순서를 변경하면 예상치 못한 사이드 이펙트가 발생할 수 있다.
3. 기본 생성자의 접근제한자를 AccessLevel.PROTECTED
로 설정하기
@NoArgsConstructor(access = AccessLevel.PROTECTED)
엔티티 클래스는 아무런 인자를 받지 않는 기본 생성자를 필요로 한다.
Java에선 Lombok의 @NoArgsConstructor
어노테이션을 이용해서 기본 생성자를 손쉽게 만들 수 있다.
물론 클래스 내부에 아무런 생성자가 없다면 java 차원에서 자동으로 기본 생성자를 만들어준다. 그러나 그 경우 기본 생성자의 접근 제한자를 명확히 알 수 없다. 또한 여러 이유로 다른 생성자를 만들어주게 된다면 기본 생성자가 만들어지지 않아 이슈가 발생한다.
왜 엔티티 클래스는 기본 생성자가 필요할까?
JPA 공식 문서에서는 엔티티 클래스는 반드시 public
혹은 protected
수준의 기본 생성자를 가져야한다고 명시되어 있다.
그 이유는 JPA가 엔티티 객체를 생성하고 값을 주입할 때 Java의 Reflection API를 활용하기 때문이다.
Reflection API는 간단하게 정리하면 구체적인 클래스 타입을 알지 못해도 클래스의 메소드, 타입, 변수 등에 접근할 수 있도록 돕는 API이다. 이 API를 활용하여 setter를 사용하지 않고도 값을 주입할 수 있다.
그러나 Reflection API는 클래스 생성자의 인자 정보를 가져올 수 없다는 한계점이 있어, 기본 생성자를 필요로 하는 것이다.
org.springframework.orm.jpa.JpaSystemException: No default constructor for entity
엔티티 클래스에 기본 생성자를 생성하지 않은 채로 데이터베이스를 조회하면 다음과 같은 예외가 발생한다. 객체를 생성할 때 Reflection API를 사용하기 위한 기본 생성자가 없기 때문임을 알 수 있다.
그렇다면 왜 protected
수준으로 지정할까?
아무 곳에서나 기본 생성자를 호출할 수 없도록 제한하기 위해서는 protected
보다 private
수준의 접근 제한자를 활용하는 것이 더 좋지않을까?
그렇지만 JPA 공식 문서는 기본 생성자의 접근 제한자를 명시하며 private
를 사용하지 않도록 한다.
그 이유로는 Lazy Loading을 위한 Proxy 클래스에 있다. JPA 엔티티의 Proxy 클래스는 타겟 클래스를 상속해서 구현한다.
그러면 이 Proxy 클래스를 JPA가 생성하기 위해서 타겟 클래스의 기본 생성자를 사용하게 된다. 그러나 만약 부모 클래스인 타겟 클래스에 private
기본 생성자만 존재한다면, 자식 클래스인 Proxy 클래스는 기본 생성자에 접근할 수 없어 객체를 생성할 수 없는 이슈가 발생하게 된다.
4. Builder 패턴 사용하기
@Builder
어노테이션을 (1)클래스에 붙이거나 (2)생성자에 붙이는 방식으로 Builder 패턴을 간단히 구현할 수 있다.
구체적인 사용 방법은 다른 글에 정리해두었다.
[Lombok] @Builder 어노테이션으로 builder 패턴 사용하기
간단히 정리하자면 생성자에 @Builder
어노테이션을 붙여 사용하는 것이 좋다.
5. toString()
메소드에 연관관계 필드 제외하기
toString()
메소드에 연관관계 필드를 포함하게되면 무한루프 문제가 발생할 수 있다.
자동 생성된 toString()
메소드는 모든 필드의 정보를 포함해서 클래스의 정보를 출력하게 되는데,
@ToString(exclude = {"...", "...", ...})
@ToString
어노테이션의 exclude
속성값을 활용하여 toString()
메소드에서 연관관계 필드를 제외하도록 명시적으로 선언해 주는 것이 좋다.