스린이는 엔티티 설정하면서부터 모르는게 많아서 막히는 부분들이 좀 있었고, 관련해서 새로 알게 된 내용들을 조금 정리해보았다. 별건 아니지만 도움이 되는 사람이 있기를,, 바라면서 올려봄
엔티티 클래스 기본 권장 사항
✅ @NoArgsConstructor(access = AccessLevel.*PROTECTED*) 를 기본적으로 붙일 것
- 엔티티 생성을 막기 위함 → 엔티티는 보통 new로 생성하지 않는데, PROTECTED 로 하면 new로 외부에서 무분별하게 생성하지 못하게 막을 수 있음
외래키 설정하는 법
1-1) 일대다 관계일 때 (연관 관계 O)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(nullable = false)
private HoneyWalletEntity honeyWallet;
@ManyToOne, @JoinColumn 을 통해 HoneyHistoryEntity에서 HoneyWalletEntity의 id를 외래키로 설정 가능하다. 이때, honey_wallet_id 하나만 참조하는 경우여도 엔티티 타입으로 해야 한다.
= 외래키 제약조건 사용
1-2) 일대다 관계일 때 (연관 관계 O)
@Column(nullable = false)
private Long honeyWalletId;
다른 애그리게이트 참조하거나(ID만 필요), sync 테이블을 참조하는 경우(엔티티가 없으므로)
@ManyToOne 또는 @OneToMany을 굳이 사용하지 않는다.
JPA를 이용한 엔티티 매핑의 표준만을 생각한다면 위 방식이 맞지만, 실무에서는 도메인 설계 철학에 맞춘 이와 같은 방식이 장기적인 시스템 유지보수와 확장에 훨씬 유리하다고 할 수 있다.
처음에 나는 외래키를 참조하면 무조건 연관 관계 맵핑을 해야 하는 줄 알았는데 필요없는 경우에는 오히려 비효율적인 방법이라는 걸 알았다. 연관 관계를 맺을지 말지를 고민 중이라면 바로 아래 내용을 참고해보면 좋을 것 같다.
🍯 연관 관계를 맺는 기준
외래키라고 해서 반드시 연관 관계를 설정할 필요는 없다‼️
→ 엔티티 간 생명 주기를 공유한다면 연관 관계를 맺는다 ✅
💡 DDD의 Aggregate 개념 알고가기
애그리거트(Aggregate): DDD에서 일관성을 유지해야 하는 엔티티와 값 객체의 묶음
- 애그리게이트의 규칙
- 트랜잭션 경계: 하나의 애그리게이트는 하나의 트랜잭션 단위이자 하나의 영속성(Persistence) 단위로 관리된다.
- 참조 제한: 다른 애그리게이트를 참조할 때는 원칙적으로 객체 참조(엔티티 객체) 대신 ID로만 참조해야 한다.
예시로 이해하기
만약 Order 애그리게이트가 Customer 애그리게이트를 @ManyToOne Customer customer로 참조한다면, Order를 조회할 때 Customer 객체까지 함께 로딩되거나, Customer가 변경될 때 Order 트랜잭션에 영향을 줄 수 있다.
<주문 애그리게이트>
| 애그리게이트 | 구성 요소 | 외부 참조 방법 |
|---|---|---|
Order (루트) |
OrderLine, ShippingInfo 등 |
Order 엔티티 외부에서는 orderId로만 참조해야 함 |
Customer (루트) |
Address, Contact 등 |
Customer 엔티티 외부에서는 customerId로만 참조해야 함 |
같은 애그리게이트 내의 구성 요소 엔티티들은 서로 엔티티 객체 자체를 참조❗
- 결론: 생명 주기가 독립적인 별개의 애그리게이트를 참조할 때는 객체 참조(
@ManyToOne)를 끊고 ID만 사용하여 느슨하게 연결하는 것이 DDD 원칙에 부합한다
2) 상속 관계일 때 (일대일 확장)

