JPA를 사용하면서 두 엔티티간 일다대 관계를 설정하는 경우가 많았다. 어쩔때는 중간 테이블이 생기고 어쩔때는 데이터베이스에 데이터가 저장이 안되고, 그럴때마다 그냥 mappedBy 추가하고 joinColumn 추가하고 넘어갔다. 특히 두 엔티티 관계를 설정할 때 연관관계의 주인이라는 개념을 잘 이해하지 못했다. 아직 제대로 개념을 익히지는 못했지만 두 엔티티 관계 설정을 해보며 어떤 변화가 생기는지 테스트해봤다.
기본 코드
아래의 Parent, Child 두 엔티티에 일대다 관계를 설정할 것이다. 각 엔티티에 @ManyToOne
이나 @OneToMany
를 추가해보며 테스트 코드를 실행했다.
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
@Entity
public class Child {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
테스트 코드는 아래처럼 Parent와 Child를 생성 후 서로 참조하도록 코드를 작성했다.
Parent parent = new Parent();
em.persist(parent);
em.flush();
Child child1 = new Child();
Child child2 = new Child();
// 경우에 따라 A, B 둘 중 한 코드만 추가했다.
// (A)
parent.getChildren().add(child1);
parent.getChildren().add(child2);
// (B)
child1.setParent(parent);
child2.setParent(parent);
em.persist(child1);
em.persist(child2);
em.flush();
연관 관계의 주인
데이터베이스에서는 테이블의 외래키로 두 테이블의 연관 관계를 관리한다. 하지만 객체에서는 참조를 통해 객체의 관계를 만들 수 있는데 이 때 두 객체가 서로 참조하는 양방향 관계를 만들 수도 있을 것이다. 여기서 데이터베이스와 객체의 패러다임 불일치가 발생한다.
두 객체가 서로 참조하는 상황에서 외래키를 관리하는 객체를 연관관계 주인이라고 한다.
- @OneToMany, @ManyToOne 관계에서는 한쪽이 연관 관계의 주인이 된다.
- 주인은 관계를 관리하고 데이터베이스의 외래 키를 실제로 업데이트하는 역할
- JPA에서는 이 주인을 명시적으로 지정해야 하며 주인은 외래 키가 실제로 위치한 테이블을 관리하는 엔티티
연관관계의 주인으로 설정한 엔티티의 객체에 참조를 추가해야 데이터베이스에서 외래키 설정이 되는 것이다.
// Parent에 Child 참조를 추가한다.
parent.getChildren().add(child);
// Child에 Parent 참조를 추가한다.
child.setParent(parent);
Parent, Child에서 각각을 주인으로 설정한 다음 테스트해봤다.
Parent를 주인으로 설정
공부할 때 외래키를 가진 테이블과 매핑되는 엔티티만 연관 관계의 주인이 된다고 알고있었는데, 외래키를 가지지 않은 엔티티도 주인으로 설정할 수 있다.
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany
@JoinColumn(name = "parent_id")
private List<Child> children = new ArrayList<>();
}
@Entity
public class Child {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
테스트 실행
Parent parent = new Parent();
em.persist(parent);
em.flush();
Child child1 = new Child();
Child child2 = new Child();
// Parent에 Child를 추가했다.
parent.getChildren().add(child1);
parent.getChildren().add(child2);
em.persist(child1);
em.persist(child2);
em.flush();
// 아래의 SQL이 실행된다.
insert into parent (id) values (default);
insert into child (id) values (default);
insert into child (id) values (default);
update child set parent_id=1 where id=1;
update child set parent_id=1 where id=2;
parent에만 child 관계를 설정했지만 UPDATE
로 child의 parent_id를 지정한다.
Child를 주인으로 설정
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "parent")
private List<Child> children = new ArrayList<>();
}
@Entity
public class Child {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@Setter
private Parent parent;
}
Parent parent = new Parent();
em.persist(parent);
em.flush();
Child child1 = new Child();
Child child2 = new Child();
// 각 Child에 Parent를 설정했다.
child1.setParent(parent);
child2.setParent(parent);
em.persist(child1);
em.persist(child2);
em.flush();
// 아래의 SQL이 실행된다.
insert into parent (id) values (default);
insert into child (parent_id,id) values (1,default);
insert into child (parent_id,id) values (1,default);
결론
- 데이터베이스는 일대다 관계를 외래키로 관리하기 때문에 두 객체간 관계 설정을 위해서는 외래키를 설정해야한다.
- 연관관계의 주인으로 설정한 엔티티에 다른 엔티티의 참조를 추가하고 객체를 할당해야 데이터베이스에서 외래키를 설정한다.
[번외] 주인 지정이 없다면?
@Entity
public class Parent {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany
private List<Child> children = new ArrayList<>();
}
@Entity
public class Child {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Parent parent;
}
중간에 두 엔티티를 매핑하는 테이블이 생성된다.
create table parent_children (
parent_id bigint not null,
children_id bigint not null
);
parent.getChildren().add(child1);
parent.getChildren().add(child2);
// child1.setParent(parent);
// child2.setParent(parent);
insert into parent (id) values (default);
insert into child (parent_id,id) values (1,default);
insert into child (parent_id,id) values (1,default);
insert into parent_children (parent_id,children_id) values (1,1);
insert into parent_children (parent_id,children_id) values (1,2);
아직 명확하게 알지 못하는 부분도 많고 포스팅의 문장도 매끄럽지 못한 것 같다.
- 일대다 관계 설정에서
@JoinColumn
은 언제 생략할 수 있는가? - 연관관계 주인 설정은 어떤 기준으로 결정해야 하는가?
그래도 언제 매핑 테이블이 생기는지, 연관 관계 주인에 참조를 추가해야 외래키가 업데이트된다 등을 알 수 있었다. 모두 이해하고 왜 이런지 설명할 수 있도록 JPA에 대해 더 공부해야겠다.