본문 바로가기

개발/쉽게 이해하고 사용하는 JPA

JPA 관계와 그 사용법에 대해 (양방향)

데이타베이스에서 테이블간의 관계를 구성하는 방법은 OneToOne, OneToMany, ManyToOne, ManyToMany 가 있습니다. 테이블간의 관계에서는 단방향과 양방향에 대한 구분이 없지만 JPA 상에서는 사용하는 Entity 에 따른 차이가 존재합니다. 단방향은 FK (Foreign Key) 를 소유한 Entity 에서 대상 Entity 를 참조하는 방식이며 양방향은 FK 를 소유한 Entity 와 대상 Entity 에서 서로가 서로를 참조할 수 있는 방식입니다.

양방향 관계를 지정할때 유의해야 할 점은 서로의 Entity 데이타에 대해 동일한 사용 권한을 가지기 때문에 의도치 않은 데이타의 오염이 일어 날 수 있다는 점 입니다. Entity 의 값이 변경 되었는데 이게 누구에 의한 데이타의 변경인지 파악하기 어려워 진다는 겁니다. 그래서 프로그램을 개발할때 데이타의 변경에는 최대한 폐쇄적으로 접근하는게 좋기 때문에 가급적이면 양방향의 관계는 사용하지 않는 것이 좋습니다.

 

@OneToOne

앞서 단방향 @OneToOne 관계를 설명하면서 FK 를 소유한 자식 Entity 가 부모 Entity 를 참조하는 형태에 대해서 살펴 보았습니다. 양방향 @OneToOne 은 이와 크게 다르지 않으며 부모 Entity 에서 자식 Entity 에 대한 mappedBy 설정만 추가하면 됩니다. 이때 mappedBy 설정에는 자식 Entity 에서 바라보는 부모 Entity 의 변수이름을 지정합니다.

@Entity(name = "parent")
public class Parent {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @OneToOne(mappedBy = "parent") // Child Entity 에서 변수로 사용하고 있는 parent 이름
    private Child child;
}

@Entity(name = "child")
public class Child {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @OneToOne
    @JoinColumn(name = "parent_id")
    private Parent parent;
}

 

@OneToMany, @ManyToOne

@OneToMany, @ManyToOne 은 사용되는 Entity 가 누구냐에 따라 달라지는 것이지 원래는 데이타베이스 상에서 같은 관계를 의미합니다. 부모 Entity 에서는 @OneToMany, 자식 Entity 에서는 @ManyToOne 이 되는 것 입니다. 그렇기 때문에 양방향으로 @OneToMany 를 사용하면 자식 Entity 에는 @ManyToOne 이 같이 사용 됩니다.

앞서 단방향 @OneToMany 일 경우 관계 중 유일하게 FK 가 위치한 자식 Entity 가 아닌 부모 Entity 에 어노테이션이 정의된다고 이야기 드렸습니다. 양방향 @OneToMany 일때는 부모 Entity 에 @JoinColumn 어노테이션이 제거되고 @OneToManymappedBy 속성을 추가해 자식 Entity 와의 관계를 설정합니다. 이때 mappedBy 속성에는 자식 Entity 에서 부모 Entity를 바라보는 변수이름을 지정합니다. 자식 Entity 에서는 단방향 @ManyToOne 와 동일하게 부모와의 관계를 지정해 줍니다.

@Entity(name = "parent")
public class Parent {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @OneToMany(mappedBy = "parent")
    private List<Child> childList;
}

@Entity(name = "child")
public class Child {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "parent_id")
    private Parent parent;
}

 

@ManyToMany

단방향 @ManyToMany 는 앞서 물리적으로는 존재할 수 없는 논리적인 관계이며 이를 위해 중간에 서로의 PK 정보를 담아두는 맵핑 테이블을 사용해 @ManyToMany 를 표현한다고 이야기 드렸습니다. 양방향 @ManyToMany 는 이와 크게 다르지 않지만 문제는 서로의 Entity 가 동등한 위치를 가지고 있기 때문에 관계 설정 정보를 어디에 지정할 것 인가를 결정해야 한다는 점 입니다. 관계 설정 정보를 둘중 어디에 둘 것인지를 결정 했다면 반대쪽 Entity 에 @ManyToMany 어노테이션을 정의하고 mappedBy 속성에 정의된 변수이름을 지정합니다.

@Entity(name = "parent")
public class Parent {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToMany
    @JoinTable(
        name = "parent_child",
        joinColumns = @JoinColumn(name = "parent_id"),
        inverseJoinColumns = @JoinColumn(name = "child_id")
    )
    private List<Child> childList;
}

@Entity(name = "child")
public class Child {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @ManyToMany(mappedBy = "childList") // 반대쪽에 정의된 변수이름으로 관계를 지정
    private Parent parent;
}

 

관계를 설정하기에 앞서

JPA 에서 Entity 간의 관계를 지정할때 주의해야 될 점은 Entity 객체의 모든 관계에 대해서 설정을 하겠다고 생각을 하지 않아야 한다는 점 입니다. 물론 논리적으로 완벽하게 데이타베이스상의 모든 관계를 정의하고 싶다는 욕심이 들더라도 이것은 의도치 않은 데이타 오용을 막고 불필요한 부하가 증가되어 시스템의 전반적인 속도가 느려지는 현상을 막을 수 있습니다.

관계를 설정하기전 반드시 이 관계가 시스템상에서 꼭 논리적으로 결합되어야 하는지에 대해 생각해봐야 합니다. 구분하기 위한 적절한 기준은 이 Entity 객체가 혼자 독립적으로 데이타 조작이 되는지 여부 입니다. 만약 단일 Entity 가 다른 Entity 와 연관이 없이 CRUD 가 모두 동작이 된다면 이는 독립적인 Entity 일 확률이 아주 높습니다. 만약 이에 대해 정확한 답을 내릴 수 없다면 두 Entity 객체 사이에 관계를 설정하지 않고 데이타의 Key 를 이용해 값을 참조 하는 방법을 사용해야 하는 것이 좋습니다.

 

다음글에서는 Repository 를 이용한 값의 조회에 대해서 알아 보겠습니다.