JPA는 DB와 달리, 양쪽 엔티티가 서로 다대다 관계일 때 @ManyToMany를 통해 쉽게 매핑할 수 있게 해준다. (DB와의 불일치 해결)
예를 들어, 메시지와 첨부파일(BinaryContent) 간의 관계는 @ManyToMany와 @JoinTable을 이용해 다음과 같이 선언할 수 있다.
(@JoinTable을 사용하지 않는다면, JPA가 연결 테이블을 생성해줌)
@Entity
public class Message {
@ManyToMany
@JoinTable(
name = "message_attachments",
joinColumns = @JoinColumn(name = "message_id"),
inverseJoinColumns = @JoinColumn(name = "attachment_id")
)
private List<BinaryContent> attachments;
}
하지만 이 방식은 실무에서 거의 쓰지 않음!
대신 실무에서는 @ManyToMany 대신 연결 엔티티를 명시적으로 생성하여 관계를 표현한다.
Message ↔ MessageAttachment ↔ BinaryContent
@Entity
public class MessageAttachment {
@EmbeddedId
private MessageAttachmentId id;
@ManyToOne
@MapsId("messageId") // 복합키의 messageId와 message.id(PK) 연결
@JoinColumn(name = "message_id")
private Message message;
@ManyToOne
@MapsId("attachmentId") // 복합키의 attachmentId와 binarycontent.id(PK) 연결
@JoinColumn(name = "attachment_id")
private BinaryContent binaryContent;
public MessageAttachment(Message message, BinaryContent binaryContent) {
this.message = message;
this.binaryContent = binaryContent;
this.id = new MessageAttachmentId(message.getId(), binaryContent.getId());
}
protected MessageAttachment() {}
}
public class Message {
@OneToMany(mappedBy = "message", cascade = CascadeType.ALL, orphanRemoval = true)
private List<MessageAttachment> attachments;
}
@Embeddable
public class MessageAttachmentId implements Serializable {
private UUID messageId;
private UUID attachmentId;
// equals(), hashCode() 꼭 오버라이드
}
// Message에서 BinaryContent ID 추출
List<UUID> binaryContentIds = message.getAttachments().stream()
.map(att -> att.getBinaryContent().getId())
.toList();
public List<BinaryContent> getBinaryContents() {
return attachments.stream()
.map(MessageAttachment::getBinaryContent)
.toList();
}

@OneToMany
@JoinTable(
name = "message_attachments",
joinColumns = @JoinColumn(name = "message_id"), // 현재 테이블과 연결되는 JoinTable의 FK
inverseJoinColumns = @JoinColumn(name = "attachment_id") // 상대 테이블과 연결되는 JoinTable의 FK
)
private List<BinaryContent> attachments;