- 엔티티에서 setter 를 사용하는 것보다 생성자를 통해 파라미터를 넘기는 것이 좋습니다.
- Setter를 남용하다 보면 여기저기서 객체의 값을 변경할 수 있으므로 객체의 일관성을 보장할 수가 없습니다.
- 그리고 setter 는 의도를 알아채기 힘들기 때문에 사용을 자제해야합니다.
그러므로 protect 생성자를 생성하여 아무곳에서 생성되는 것을 막는것이 좋습니다.
Admin admin = new Admin();
admin.setName("codej");
admin.setAge(27);
- 그러나 JPA 표준 스펙에 디폴트 생성자가 있어야 하기에 private 생성자를 사용할 수 없습니다.
JPA는 프록시 기술을 사용하는데 거기서 프록시 기술을 쓸 때, jpa hibernate가 객체를 강제로 만들어야하는데 private 로 만들어보리면 막혀버리기 때문입니다. 그래서 protected 생성자를 사용하는 편입니다.
- lombok 의 @NoArgsConstructor(access = AccessLevel.PROTECTED)를 엔티티 클래스 위에 선언함으로 간단하게 생성자를 사용할 수 있습니다.
그럼 왜 JPA의 엔티티에 기본 생성자가 필요할까?
-
JPA는 엔티티에 기본 생성자, 즉 아무런 매개변수를 받지 않는 생성자를 만드는 것을 강제하고 있습니다. JPA 구현체마다 스펙이 조금씩 달라서 기본 생성자를 만들지 않아도 정상적으로 작동하는 경우가 있지만, 엔티티에는 기본 생성자가 있어야 합니다
-
그렇다면 JPA 는 왜 엔티티에 기본 생성자를 만들도록 강제하는 것일까?
- JPA와 유사하게 기본 생성자를 강제하는 경우를 살펴보면, 스프링을 하면서 기본 생성자가 강제되는 경우가 있습니다.
@RequestBody
를 DTO로 바인딩하는 과정에서 바인딩할 타입에 기본생성자가 존재하지 않는다면 정상적으로 바인딩되지 않는 예외가 발생합니다.
- 이는 스프링의
@RequestBody
바인딩 방식이 기본생성자를 통해 객체를 생성한 후 Java Reflection
을 이용하여 필드 값을 집어넣어 주는 방식이기 때문입니다.
- Reflection은 클래스의 이름만 알아도 생성자,필드,메서드 등 클래스의 모든 정보에 접근이 가능합니다.
- 하지만 Reflection이 가져올 수 없는 정보가 있는데, 바로 생성자의 매개변수 정보 입니다.
- 때문에 Reflection으로 생성할 객체에 모든 필드를 받는 생성자가 있더라도 Reflection은 해당 생성자를 호출할 수가 없습니다.그래서 Reflection은 기본생성자로 객체를 생성하고 필드 값을 강제로 매핑해주는 방식을 사용합니다.
-
JPA 역시 데이터를 DB에서 조회해 온 뒤 객체를 생성할 때 Reflection을 사용합니다.
- 때문에 기본 생성자로 객체를 생성합니다. 실제로 빈 생성자와 완전한 생성자에 각각 다른 로그를 찎어놓고 테스트를 해보면, 기본 생성자에 만들어 놓은 로그만 찍히는 것을 볼 수 있습니다.
- 결론적으로는 기본 생성자가 존재하지 않는다면 DB에서 조회해 온 값을 엔티티로 만들 때 객체 생성 자체가 실패하게 되기 때문에 , JPA에서는 기본스펙으로 기본 생성자를 반드시 생성해 줄 것을 정해놓고 있는 것입니다.
-
왜 Reflection을 사용할까?
- 이는 우리가 엔티티로 어떤 타입을 생성할지 JPA는 알 수 없기 때문입니다.
- Reflection을 사용하지 않고 객체를 생성하려면 미리 객체의 타입을 알고 있어야 합니다.
- 하지만 프레임 워크나 라이브러리는 사용자가 정의할 구체 클래스 정보를 알 수가 없습니다.
- 때문에 어떤 타입으로 엔티티를 만들더라도 해당 엔티티를 생성하기 위해서 Reflection을 사용하여 엔티티 인스턴스를 만들어 주는 것입니다.