1. 문제
JPA에서 연관 편의 메소드는 양방향 연관을 즉시 만들기 위해 작성된 메소드입니다. 보지마.
이때, 엔티티 A(다수)와 B(하나)가 양방향 관계를 갖는다면 연관 편의 메소드는 총 3가지 경우로 작성할 수 있다.
- (1) A를 등록하고 B를 관리합니다.
- (2) B에 쓰고 A에게 투여한다.
- (3) A와 B를 각각 씁니다.
방법 (3)은 여기에서 피해야 할 패턴입니다. 그러나 두 위치에서 메서드 호출을 생략하면 논리 문제가 발생하고 코드 유지 관리가 복잡해집니다.
그렇다면 (1)과 (2)의 경우 연관 편의 메서드를 정의하는 가장 좋은 방법은 어디일까요?
이 기사에서 나는 이 질문에 대한 나의 생각을 포착하려고 노력할 것이다.
2. 간단한 양방향 ManyToOne 관계
아래와 같이 두 개의 엔터티가 있다고 가정해 보겠습니다.
(핀.자바)
@Entity
@Table(name = "pin")
public class Pin extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;
@OneToMany(mappedBy = "pin", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<PinTag> pinTags = new ArrayList<>();
...
}
(이미지.자바)
@Entity
@Table(name = "picture")
public class Picture extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Pin pin;
}
연결 편의 메서드가 핀 엔터티에 있어야 합니까? 그림 엔터티에 있어야 합니까?
사실, 그것들을 어디에 두느냐는 중요하지 않습니다. 그러나 저는 관례로 핀 엔티티를 고수하는 경향이 있습니다.
그 이유는 이는 비즈니스 로직 관점에서 Pin 엔터티는 이미지를 사용하고 Picture 엔터티는 핀을 사용하지 않기 때문입니다.보지마. 실제로 코드를 작성할 때 핀 관련 코드에 이미지를 저장/업데이트/삭제하고, 이미지 쪽이 핀에 거의 닿지 않기 때문입니다.
게다가, 캐스케이드 옵션을 통해 자녀의 라이프 사이클을 관리하는 부모의 측면을 고려할 때 핀의 관점에서 자신의 이미지 목록을 관리하는 것은 의도를 명확하게 할 수 있습니다.
따라서 아래와 같이 Pin 엔터티에 대한 편리한 연결 방법을 작성합니다.
@Entity
@Table(name = "pin")
public class Pin extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;
@OneToMany(mappedBy = "pin", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<PinTag> pinTags = new ArrayList<>();
####### 연관관계 편의 메서드 #######
public void addPicture(Picture picture) {
if(!getPictures().contains(picture)){
getPictures().add(picture);
}
picture.setPin(this);
}
public void removePicture(Picture picture) {
this.getPictures().remove(picture);
picture.setPin(null);
}
}
3. ManyToMany 관계는 어떻습니까?
ManyToMany 관계를 해결하려면 연결 엔터티를 사용하여 두 엔터티를 함께 연결합니다.
아래의 엔터티 관계는 사용자가 여러 서클에 가입할 수 있고 서클에 여러 사용자가 있을 수 있다는 요구 사항에서 발생합니다.
그리고 user-UserCircle과 circle-UserCircle에 각각 양방향 매핑을 적용했습니다.
(사용자.자바)
@Entity
@Table(name = "users")
public class Users extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<UserCircle> userCircleList = new ArrayList<>();
...
}
(서클. 자바)
@Entity
@Table(name = "circle")
public class Circle extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;
@OneToMany(mappedBy = "circle", cascade = CascadeType.ALL, orphanRemoval = true)
@Builder.Default
private List<UserCircle> userCircleList = new ArrayList<>();
...
}
(UserCircle.java)
@Entity
@Table(name = "user_circle")
public class UserCircle extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Users user;
@ManyToOne(fetch = FetchType.LAZY)
private Circle circle;
...
}
자, 이런 상황에서 사용자와 서클 엔터티에 대한 편리한 매핑 방법을 작성하는 것이 좋을까요?
내 추측은 아니오입니다. 이 경우 UserCircle 측에 연관 편의 메소드를 작성하는 것이 더 좋다고 생각합니다.하다.
다음 테스트 코드를 통해 그 이유를 살펴보겠습니다. 매핑 방법(addUserCircle)이 사용자 및 원에 있다고 가정합니다.

이 테스트 코드는 실패합니다. 이는 Circle에 작성된 Association Convenience 메소드가 호출되었지만 사용자 측에서 생성된 Association Convenience 메소드가 호출되지 않았기 때문입니다.
이 테스트 코드가 성공하려면 u1.addUserCircle(uc1); 호출된 메서드를 추가로 호출했어야 합니다.
이 상황은 소개에서 논의되었습니다 양측에서 연관편의법을 이용하여 상황을 회피하는 이유(3)와 유사해진다.
따라서 ManyToMany 관계를 해결하기 위해 연결 엔터티의 세 엔터티를 관리하는 간단한 연결 메서드를 만드는 것이 좋습니다.
@Entity
@Table(name = "user_circle")
public class UserCircle extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Id
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
private Users user;
@ManyToOne(fetch = FetchType.LAZY)
private Circle circle;
####### 연관관계 편의 메서드 #######
public void setUserAndCircle(Users user, Circle circle) {
addUserCircleForUser(user);
addUserCircleForCircle(circle);
setUser(user);
setCircle(circle);
}
private void addUserCircleForUser(Users user) {
if(!user.getUserCircleList().contains(this)){
user.getUserCircleList().add(this);
}
}
private void setUser(Users user) {
this.user = user;
}
private void addUserCircleForCircle(Circle circle) {
if(!circle.getUserCircleList().contains(this)){
circle.getUserCircleList().add(this);
}
}
private void setCircle(Circle circle) {
this.circle = circle;
}
}
그리고 테스트 코드를 아래와 같이 작성하면, 테스트는 라인 매핑 편의 메서드 호출로 성공합니다.하다. 또한 User 및 Circle 매개변수의 필수 설정은 컴파일 타임에 잘못된 논리를 사용하는 것을 방지합니다.당신은 그것을 만들 것입니다

4. 구성
지금까지 말한 내용을 요약하면 크게 두 가지입니다.
- ManyToOne 관계의 두 엔터티가 양방향 연결인 경우 부모 측에 편리한 연결 방법을 작성합니다.
- 중간 연결 엔터티와 양방향 연결을 수행할 때 ManyToMany 관계를 해결하려면 편리한 연결 메서드를 작성합니다.
실용적인 연상법을 쓰는 방식은 사람마다 다른 것 같고, 제가 생각한 방식이 정답은 아니니 참고만 하시면 좋을 것 같습니다.
