인프런 커뮤니티 질문&답변

kangsy763님의 프로필 이미지
kangsy763

작성한 질문수

자바 ORM 표준 JPA 프로그래밍 - 기본편

양방향 연관관계와 연관관계의 주인 2 - 주의점, 정리

양방향 관계 질문이 있습니다.

작성

·

515

3

안녕하세요 영한님, 프로젝트를 하다가 막히는 부분이 생겨서 해당 부분 복습하다 이 부분에도 궁금한점이 생겨 질문드립니다!

1. 

Team team = new Team();
team.setName("java");

Member member = new Member();
member.setUsername("kang");

member.setTeam(team);

em.persist(team);
em.persist(member);

다음과 같은 상황에서는 insert 문이 2번 나갑니다.

Team team = new Team();
team.setName("java");

Member member = new Member();
member.setUsername("kang");

member.setTeam(team);

em.persist(member);
em.persist(team);

위의 상황에서는 insert문 2번 후에 update 문이 나갑니다. 
이 이유는 member.setTeam(team); 을 했지만 jpa는 엔티티 저장시에 연관된 엔티티들이 모두 영속 상태여야 하니깐(team의 id가 없으니깐?) team_id를 null 로 두어서 em.persist(team) 후에 update 문이 호출된 것으로 보이는데 맞나요 ?? 

사실 이 질문을 드리는 이유는 제가 현재 진행하고 있는 프로젝트에서 어떻게 처리해야할지 의문이 드는 부분이 생겨서입니다.

2. 티켓판매 어플리케이션이고, Order 테이블이 있고 Ticket 테이블이 있습니다. 일대다 매핑을 해둔 상태입니다. 주문을 받으면 해당 티켓을 주문에 등록하는 느낌입니다. 
 그래서 Order 를 생성 시에 Ticket을 생성한 후에 Order와 매핑을 해주려고 했습니다. 그런데,  'Many'(Ticket) 쪽을 먼저 save 한 후에(영속성컨텍스트에 올린 후) 'One' (Order)에 집어넣고 order를 save 하면 1번 질문과 같이 update 문이 나갈 것이라는 것을 알게되었습니다. 이 로직은 OrderService -> OrderRepository 에서 일어나는 로직입니다. 설계가 잘못된건가요? Ticket 을 create 하는 부분에서 order를 생성을 먼저하는 것이 맞다고는 생각이 드는데, 실제 주문단계를 생각해보면 order 안에 ticket이 있는 것이라고 생각이 들어서 인지부조화가 오는 기분입니다... 어떻게 해야할까요?

+)  생각해보니깐 jpa 활용 1편 주문 관련 부분이랑 굉장히 유사한 것 같은데 해당부분을 다시 복습해보겠습니다...

답변 3

1

kangsy763님의 프로필 이미지
kangsy763
질문자

친절한 답변 너무나도 감사드립니다 !!!! 잘 해결해보겠습니다 감사합니다! ^^

1

김영한님의 프로필 이미지
김영한
지식공유자

안녕하세요. kangsy763님

먼저 1번 질문입니다.

결론부터 말씀드리면 연관관계(setXxx)를 설정하기 전에 먼저 연관관계 대상들을 영속 상태로 만드세요.

여기서는 연관관계의 대상이 되는 team을 먼저 영속 상태로 만들고, 그 다음에 member.setTeam(team)을 설정하시는 것이 좋습니다.

단순하게 접근해서 먼저 필요한 연관관계 엔티티들을 먼저 영속상태로 만든 다음에 영속상태가 되면, 연관관계를 맺어주면 됩니다.

다음 코드를 보시면 알겠지만 연관관계를 먼저 맺은 다음에, 영속상태로 바꾸면, 내부에서는 객체가 변경된 것으로 인식할 수 있습니다.

Team team = new Team();
team.setName("java");

Member member = new Member();
member.setUsername("kang");

member.setTeam(team);

em.persist(member); //member를 persist 하는 시점에 member[id=1], team[id=null, name="java"]
em.persist(team); //team을 persist 하는 시점에 member[id=1], team[id=2, name="java"]
//결과적으로 member가 사용해야 하는 team의 id가 null -> 2로 변경됨, 따라서 update 추가 발생
em.flush();

2번 질문입니다.

고민하시는 부분이 뭔지 이해가 되네요. 코드를 볼 때 order가 ticket를 가지는 그림으로 가고 싶으신거지요? 주문을 할 때 그 안에 티켓을 담는 그림이 더 잘 이해가 되니까요.

그런데 연관관계의 주인은 비즈니스 로직의 흐름이 아니라 외래키(FK)가 있는 곳을 기준으로 가져가야 합니다.

지금은 ticket.setOrder() 이 부분이 연관관계의 주인이 되는 것이지요.

자~ 여기에서 인지 부조화를 해결할 수 있는 좋은 대안이 있습니다.

바로 연관관계 편의 메서드를 잘 활용하는 방법입니다.

그러면 order.addTicket(ticket) 라는 로직을 활용해서 인지부조화도 해결하고, 고민하시는 문제도 해결할 수 있습니다.

예제 코드를 보여드릴께요.

@Entity
@Getter @Setter
public class Ticket {

@Id
@GeneratedValue
private Long id;
private String name;

@ManyToOne
@JoinColumn(name = "order_id")
private Order order;

}

@Entity
@Getter @Setter
@Table(name = "orders")
public class Order {

@Id
@GeneratedValue
private Long id;
private String name;

@OneToMany(mappedBy = "order")
private List<Ticket> tickets = new ArrayList<>();

public void addTicket(Ticket ticket) {
this.tickets.add(ticket);
ticket.setOrder(this);
}

}

이렇게 되어 있을 때, 아마 다음 방법으로 아마 하셨을거에요.

방법1 - 연관관계의 주인 바로 사용

@Test
void orderTicket() {
//Order 생성로직
Order order = new Order();
em.persist(order);

Ticket ticket = new Ticket();
ticket.setOrder(order); //연관관계 주인

em.persist(ticket);

}

방법2 - order 중심의 연관관계 편의 메서드 사용

@Test
void orderTicket2() {
//Order 생성로직
Order order = new Order();
em.persist(order);

Ticket ticket = new Ticket();
order.addTicket(ticket); //연관관계 편의 메서드

em.persist(ticket);
em.flush();
}

이렇게 방법2 처럼 사용하시면 쿼리 문제도 해결하고, 인지 부조화도 해결할 수 있습니다.

추가로 이 두 예제에서는 update 쿼리가 나가지 않습니다. 그런데, 애플리케이션 로직이 정말 복잡해지면, 깔끔한 애플리케이션 로직을 얻기 위해서 update 쿼리 한 두게 정도는 허용하는게 더 나을 수도 있습니다.

도움이 되셨길 바래요^^

0

김영한님의 프로필 이미지
김영한
지식공유자

kangsy763님 화이팅^^!

kangsy763님의 프로필 이미지
kangsy763

작성한 질문수

질문하기