본문 바로가기
TIL

@NoArgsConstructor,@AllArgsConstructor와(access=AccessLevel.PROTECTED) + @Builder

by Wanado 2025. 3. 10.
728x90

https://oh-sh-2134.tistory.com/107

 

@NoArgsConstructor(access = AccessLevel.PROTECTED)를 이용하여 의미있는 생성자를 만들어 보자( + @Bulider)

목표 @NoArgsConstructor와 함께 생성자를 만드는 어노테이션들을 알아보고 의미 있는 생성자를 만드는 방법을 알아보자. 내용 Entity위에서 자주 볼 수 있는 어노테이션은@NoArgsConstructor, @AllArgsConstructo

oh-sh-2134.tistory.com

 

 

 @NoArgsConstructor(access = AccessLevel.PROTECTED) 와 @AllArgsConstructor(access = AccessLevel.PROTECTED) 의미

1️⃣ @NoArgsConstructor(access = AccessLevel.PROTECTED)

@NoArgsConstructor(access = AccessLevel.PROTECTED) 
public class User {
private String name; 
private int age;
}

🔹 역할:

  • 매개변수가 없는 기본 생성자를 자동으로 생성합니다.
  • 생성자의 접근 제한을 PROTECTED로 설정하여 외부에서 객체 생성을 막음.

🔹 생성되는 코드:

위의 Lombok 어노테이션을 적용하면 다음과 같은 생성자가 자동으로 생성됩니다.

protected User() { }

이렇게 하면 같은 패키지나 하위 클래스에서는 객체를 생성할 수 있지만, 외부 클래스에서는 객체 생성을 막을 수 있습니다.

사용 이유

  • JPA(Entity)에서 기본 생성자가 필요할 때
    → JPA는 프록시 생성을 위해 기본 생성자가 필요하지만, 실수로 직접 호출하지 않도록 protected 접근 제한을 걸어둠.
  • 객체 생성을 제한하고 싶을 때
    → 객체를 오직 static factory method로 생성하고 싶은 경우 사용.

2️⃣ @AllArgsConstructor(access = AccessLevel.PROTECTED)

@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
    private String name;
    private int age;
}

🔹 역할:

  • 모든 필드를 포함하는 생성자를 자동으로 생성합니다.
  • 생성자의 접근 제한을 PROTECTED로 설정하여 외부에서 직접 객체 생성을 막음.

🔹 생성되는 코드:

위의 Lombok 어노테이션을 적용하면 다음과 같은 생성자가 자동으로 생성됩니다.

protected User(String name, int age) {
    this.name = name;
    this.age = age;
}

사용 이유

  • JPA에서 엔티티의 일관성을 유지하기 위해 사용
    → JPA에서는 setter를 만들지 않고, 생성자 기반으로 값을 설정하는 방식이 권장됨.
  • 객체 생성의 통제를 위해 사용
    → new User(name, age); 같은 직접 생성은 막고, 팩토리 메서드를 통해 생성하도록 강제할 때 사용.

@NoArgsConstructor(access = AccessLevel.PROTECTED) 와 @AllArgsConstructor(access = AccessLevel.PROTECTED) 를 사용하면 뭐가 다르지??

1️⃣ @NoArgsConstructor(access = AccessLevel.PROTECTED)로 발생하는 오류

@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
    private String name;
    private int age;
}

🔹 외부 클래스에서 객체를 생성하면 오류 발생

 
public class Main {
    public static void main(String[] args) {
        User user = new User(); // ❌ 컴파일 에러 발생
    }
}

2️⃣ @AllArgsConstructor(access = AccessLevel.PROTECTED)로 발생하는 오류

@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
    private String name;
    private int age;
}

🔹 외부에서 객체를 생성하면 오류 발생

public class Main {
    public static void main(String[] args) {
        User user = new User("Alice", 25); // ❌ 컴파일 에러 발생
    }
}
 
 
>>그래서 나타난 정적메서드

정적 팩토리 메서드 사용

이러한 제한이 있는 이유는 객체 생성을 제어하고, new User() 대신 특정 로직을 통해 인스턴스를 생성하도록 강제하기 위해서입니다.
정적 팩토리 메서드 (static factory method)를 사용하면 해결할 수 있습니다.

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
    private String name;
    private int age;

    // 정적 팩토리 메서드 추가
    public static User createUser(String name, int age) {
        return new User(name, age);
    }
}

🔹 올바른 사용법

public class Main {
    public static void main(String[] args) {
        // 정적 팩토리 메서드를 통해 객체 생성 (정상 동작)
        User user = User.createUser("Alice", 25);
    }
}

 



 

@Builder까지 추가한다면?

@NoArgsConstructor(access = AccessLevel.PROTECTED)  // 기본 생성자를 protected로 제한
@AllArgsConstructor(access = AccessLevel.PROTECTED) // 모든 필드 값을 받는 생성자를 protected로 제한
@Builder // 빌더 패턴 적용
public class User {
    private String name;
    private int age;
}

 

위와 같이 @Builder를 적용하면 JPA가 내부적으로 프록시 객체를 생성할 때 기본 생성자를 호출할 수 있습니다.
그러나 @Builder는 기본적으로 모든 필드를 포함하는 AllArgsConstructor를 사용하기 때문에,
id 필드까지 포함되어 있어 예상치 못한 동작이 발생할 수 있습니다.

 

@Builder 전용 생성자 추가

JPA에서 @Builder를 사용할 때는, @Builder가 id를 포함하지 않도록 따로 생성자를 지정하는 것이 좋습니다.

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@Builder(builderMethodName = "userBuilder") // 빌더 메서드 이름 변경
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private int age;

    // @Builder 전용 생성자 (id를 제외)
    @Builder
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

해결된 동작 방식

public class Main {
    public static void main(String[] args) {
        // ✅ 빌더를 사용하여 객체 생성 (id 자동 생성)
        User user = User.userBuilder()
                        .name("Alice")
                        .age(25)
                        .build();
        
        System.out.println(user);
    }
}

결론: 왜 이렇게 쓰는 게 좋은가?


@NoArgsConstructor(access = AccessLevel.PROTECTED) JPA에서 기본 생성자 필요할 때만 사용 가능, 외부에서 객체 생성 차단
@AllArgsConstructor(access = AccessLevel.PROTECTED) new User(name, age) 사용을 막고, 빌더나 팩토리 메서드로만 생성 가능
@Builder 필드가 많을수록 객체 생성이 간결해지고 유지보수성이 증가
DTO 변환과 함께 사용 서비스 계층에서 DTO 사용으로 코드 안정성 증가

JPA 엔티티를 안전하게 관리하면서, 유지보수하기 쉬운 코드 작성 가능
불필요한 객체 생성을 막고, 빌더 패턴을 통해 명확한 객체 생성 가능
DTO와 함께 사용할 때 더욱 유용한 구조 제공
필드가 많아질수록 빌더 패턴을 사용하면 코드의 가독성과 유지보수성이 향상됨

728x90