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와 함께 사용할 때 더욱 유용한 구조 제공
✅ 필드가 많아질수록 빌더 패턴을 사용하면 코드의 가독성과 유지보수성이 향상됨
'TIL' 카테고리의 다른 글
Error와 예외처리 (0) | 2025.03.12 |
---|---|
spring cloud - Gateway (0) | 2025.03.11 |
StackOverflowError:null (feat.AuthenticationManager) (0) | 2025.03.07 |
Delivery프로젝트_5. (Trouble Shooting) JPA 연관관계 (단방향, 양방향) (0) | 2025.02.20 |
Delivery프로젝트_6. 단위테스트와 통합테스트 (1) | 2025.02.19 |