공부/JPA

다양한 연관관계 매핑

Stair 2024. 12. 3. 13:18
반응형

연관관계 매핑시 고려사항 3가지

- 다중성

    - 다대일 : @ManyToOne

    - 일대다 : @OneToMany

    - 일대일 : @OneToOne

    - 다대다 : @ManyToMany

- 단방향, 양방향

    - 테이블(단방향) : 외래 키 하나로 양쪽 조인이 가능하다 즉 방향이라는 개념 자체가 없다.

    - 객체(양방향) : 객체는 참조용 필드가 있는 쪽으로만 참조가 가능하다. 한쪽만 참조하면 단방향이고, 양쪽이 참조하면                                 양방향이다.

- 연관관계의 주인

    - 테이블은 외래 키 하나로 두 테이블이 연관관계를 맺는다.

    - 객체 양방향 관계는 A->B, B->A처럼 참조가 2군데이다.

    - 객체 양방향 관계는 참조가 2군데 있음, 둘중 테이블의 외래키를 관리할 곳을 지정해야 한다.

    - 연관관계의 주인 : 외래키를 관리하는 참조

    - 주인의 반대편, 외래 키에 영향을 주지 않음, 단순 조회만 가능하다.

 

 

 

다중성

1. 다대일(@ManyToOne)

 

테이블 기준으로 TEAM_ID는 FK이기 때문에 TEAM값이 들어와있다.

그래서 Member객체 기준으로는  TEAM_ID에 연관관계를 매핑해주면 된다

Member의 team이나 MEMBER의 TEAM_ID이나 둘 다 Team을 찾아가기 위해 만들어져있는 것들이기 때문이다.

 

다대일 단방향 그림 예시는 위와 같다. DB 입장에서 보면 TEAM은 1이고 MEMBER는 N이기 때문에 외래키는 무조건 MEMBER쪽으로 가야한다.(다 쪽엔 항상 외래키가 들어가야한다.)

@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;

 

양방향은 반대쪽에서 참조하기만 하면 된다.

@OneToMany(mappedBy = "team") //Member엔티티에 team변수명에 걸려 있다.
private List<Member> members = new ArrayList<>();

 

 

 

2. 일대다(@OneToMany)

여기에서는 1이 연관관계의 주인이다. 이 모델은 보통 권장되지 않는다.

Member입장에서는 Team을 모르나 Team입장에선 Member 리스트를 갖는다.

하지만 DB입장에서는 무조건 다(MEMBER)쪽에 외래키가 있어야 한다.(팀이 1이고 멤버가 다이기 때문)

@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;

    public Member() {

    }
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

}
@Entity
public class Team {

    @Id
    @GeneratedValue
    @Column(name = "TEAM_ID")
    private Long id;
    private String name;

    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<Member> members = new ArrayList<>();

    public List<Member> getMembers() {
        return members;
    }

