@Setter로 생성자 이용 vs @Builder
JUnit 테스트 할 때 @Setter를 사용하면 set문장이 많아져 코드가 길어진다. 이를 @Builder를 사용하면 한줄코드로 간편하다. 또한, 명확히 어떤 필드에 어떤 값을 채워야 할지를 알 수 있는 장점이 있다.
(@ Builder는 해당 클래스의 빌더 패턴 클래스를 생성해주고, 생성자 상단에 선언 시 생성자에 포함된 필드만 빌더에 포함한다.)
주의: @Builder를 사용하려면 기본생성자를 만들어주는
@NoArgsConstructor(access = AccessLevel.PROTECTED) 어노테이션도 함께 써야 한다.
그 이유는
- Reflection을 통한 객체 생성: 빈 객체를 생성하고 필드 값을 설정하기 위해 Java Reflection을 사용할 때, 해당 클래스는 기본 생성자를 가져야 합니다. Lombok의 @Builder가 빈 객체를 생성할 때에도 기본 생성자를 사용하는데, 이때 @NoArgsConstructor가 필요합니다.
- JPA (Java Persistence API)에서의 사용: 많은 JPA 프레임워크에서는 엔티티 클래스에 기본 생성자를 요구합니다. @Builder를 사용하여 객체를 생성하는 경우, JPA가 엔티티를 올바르게 관리하기 위해서는 기본 생성자가 필요합니다.
따라서 @Builder를 사용할 때에는 해당 클래스에 기본 생성자를 제공하는 @NoArgsConstructor를 함께 사용하는 것이 권장됩니다. 이를 통해 코드의 안정성을 높일 수 있고, 다양한 상황에서 예상치 못한 문제를 방지할 수 있습니다.
코드예시:
엔티티 코드
package com.pnow.domain;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import javax.persistence.*;
import java.util.List;
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED) //기본생성자
//@Setter
public class Category {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "category_id")
private Long id;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private CategoryType categoryName;
@OneToMany(mappedBy = "category", cascade = CascadeType.REMOVE)
private List<Store> storeList;
@Builder //JUnit 테스트용
public Category(CategoryType categoryName) {
this.categoryName = categoryName;
}
}
JUnit repository 테스트에서 Builder() 사용 코드
package com.pnow.repository;
import com.pnow.domain.Category;
import com.pnow.domain.CategoryType;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Slf4j
//@ExtendWith(SpringExtension.class) //JUnit5 버전
@SpringBootTest //자동으로 h2 데이터베이스 실행함
public class CategoryRepositoryTests {
@Autowired
private CategoryRepository categoryRepository;
@AfterEach //JUnit5
public void cleanup() {
categoryRepository.deleteAll();
}
@Test
public void 카테고리저장_불러오기() {
//given
for (CategoryType category: CategoryType.values()) {
log.info("category : {}",category);
categoryRepository.save(Category.builder()
.categoryName(category)
.build());
}
//when
List<Category> categoryList = categoryRepository.findAll();
//then
Category category = categoryList.get(0);
assertEquals(CategoryType.한식, category.getCategoryName());
}
}
생성자나 빌더나 생성 시점에 값을 채워주는 역할은 똑같다. 다만, 생성자의 경우 지금 채워야 할 필드가 무엇인지 명확히 지정할 수 없다.
예를 들어 다음과 같은 생성자가 있다면 개발자가 new Example(b, a) 처럼 a와 b의 위치를 변경해도 코드를 실행하기 전까지는 문제를 찾을 수가 없다.
public Example(String a, String b) {
this.a=a;
this.b=b;
}
하지만 빌더를 사용하게 되면 다음과 같이 어느 필드에 어떤 값을 채워야 할지 명확하게 인지할 수 있다.
Example.builder()
.a(a)
.b(b)
.build();
'JPA' 카테고리의 다른 글
[JPA] Repository에서 DTO로 데이터 셋팅 @Query() (0) | 2024.02.22 |
---|---|
[JPA] Entity PK는 bigint(MySQL)-Long(java) 타입으로 해야 하는 이유 (0) | 2024.02.15 |
[JPA] jpa insert시 default 적용 (0) | 2024.02.13 |
[JPA] jpa 메서드 (2) | 2024.02.12 |