이번에 진행하는 프로젝트에서 다대다 관계를 정의하게 되었다. (그것도 두개나)
우선 간단히 설명하자면, 스프링부트에선 ManyToMany 어노테이션을 통해 간단히 다대다 관계를 정의할 수 있지만, 이는 실무에서는 절대 사용하면 안된다.
우선 ManyToMany을 사용하면 Hibernate에서 자동으로 매핑엔티티를 만들어주지만, 개발자가 이를 수정할 수 없어 매핑엔티티에 추가 필드가 요구되는 경우가 많은 실무에 부적합하다.
또 요구사항이 변동되면 수정하기 힘들며 애초에 중간엔티티가 숨겨져있기 때문에 여러모로 제약이 많다.
따라서 ManyToMany 대신 개발자가 직접 매핑엔티티를 만들어 OneToMany와 ManyToOne으로 연결해주는 것이 바람직하다.

public class MemberRecord {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "mr_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "m_id")
private Member member;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "r_id")
private Record record;
@Description("MemberRecord 매핑 삭제")
@Column(name = "is_deleted")
@Builder.Default
private Boolean deleted = false;
@Enumerated(EnumType.STRING)
private Role role;
public void delete(){
this.deleted=true;
}
}
Member와 Record 사이에 MemberRecord 엔티티를 두어 각각 ManyToOne으로 매핑하면 된다.
코드에서 볼 수 있듯이 중간엔티티에서 필요한 필드 (Role)을 추가할 수 있었다.
물론, 이렇게 하면 로직이 매우 복잡해진다. 서비스로직을 보자.
@Override
public Record save(RecordDTO recordDTO){
// 멤버 리스트 & 태그 리스트 받아와서 Record 저장
List<String> tags = recordDTO.tagList();
List<Long> members = recordDTO.memberList();
Long projectId = recordDTO.projectId();
Long recordWriterId=recordDTO.recordWriterId();
Project project = projectRepository.findById(projectId)
.orElseThrow(() -> new NotFoundException("해당 프로젝트를 찾을 수 없습니다. ID: " + projectId));
Record record=recordDTO.toEntity(project);
Record savedRecord = recordRepository.save(record);
// Tag 저장
for (String tagName : tags) {
tagService.save(tagName);
}
memberRecordService.save(members,savedRecord,recordWriterId);
recordTagService.save(tags,savedRecord);
return savedRecord;
}
일일히 매핑엔티티를 저장해줘야 하기 때문에 상당히 복잡하다.
또 저장 순서도 중요하다.
MemberRecord와 RecordTag는 모두 Record를 참조하기 때문에 Record가 먼저 저장되고 매핑엔티티가 저장돼야한다. 마찬가지로 RecordTag가 Tag를 참조하고 있으므로 Tag가 먼저 생성->저장된 후 매핑엔티티가 저장돼야한다.
그렇다면 참조한 객체를 가져올 땐 어떻게 하느냐 !
tagService.delete(record.getRecordTagList());
이런식으로 직접 record.getTagList()로 바로 가져오는 것이 아닌 매핑엔티티를 가져오는거다.
@Override
public void delete(List<RecordTag> recordTagList){
//Tag Count 감소, 0이라면 삭제
//deleted 여부도 검사해야될듯
for (RecordTag recordTag : recordTagList) {
Tag tag= recordTag.getTag();
tag.decrementCount();
if (tag.getCount() <= 0) {
tag.delete();
}
tagRepository.save(tag);
}
}
그리고 매핑엔티티에서 매핑된 객체를 추출해내는 단계를 거쳐야한다.