Account 시스템 업그레이드 | Builder vs. SuperBuilder

Faithful Dev·2025년 3월 4일

스프링 프레임워크

목록 보기
20/20

Account 프로젝트를 리팩터링하는 과정에서 domain 패키지에 BaseEntity를 만들었고, 상속 관계에서 Builder가 충돌하는 문제가 발생했다.
이것을 해결하기 위하여 @SuperBuilder를 사용했는데, 오늘은 Builder와 SuperBuilder의 차이점에 대해서 알아보자.

Builder 패턴

Builder 패턴은 객체를 생성할 때 가독성을 높이고, 선택적으로 필드를 설정할 수 있는 패턴이다. 특히, 생성자에 많은 매개변수가 필요할 때 코드가 깔끔해진다는 장점이 있다.

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가 부모 클래스의 필드를 제대로 처리하지 못하는 문제가 발생한다.


Builder의 한계 (상속 문제)

이제 BaseEntity를 만들어서 AccountUser가 상속하도록 변경해보자.

BaseEntity 추가

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;
}

AccountUser에서 BaseEntity 상속

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: Builder의 상속 문제 해결

@SuperBuilder상속된 필드까지 포함하여 빌더를 생성한다.
이제 BaseEntityAccountUser@SuperBuilder로 변경해보자.

BaseEntity 수정

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;
}

AccountUser 수정

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;
}

SuperBuilder 적용 후 사용 코드

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 vs. SuperBuilder 정리

비교 항목BuilderSuperBuilder
기본 개념객체를 빌더 패턴으로 생성상속 관계에서도 빌더 패턴 적용 가능
상속 지원❌ 지원 안 됨✅ 상속된 필드 포함
사용 방식@Builder 사용@SuperBuilder 사용
유연성단일 클래스에서는 충분상속 관계에서는 필수적

정리

  • 단일 클래스에서는 @Builder를 사용해도 충분하다.
  • 상속 관계에서는 @SuperBuilder를 써야 부모 클래스 필드까지 제대로 설정할 수 있어.
  • 빌더 충돌이 발생하는 경우, 대부분 @Builder가 상속을 제대로 처리하지 못해서이다. → 이때 @SuperBuilder로 변경하면 해결 가능하다.
profile
Turning Vision into Reality.

0개의 댓글