🚩 1. 들어가며...
JPA Spring Date를 공부할 목적으로 개인 프로젝트를 진행하며 Entity클래스를 구현하고 있던 중 Lombok 라이브러리의 @AllArgsConstructor의 문제점과 @Setter를 지양해야 한다는 것을 알게 되었다. 따라서 '@AllArgsConstructor', '@Setter'이 두 어노테이션을 사용하지 않고 Entity객체를 만들고 @ToString 사용시에도 순환 참조에 유의하여 사용하는 방법에 대해서 포스팅할 것이다.
🚩 2. @AllArgsConstructor, @Setter 사용을 지양하고 @Builder를 도입하기
📌 2-1 기존 코드
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long no;
@Column(nullable = false, length = 100)
private String thumbnailPath;
@Column(nullable = false, length = 45)
private String name;
@Column(nullable = false, length = 255)
private String content;
// 연관 관계 매핑 (Category 엔티티와의 관계)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_no")
private Category category;
}
기존의 코드들은 보통 이렇게 어노테이션이 덕지덕지 붙어있는 채로 작성되어 있을 것이다. 먼저 JPA Entity는 기본 생성자가 필요하다. 그렇기 때문에 기본 생성자를 명시적으로 작성하거나 @NoArgsConstructor를 붙여야 한다. 그리고 @Builder는 자동으로 모든 인자를 받는 생성자를 만들지만, 이미 생성자가 존재하면 자동으로 생성자를 만들지 않는다. 그렇기 때문에 모든 인자를 받는 생성자를 만들기 위해 추가로 @AllArgsConstructor도 붙여야한다.
- @Setter
- @NoArgsConstructor
- @AllArgsConstructor
- @ToString
그런데 이 어노테이션들은 몇 가지 문제점과 개선점이 있다.
🚩 3. 각 어노테이션의 문제점과 개선점
📌 3-1 @Setter의 문제점과 개선법
- @Setter 어노테이션은 객체의 필드 값을 쉽게 변경할 수 있게 해주지만, 이는 곧 어디서든 필드 값을 변경할 수 있기 때문에 객체의 안전성을 해칠 수 있다.
- 그렇기 때문에 특히 JPA 엔티티에서는 사용하지 않는 것이 좋다. 대신, 의미 있는 메서드를 통해 값을 변경하는 방식을 추천한다.
➡️ 의미 있는 메서드로 대신하자! 하지만 객체의 무결성을 해칠 수 있으므로 웬만해선 JPA 엔티티에서 권장하지 않는다.
📌 3-2 @NoArgsConstructor의 접근 제어자의 중요성
- @NoArgsConstructor 어노테이션은 기본 생성자를 자동으로 생성해준다. 하지만 이 어노테이션을 접근 제어자를 명확하게 정의하지 않고 그대로 사용하면 의미 없는 객체 생성을 막을 수 없다.
- 따라서 '@NoArgsConstructor(access = AccessLevel.PROTECTED)' 와 같이 어노테이션을 정의함과 동시에 접근제어자를 명시하는 것이 좋다. 이렇게 하면 어디서든 무분별한 객체 생성을 방지하고 코드의 안전성을 높일 수 있다.
- 의미있는 객체를 생성하기 위해서 클래스나 메서드 레벨에서 @Builder를 사용할 수 있다.
➡️ @NoArgsConstructor을 사용할 때 접근 제어자를 명시하여 무분별한 객체 생성을 방지하고 @Builder를 사용하자!
📌 3-3 @AllArgsConstructor의 생성자 자동 생성의 함정
- @AllArgsConstructor 어노테이션은 클래스의 모든 필드를 인자로 받는 생성자를 자동으로 만들어준다. (하지만 이미 생성자가 존재하는 경우에는 자동으로 만들어주지 않는다.) 그러나 이 방식은 필드의 선언 순서에 영향을 받는다.
- 이 말은 곧 변수의 순서 변경되면 생성자의 입력 값 순서도 바뀌어 여러곳에서 큰 오류가 발생할 수 있다. 이러한 문제를 피하기 위해서 @AllArgsConstructor어노테이션 대신 생성자(메서드 레벨)에 @Builder를 사용해서 @AllArgsConstructor사용을 지양한다.
➡️ 필드의 순서 변경으로 인한 오류 가능성을 배제하기 위해 @AllArgsConstructor을 사용하지 않고 생성자에 @Builder를 사용하자!
📌 3-4 @ToString 연관 관계 필드 주의하기
- @ToString 어노테이션은 객체의 문자열 표현을 자동으로 생성해준다. 하지만 연관관계가 매핑된 필드를 포함할 경우 무한 루프 문제가 발생할 수 있다.
- 따라서 '@ToString(exclude = "...")'를 사용하여 연관 관계 필드를 명시적으로 제외해준다. 이 방법은 객체간의 관계를 명확히 하고 무한 루프의 위험을 피할 수 있다.
➡️ '@ToString(exclude = "...")'를 사용하여 연관 관계 필드를 명시적으로 제외하여 무한 루프의 위험에서 벗어나자!
❗Lombok 공식 문서
Stable
projectlombok.org
🚩 4. 개선된 JPA Entity 클래스
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@ToString(exclude = {"category"})
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long no;
@Column(nullable = false, length = 100)
private String thumbnailPath;
@Column(nullable = false, length = 45)
private String name;
@Column(nullable = false, length = 255)
private String content;
// 연관 관계 매핑 (Category 엔티티와의 관계)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "category_no")
private Category category;
@Builder
public Product(Long no, String thumbnailPath, String name, String content, Category category) {
this.no = no;
this.thumbnailPath = thumbnailPath;
this.name = name;
this.content = content;
this.category = category;
}
}
🚩 5. 마치며...
자바 어노테이션은 코드를 간결하게 구현할 수 있게 도와주는 아주 강력한 기능이다. 특히 Lombok은 클래스를 작성할 때 정말 편리하고 작성할 수 있도록 도움을 준다. 하지만 너무나도 편리하기 때문에 어노테이션의 작동 방식을 잘 알지 못한채로 무분별하게 사용하게 되면 예상치 못한 오류나 코드의 안정성을 해치고, 불변성을 가져야하는 목표를 가진 객체가 불변성을 잃을 수도 있다. 그렇기 때문에 이 강력하고 편리한 기능을 사용하기 위해서는 어노테이션에 대한 이해도를 가져야 한다. 그렇게 된다면 어노테이션 기능은 나의 개발의 생산성을 높이고, 좀 더 편리하고 간결하게 개발을 도와줄 것이다.