Account 프로젝트를 리팩터링하는 과정에서 domain 패키지에 BaseEntity를 만들었고, 상속 관계에서 Builder가 충돌하는 문제가 발생했다.
이것을 해결하기 위하여 @SuperBuilder를 사용했는데, 오늘은 Builder와 SuperBuilder의 차이점에 대해서 알아보자.
Builder 패턴은 객체를 생성할 때 가독성을 높이고, 선택적으로 필드를 설정할 수 있는 패턴이다. 특히, 생성자에 많은 매개변수가 필요할 때 코드가 깔끔해진다는 장점이 있다.
아래는 AccountUser 엔터티에서 @Builder를 사용한 코드이다.
package com.example.account.domain;
import jakarta.persistence.*;
import lombok.*;
import org.springFramework.annotation.CreateDate;
import org.springframework.annotation.LastModifiedDate;
import java.time.LocalDateTime;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity
public class AccountUser {
@Id
@GeneratedValue
private Long id;
private String name;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
public class Main {
public static void main(String[] args) {
AccountUser user = AccountUser.builder()
.id(1L)
.name("Dami")
.createdAt(LocalDateTime.now())
.updatedAt(LocalDateTime.now())
.build();
System.out.println(user.getName()); // Dami
}
}
이렇게 AccountUser.builder()를 사용하면 모든 필드를 선택적으로 설정할 수 있으며, 모든 필드가 같은 클래스에 있기 때문에 @Builder가 정상적으로 동작한다.
하지만, 여러 엔터티에서 id, createdAt, updatedAt 같은 공통 필드를 계속 선언해야 하므로 코드 중복이 발생한다.
이를 해결하기 위해 BaseEntity를 만들어 공통 필드를 분리하면, Builder 패턴이 깨질 위험이 있다. 즉, BaseEntity를 상속하는 순간, @Builder가 부모 클래스의 필드를 제대로 처리하지 못하는 문제가 발생한다.
이제 BaseEntity를 만들어서 AccountUser가 상속하도록 변경해보자.
package com.example.account.domain;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder // ❌ 문제 발생 가능
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@Id
@GeneratedValue
private Long id;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
package com.example.account.domain;
import jakarta.persistence.Entity;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder // ❌ 문제 발생 가능
@Entity
public class AccountUser extends BaseEntity {
private String name;
}
public class Main {
public static void main(String[] args) {
AccountUser user = AccountUser.builder()
.name("Alice")
.id(1L) // ❌ id를 설정할 수 없음
.createdAt(LocalDateTime.now()) // ❌ 부모 필드 사용 불가
.build();
}
}
@Builder는 부모 클래스(BaseEntity)의 필드를 포함하지 않음Account.builder()를 사용할 때 id, createdAt, updatedAt을 사용할 수 없게 된다.@SuperBuilder를 사용해야 한다.@SuperBuilder는 상속된 필드까지 포함하여 빌더를 생성한다.
이제 BaseEntity와 AccountUser를 @SuperBuilder로 변경해보자.
package com.example.account.domain;
import jakarta.persistence.*;
import lombok.*;
import lombok.experimental.SuperBuilder;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder // ✅ SuperBuilder 적용
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@Id
@GeneratedValue
private Long id;
@CreatedDate
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
package com.example.account.domain;
import jakarta.persistence.Entity;
import lombok.*;
import lombok.experimental.SuperBuilder;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder // ✅ SuperBuilder 적용
@Entity
public class AccountUser extends BaseEntity {
private String name;
}
public class Main {
public static void main(String[] args) {
AccountUser user = AccountUser.builder()
.id(1L) // ✅ 이제 설정 가능!
.createdAt(LocalDateTime.now()) // ✅ 부모 필드도 설정 가능!
.updatedAt(LocalDateTime.now())
.name("Alice")
.build();
System.out.println(user);
}
}
이제 부모 클래스(BaseEntity)의 필드도 빌더에서 설정할 수 있다!
이는 SuperBuilder가 상속된 필드까지 고려하기 때문이다.
| 비교 항목 | Builder | SuperBuilder |
|---|---|---|
| 기본 개념 | 객체를 빌더 패턴으로 생성 | 상속 관계에서도 빌더 패턴 적용 가능 |
| 상속 지원 | ❌ 지원 안 됨 | ✅ 상속된 필드 포함 |
| 사용 방식 | @Builder 사용 | @SuperBuilder 사용 |
| 유연성 | 단일 클래스에서는 충분 | 상속 관계에서는 필수적 |
@Builder를 사용해도 충분하다.@SuperBuilder를 써야 부모 클래스 필드까지 제대로 설정할 수 있어.@Builder가 상속을 제대로 처리하지 못해서이다. → 이때 @SuperBuilder로 변경하면 해결 가능하다.