JPA의 연관관계 예제 정리
실무에서 JPA를 사용하게 될 때, 가장 중요한 건 도메인의 설계이며 JPA의 연관관계를 바탕으로 어떻게 풀어나갈 것인가를 먼저 생각하게 됩니다. 각 테이블의 특성에 대한 연관관계를 생각한다면 맞지만 비즈니스의 요구사항에 따라 성능 문제를 해결하기 위한 설계를 해야 하며, 그에 따라 연관관계를 많이 고민해 보고 풀어나가야 한다고 생각합니다. 이전 글의 영속성 컨텍스트와 프록시에 이해를 바탕으로 이번 글에는 JPA가 객체와 RDBMS 사이를 어떤 형식으로 매핑하고 연관관계를 설정하는 건지 알아보는 시간을 갖도록 하겠습니다.
JPA의 연관관계
연관관계를 설정할떄는 다음과 같이 바라볼 수 있습니다.
- 연관 관계의 방향: 단방향, 양방향
- 연관 관계의 주인: 양방향에서의 관리 주체
- 연관 관계 : 일대일(1:1), 일대다(1:N), 다대일(N:1), 다대다(N:M)
연관관계의 방향
기존 데이터베이스에서는 외래키를 기준으로 테이블의 관계를 맺습니다. JPA에서는 연관 관계를 설정하여 엔티티 그래프로 관계에 대한 데이터를 가져옵니다. 이때 한 엔티티에만 연관관계를 명시해 준다면 단방향, 관계가 있는 두 엔티티에 명시를 해준다면 양방향이 됩니다.
게시글과 카테고리 엔티티가 있으며, 게시글은 하나의 카테고리를 가질 수 있다고 가정해 봅시다. 게시글에서는 카테고리가 필요하지만, 카테고리에는 게시글 정보가 있어야 할까요? 결론은 아닙니다. 무조건적인 관계의 설정은 할 필요가 없습니다. 서로의 관계에 대한 값이 필요할 때 양방향으로 설정해 주면 됩니다.
연관관계의 주인
연관관계의 주인이라는 의미는 관계를 맺은 두 객체의 저장, 수정, 삭제의 권한을 갖는 주체가 된다는 의미 입니다. 이때 주인이 아니면 조회만 가능합니다. 이를 구별하는 방법으로는 반대 객체에서는 MappedBy로 명시해 주고, 주인인 객체에는 외래키인 FK를 가지도록 설정해둔 것이 주인이 되는 것입니다.
일대다 (@OneToMany)
- 한 축구팀과 선수들의 관계에서 한 팀에는 여러 선수들이 존재 할 수 있습니다. 이러한 하나의 팀에 대한 여러 선수들의 관계를 일대다 관계라고 합니다.
- @OneToMany는 기본 타입이 Lazy로 지연로딩으로 실행됩니다.
- mappedBy를 지정하여 관계의 주인을 설정할 수 있습니다. 이떄 주인은 선수들이 되며 선수들을 팀에 추가하거나 삭제할 수 있습니다.
@OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
private List<player> players;
다대일 (@ManyToOne)
- 여러 선수들은 특정한 팀에 소속될 수 있습니다. 이때 여러 선수들에 대한 하나의 팀의 관계를 다대일 관계라고 합니다.
- @ManyToOne은 기본 타입이 Eager로 즉시 로딩이 실행됩니다.
- @JoinColumn으로 연결된 외래키의 컬럼명을 지정해 줄 수 있으며 외래키인 팀을 키값으로 가지게 됩니다.
@ManyToOne
@JoinColumn(name = "team_id", referencedColumnName = "id")
private Team team;
일대일 (@OneToOne)
- 한 선수는 한 개인 물품 보관함을 사용할 수 있습니다. 이떄 한 선수에 대한 한 물품 보관함의 관계를 일대일 관계라고 합니다.
- @OneToOne은 기본 타입이 Eager로 즉시 로딩이 실행됩니다.
- 주로 사용하는 테이블에 FK를 넣는다고 생각하시면 됩니다. 보통 querydsl를 사용하거나, 쿼리를 풀어 쓴다고 해도, 주 테이블에서 조회를 하지 하위 테이블에서는 조회할 일이 거의 없기 떄문입니다. 더 쉽게 생각한다면 일대일 관계를 일대다 라고 생각하고, 어떤 쪽이 주 테이블일까 라고 생각하시면 될 것 같습니다. 나아가서는 비즈니스의 요구사항이 바뀌었을 때도 좀 더 유연하게 대처할 수 있습니다.
//Locker.class
@OneToOne(mappedBy = "locker")
private Player player
//Player.class
@OneToOne(Joincolumn = "locker_id")
private Locker locker
다대다 (@ManyToMany)
- 여러 선수들은 여러 개의 경기장에서 뛸 수 있습니다. 이때 여러 선수에 대한 여러 경기장 기록들의 관계를 다대다 관계라고 합니다.
- @ManyToMany는 기본 타입이 Lazy로 지연로딩으로 실행됩니다.
- 다대다를 글에 검색해 보면 사용하지 말라고 나옵니다. 그럼 다대다가 없으면 어떻게 다양한 비즈니스의 요구사항을 바탕으로 도메인을 설계 할 수 있을까 라는 생각이 들게 됩니다. 다대다 대신에 이 연관관계를 풀어 일대다 다대일로 풀어서 다대다처럼 만들어 사용하면 됩니다.
//Player_Ground.class
@Id
private Long id;
@ManyToOne
@JoinColumn(name = "player_id", referencedColumnName = "id")
private Player player;
@ManyToOne
@JoinColumn(name = "ground_id", referencedColumnName = "id")
private Ground ground;
JPA를 사용할 때 올바른 연관관계 설정은 데이터베이스 설계와 비즈니스 요구사항을 충족하는 핵심 요소 중 하나입니다. 연관관계 설정의 방향, 주인, 종류를 고려하여 엔티티 간의 관계를 설계하면 효율적인 데이터베이스 관리 및 성능 향상을 기대할 수 있습니다. 하지만 관계를 맺으면서 생기는 JPA의 N+1 문제를 인지하지 못하고 사용한다면 매우 비효율적으로 사용 될 수 밖에 없게 됩니다. 다음 시간에는 JPA의 N+1 문제에 대해 알아보겠습니다. 읽어주셔서 감사합니다.
'Spring' 카테고리의 다른 글
[JPA] QueryDSL의 페이징 카테시안 곱 문제 해결 방법 (1:N Pagination) (0) | 2024.02.16 |
---|---|
[JPA] QueryDSL에서 JPA N+1 문제 해결하기 (0) | 2024.02.13 |
[JPA] 영속성 컨텍스트의 특징과 프록시 이해하기 (1) | 2024.02.01 |
[JPA] JPA 개념과 기본 설정 가이드 (0) | 2024.01.29 |
[Spring] SpringBoot JWT 로그인 구현 가이드 (1) | 2024.01.26 |