@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "type")
public class InAppNotification {
@Id
private Long in_app_notification_id;
@Enumerated(EnumType.STRING)
private NotificationType type;
//...
}
- 부모
- 상속 관계에서 조인 전략을 사용하겠다는 의미로
@Inheritance(strategy = InheritanceType.JOINED)선언 @DiscriminatorColumn를 통해 자식 엔티티의 타입을 구분하는 컬럼(type)을 명시- ENUM(’CHAT’, ‘APPLICATION’, ‘MATCH’) 타입의 type 칼럼으로 자식 엔티티를 구분하라고 지시하는 것
- 이를 명시해주지 않을 경우, JPA가 명확히 구분하지 못함
- 상속 관계에서 조인 전략을 사용하겠다는 의미로
@DiscriminatorValue("CHAT")
public class ChatInAppNotification extends InAppNotification{
// ...
}
- 자식
@DiscriminatorValue를 통해 어떤 타입인지 명시(JPA에게 알려주는 것)
다대다 관계를 위한 연결 테이블 설정하는 법
상황 : 다대다 관계로 연결된 Agreement 테이블과 HelpCategory 테이블(Sync 테이블)을 연결하는 엔티티를 생성해야 함. 이때 이 연결 테이블은 Agreement의 PK와 HelpCategory_Sync의 PK만을 복합 기본키로 두고 있으며, 다른 필드는 존재하지 않음.
복합 기본키 설정
복합 키를 별도의 작은 클래스로 정의하고, 이 클래스를 @EmbeddedId를 사용해 주 엔티티에 삽입하여 관리한다. (→ JPA에서 복합 키를 다루는 가장 권장되는 방법)
1️⃣ 복합 키 클래스 정의 (@Embeddable)
@Embeddable
public class AgreementHelpCategoryId implements Serializable {
// Agreement 엔티티의 ID (FK)
private Long agreementId;
// HelpCategory 테이블의 ID (FK)
private Long helpCategoryId;
// 복합 키를 사용 시 equals()와 hashCode() 구현 필수
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AgreementHelpCategoryId that = (AgreementHelpCategoryId) o;
return Objects.equals(agreementId, that.agreementId) &&
Objects.equals(helpCategoryId, that.helpCategoryId);
}
@Override
public int hashCode() {
return Objects.hash(agreementId, helpCategoryId);
}
// 기본 생성자 (필수)
public AgreementHelpCategoryId() {}
}
2️⃣ 연결 엔티티 정의 (AgreementHelpCategory)
@Entity
public class AgreementHelpCategory {
// 1. 복합 키 객체를 기본 키로 사용 (@EmbeddedId)
@EmbeddedId
private AgreementHelpCategoryId id;
// 2. Agreement 엔티티와의 관계 설정 (일반적인 ManyToOne)
// @MapsId("agreementId")를 통해 복합 키의 agreementId 필드에 매핑
@ManyToOne(fetch = FetchType.LAZY)
@MapsId("agreementId")
@JoinColumn(name = "agreement_id")
private Agreement agreement;
// 3. HelpCategory ID 매핑 (엔티티가 없으므로 ID 필드만 선언)
// HelpCategory는 sync 테이블이라 엔티티가 없으므로, ID 자체를 컬럼으로 매핑
@Column(name = "help_category_id", insertable = false, updatable = false)
private Long helpCategoryId;
}
@MapsId: 일대일 관계이면서 참조하는 외래키가 기본키로서 역할을 할 때 사용
3️⃣ 주 엔티티에 @OneToMany 관계 설정
@Entity
public class Agreement {
@Id
@Tsid
private Long agreement_id;
// Agreement는 여러 AgreementHelpCategory를 가질 수 있게 됨!
@OneToMany(mappedBy = "agreement")
private List<AgreementHelpCategory> helpCategories = new ArrayList<>();
// ...
}
→ 이렇게 하는 이유 :
“다대다 관계를 해소하면서, 한쪽 엔티티(HelpCategory)는 우리 서비스의 도메인 객체가 아니므로, 외래 키 ID 값만 사용하여 관계를 맺겠다”는 의미이다.
'백엔드 > Spring Boot' 카테고리의 다른 글
| @Transactional(readOnly=true)를 사용하는 이유 (2) | 2025.12.24 |
|---|