[Payment Entity]
@Entity
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Table(name = "payment")
public class Payment extends BaseEntity {
@Column(nullable = false)
private String orderName;
@Column(nullable = false)
private BigDecimal amount;
//TODO: enum테이블 정의하기
// @Column(nullable = false)
// private PaymentStatus status;
private BigDecimal discountAmount;
@Column(nullable = false)
private BigDecimal paidAmount;
private LocalDateTime completed_at;
@Column(nullable = false)
private String tossPaymentKey;
@OneToMany(mappedBy = "payment", cascade = CascadeType.ALL)
private List<PaymentHistory> paymentHistoryList = new ArrayList<>(); // 결제 이력
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id", nullable = false)
private Member member; // 회원 id
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "order_id", nullable = false)
private Order order; // 주문 id
}
[PaymentHistory Entity]
@Getter
@Entity
@AllArgsConstructor
@NoArgsConstructor
@Builder
@Table(name = "payment_history")
public class PaymentHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long paymentHistoryId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "payment_id", nullable = false)
private Payment payment;
//TODO: enum으로 교체하기
// private String afterStatus;
private Boolean latestStatus;
private LocalDateTime statusUpdatedAt; // 결제 상태 변경 일시
}
[PaymentMethod Entity]
@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PaymentMethod extends BaseEntity {
//TODO: 머지했을 떄 충돌 여부 확인
// @ManyToOne(fetch = FetchType.LAZY)
// @JoinColumn(name = "payment_id", nullable = false)
// private Member member;
@Column(nullable = false, length = 100)
private String methodType;
@Column(nullable = false)
private String provider;
@Column(nullable = false)
private Boolean isDefault;
//TODO: enum으로 교체하기
// @Column(nullable = false, length = 50)
// private String status;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "payment_id", nullable = false)
private Payment payment;
}
📌 @Builder, @NoArgsConstructor, @AllArgsConstructor 조합의 필요성과 장점
Spring Boot + JPA를 사용할 때, 엔티티 클래스에서 @Builder, @NoArgsConstructor, @AllArgsConstructor를 함께 사용하는 경우가 많습니다.
이 글에서는 각각의 역할과 한계를 분석하고, 왜 함께 사용하는 것이 좋은지 알아보겠습니다.
1️⃣ @Builder의 역할과 한계
🔹 빌더 패턴 적용 (@Builder)
@Builder는 객체를 생성하는 데 있어서 빌더 패턴(Builder Pattern)을 적용합니다.
빌더 패턴은 객체를 유연하고 가독성 높게 생성할 수 있도록 도와줍니다.
✅ @Builder의 장점
✔️ 객체 생성의 유연성
• @Builder를 사용하면 필요한 필드만 선택적으로 설정하여 객체를 생성할 수 있습니다.
• 필드가 많아질수록 생성자의 파라미터 순서를 외울 필요 없이 유연하게 사용할 수 있습니다.
✔️ 가독성 향상
• @Builder를 사용하면 new 키워드 없이 더 명확하고 직관적인 코드로 객체를 생성할 수 있습니다.
❌ @Builder의 한계
⚠️ 기본 생성자가 자동으로 생성되지 않음
• @Builder만 사용하면 기본 생성자(@NoArgsConstructor)가 자동으로 생성되지 않습니다.
• 기본 생성자가 없으면 JPA에서 엔티티를 생성할 수 없으므로, 추가적으로 @NoArgsConstructor를 함께 사용해야 합니다.
⚠️ 모든 필드를 받는 생성자가 자동 생성되지 않음
• @Builder를 사용하면, 객체를 개별 필드에 대해 설정할 수 있지만,
모든 필드를 한 번에 받는 생성자는 자동 생성되지 않습니다.
• 따라서, 테스트 코드 작성 시 @AllArgsConstructor를 함께 사용하면 더 편리합니다.
2️⃣ @NoArgsConstructor와 @AllArgsConstructor가 필요한 이유
🔹 JPA/Hibernate에서 기본 생성자가 필요한 이유 (@NoArgsConstructor)
JPA(Java Persistence API)와 Hibernate는 엔티티를 관리할 때 기본 생성자(No-args Constructor) 가 필요합니다.
이는 다음과 같은 이유 때문입니다.
✔️ JPA가 리플렉션(Reflection)으로 엔티티를 생성하기 때문
• JPA는 데이터베이스에서 엔티티를 조회할 때 리플렉션을 이용하여 객체를 생성합니다.
• 이 과정에서 기본 생성자가 없으면 엔티티를 생성할 수 없습니다.
✔️ 기본 생성자를 protected로 제한하는 것이 권장됨
• 엔티티는 임의로 new 연산자로 생성하기보다는 JPA를 통해 관리하는 것이 일반적입니다.
• 따라서, 기본 생성자를 public으로 두기보다는, protected로 설정하여 외부에서 직접 생성하지 못하도록 막는 것이 좋습니다.
💡 📌 해결 방법 (권장)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
위와 같이 설정하면 JPA에서는 엔티티를 생성할 수 있지만, 외부에서 new 키워드로 생성하는 것은 막을 수 있습니다.
🔹 모든 필드를 포함한 생성자가 필요한 이유 (@AllArgsConstructor)
✔️ 데이터 복제 및 복사 가능
• JPA를 사용하지 않고도, 모든 필드를 갖는 객체를 쉽게 복제할 수 있습니다.
• new 키워드를 사용하여 새로운 객체를 만들 때 유용합니다.
✔️ 테스트 코드 작성의 편의성
• @Builder만 사용할 경우 각 필드를 하나씩 설정해야 하므로 테스트 코드에서 불편할 수 있습니다.
• @AllArgsConstructor를 사용하면, 모든 필드를 한 번에 넣어 빠르게 객체를 생성할 수 있습니다.
💡 📌 해결 방법
@AllArgsConstructor
위와 같이 설정하면 모든 필드를 받는 생성자가 자동으로 생성됩니다.
3️⃣ @Builder, @NoArgsConstructor, @AllArgsConstructor 조합의 장점
세 개의 어노테이션을 조합하면 다음과 같은 장점을 얻을 수 있습니다.
어노테이션역할필요성
| @Builder | 빌더 패턴 적용 | 선택적 필드 설정 가능, 가독성 증가 |
| @NoArgsConstructor(access = AccessLevel.PROTECTED) | 기본 생성자 제공 | JPA 엔티티 생성을 위해 필수 |
| @AllArgsConstructor | 모든 필드를 포함한 생성자 제공 | 테스트 코드 작성 및 복제/복사 편의성 증가 |
📌 결론
@Builder, @NoArgsConstructor, @AllArgsConstructor를 함께 사용하면 JPA 호환성을 유지하면서 객체 생성의 유연성을 극대화할 수 있습니다.
✅ JPA 엔티티 생성 가능 (@NoArgsConstructor)
✅ 빌더 패턴 적용하여 가독성 향상 (@Builder)
✅ 테스트 코드에서 빠르게 객체 생성 가능 (@AllArgsConstructor)
📌 💡 엔티티 클래스 적용 예시
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
@Builder
@Table(name = "payment_history")
public class PaymentHistory {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long paymentHistoryId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "payment_id", nullable = false)
private Payment payment;
private String afterStatus;
private Boolean latestStatus;
private LocalDateTime statusUpdatedAt; // 결제 상태 변경 일시
}
위와 같은 조합을 사용하면 JPA 호환성을 유지하면서도, 가독성이 높고 유연한 객체 생성이 가능하다.
'뚜띠' 카테고리의 다른 글
| 예외처리 효과적으로 관리하는법 (0) | 2025.03.13 |
|---|---|
| ERD 최종 점검 (0) | 2025.02.26 |