Entity 클래스 생성 시 올바른 어노테이션 사용법

일반적으로 많이 사용하는 어노테이션

@Getter
@Setter => 문제 1. 객체가 무분별하게 변경될 가능성 있음
@NoArgsConstructor => 문제 2. 기본 생성자의 접근 제어자가 불명확함
@Builder
@AllArgsConstructor => 문제3. 객체 내부의 인스턴스멤버들을 모두 가지고 있는 생성자를 생성 (매우 위험)
@Entity
public class Member

이 어노테이션에서 문제 3가지가 있다. 이를 개선하기 위해 어노테이션을 수정하자.

수정 1. @Setter를 사용하지 않기 Setter는 그 의도가 분명하지 않고 객체를 언제든지 변경할 수 있는 상태가 되어서 객체의 안전성이 보장받기 힘듬. 특히 엔티티에서는 @Setter를 사용 시 해당 변경 가능성이 어디서 누구에 의해 발생했는지 추적하기가 힘들어진다.

값 변경이 필요한 경우 의미 있는 메서드를 생성하여 이를 사용하자!

따라서 의미있는 메서드로 생성 하자

수정 2.@NoArgsConstructor(access = AccessLevel.PROTECTED)로 변경

기본 생성자(NoArgsConstructor)의 접근 제어를 PROCTECTED 로 설정하면 아무런 값도 갖지 않는 의미 없는 객체의 생성을 막는다. 즉 무분별한 객체 생성에 대해 한번 더 체크할 수 있다.

//@NoArgsConstructor(access = AccessLevel.PROTECTED)

Member member = new Member(); //컴파일 에러 발생 이때, 의미있는 객체 생성을 위해서 @Builder을 사용하는데,

@Builder를 사용하는 방법은 총 2가지가 있다. 1) 클래스에 @Builder 2) 생성자에 @Builder

이는 다음 @AllArgsConstructor을 쓰지 않아야 하는 것과 관련이 있다.

수정 3. @AllArgsConstructor는 쓰지 않기

만약 수정 2의 방법1(클래스에 @Builder를 붙이기)을 사용, 클래스 레벨에서 @Builder와 @NoArgsConstructor를 함께 쓰면 오류가 발생.

이를 해결하기 위해서는 모든 필드를 가지는 생성자를 만들어줘야해서 @AllArgsConstructor도 같이 써주게 되는데, 하지만 @AllArgsConstructor 는 위험하다. 클래스에 존재하는 모든 필드에 대한 생성자를 자동으로 생성하는데, 인스턴스 멤버의 선언 순서에 영향을 받기 때문에 두 변수의 순서를 바꾸면 생성자의 입력 값 순서도 바뀌게 되어 검출되지 않는 치명적인 오류를 발생.

그래서 수정 2의 방법2(생성자에 @Builder)를 사용해서

@AllArgsConstructor를 쓰는 일이 없도록 하자.

리팩토링 결과

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class Member {

    @Id
    @Column(name = "id", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String email;

    private String picture;

    @Enumerated(EnumType.STRING)	// Enum타입 객체를 쓸때 활용!
    private Role role;              // 반드시 String타입으로 바꿔주고 DB 필드에 널어주어야 한다! 

    public Member update(String name, String picture) {
        this.name = name;
        this.picture = picture;
        return this;
    }

// 생성자에 @Builder 적용
    @Builder
    public Member(String name, String email, String picture, Role role) {
        this.name = name;
        this.email = email;
        this.picture = picture;
        this.role = role;
    }
}