    public void setMembers(List<Member> members) {
        this.members = members;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}
try {
    Member member = new Member();
    member.setUsername("member1");
    em.persist(member);

    Team team = new Team();
    team.setName("teamA");
    team.getMembers().add(member);
    em.persist(team);
    
    tx.commit();

 

위의 코드를 돌려보면 아래와 같은 결과가 나온다.

select * from member;

MEMBER_ID  TEAM_ID  USERNAME  

1 1 member1


select * from team;

TEAM_ID  NAME  

1 teamA

 

Hibernate: 
    /* insert for
        hellojpa.Member */insert 
    into
        Member (USERNAME, MEMBER_ID) 
    values
        (?, ?)
Hibernate: 
    /* insert for
        hellojpa.Team */insert 
    into
        Team (name, TEAM_ID) 
    values
        (?, ?)
Hibernate: 
    update
        Member 
    set
        TEAM_ID=? 
    where
        MEMBER_ID=?

 

team의 입장에서는 Member의 TEAM_ID(FK)를 수정할 수 있는 방법이 없다. 따라서 어쩔 수 없이 update쿼리를 한번 더 쳐야한다 -- 성능 상 단점이 존재한다.

그리고 실무에서는 테이블이 수십개가 엮여서 돌아가는 상황인데, @OneToMany를 사용하게 되면 운영이 복잡해진다.

.

일대다 단방향 정리

- 일대다 단방향은 일대다(1:N)에서 일(1)이 연관관계의 주인이다.

- 테이블 일대다 관계는 항상 다(N)쪽에 외래 키가 있다.

- 객체와 테이블의 차이 때문에 반대편 테이블의 외래키를 관리하는 특이한 구조이다.

- @JoinColumn을 꼭 사용해야 한다. 그렇지 않으면 조인 테이블 방식을 사용한다.(중간에 테이블을 하나 추가한다.)

- 일대다 단방향 매핑의 단점

    - 엔티티가 관리하는 외래 키가 다른 테이블에 있다.

    - 연관관계 관리를 위해 추가로 UPDATE SQL을 실행한다.

- 일대다 단방향 매핑보다는 다대일 양방향 매핑을 사용하자.

 

 

 

3. 일대일(@OneToOne)

- 일대일 관계는 그 반대도 일대일이다.

- 주 테이블이나 대상 테이블 중 외래키를 선택할 수 있다.

    - 주 테이블에 외래 키

    - 대상 테이블에 외래 키

- 외래 키에 데이터베이스 유니크(UNI) 제약조건 추가

** 유니크 값 : 테이블 열에 중복된 값을 허용하지 않는다. **

 

회원이 주 테이블이라고 가정한 그림이다. 멤버는 락커를 한개만 가져야 하고, 마찬가지로 락커도 멤버를 한개만 가져야 한다. 위를 토대로 코드를 작성해보면 다음과 같다.

@Entity
public class Locker {

    @Id @GeneratedValue
    private Long id;

    private String name;

    @OneToOne(mappedBy = "locker")
    private Member member;

}
@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

    @Column(name = "USERNAME")
    private String username;
    
    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;

 

다대일 양방향 매핑처럼 외래 키가 있는 곳이 연관관계의 주인이다.

다대일 양방향과 마찬가지로 반대편은 mappedBy를 적용한다.

 

참고 : 대상 테이블에 외래 키 단방향 관계는 jpa에서 지원하지 않는다.

 

주 테이블에 외래 키

    - 주 객체가 대상 객체의 참조를 가지는 것 처럼, 주 테이블에 외래 키를 두고 대상 테이블을 찾는다.

    - 객체지향 개발자들이 선호한다

    - jpa 매핑이 편리하다

    - 장점 : 주 테이블만 조회해도 대상 테이블에 데이터가 있는지 확인 가능하다.

    - 단점 : 값이 없으면 외래 키에 null을 허용해야한다.

대상 테이블에 외래 키

    - 대상 테이블에 외래키가 존재하는 경우이다.

    - 전통적인 데이터베이스 개발자가 선호한다.

    - 장점 : 주 테이블과 대상 테이블을 일대일에서 일대다 관계로 변경할 때 테이블 구조를 유지한다.

    - 단점 : 프록시 기능의 한계로 지연 로딩으로 설정해도 항상 즉시 로딩된다.

 

 

 

4. 다대다(@ManyToMany)

다대다는 실무에서 쓰면 안된다. 그렇기에 가볍게 보도록 하자.

- 관계형 데이터베이스는 정규화된 테이블 2개로 다대다 관계를 표현할 수 었다.

- 연결 테이블을 추가해서 일대다, 다대일 관계로 풀어내야 한다.

 

다대다는 @ManyToMany를 사용하고, @JoinTable로 연결 테이블을 지정할 수 있다.

객체는 컬렉션을 사용해서 객체 2개로 다대다 관계가 가능하다.

@Entity
public class Product {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @ManyToMany(mappedBy = "products")
    private List<Member> members = new ArrayList<>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
@Entity
public class Member {

    ...

    @ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT")
    private List<Product> products = new ArrayList<>();

 

다대다 매핑의 한계

- 편리해 보이지만 실무에서 사용할 수 없다.

- 연결 테이블이 단순히 연결만 하고 끝나지 않는다.

- 주문시간, 수량 같은 데이터가 들어올 수 있다.

 

이런 다대다 매핑의 한계를 극복하기 위해 연결 테이블용 엔티티를 추가한다.(연결 테이블을 엔티티로 승격시킨다)

- @ManyToMany -> @OneToMany, @ManyToOne

 

참고 : PK는 의미없는 값인 USER_ID를 쓰거나 @GeneratedValue를 쓰거나 해서 PK를 한개만 잡고 가면 JPA 맵핑도 심플해지고, 개발할때 유연하고 쉬워진다. 필요할때마다 제약조건을 추가하자.

 

 

 

실전 예제 - 다양한 연관관계 매핑

- 주문과 배송은 1:1(@OneToOne)

- 상품과 카테고리는 N:M(@ManyToMany)

 

 

 

 

위를 기반으로 코드를 작성해보자.

 

 

반응형

'공부 > JPA' 카테고리의 다른 글

프록시와 연관관계 정리  (0) 2024.12.21
JPA 고급 매핑  (2) 2024.12.08
연관관계 매핑 기초  (0) 2024.12.02
엔티티 매핑  (0) 2024.11.30
JPA 영속성 관리  (1) 2024.11.28