자라선

[자바 ORM 표쥰 JPA 프로그래밍] 12일차 - 엔티티 매핑 본문

Develop/JPA

[자바 ORM 표쥰 JPA 프로그래밍] 12일차 - 엔티티 매핑

자라선 2021. 8. 26. 12:01

DB 테이블은 PK와 FK를 가지고 테이블간 관계를 맺어주는데

엔티티는 기본적으로 단방향 매핑 밖에 안되어 JPA가 별도의 주인을 정하여 양방향 매핑을 가능토록 해준다.

 

테이블 관계에서 4가지의 유형이 있다

  • 1 : 1  - 서로 1대1로 매핑하는 관계로 테이블 정규화시 보통 이러한 관계로 나타내곤한다.
  • 1 : N - 1 대 N 으로 하나의 ROW가 N개의 ROW와 매핑이 가능한 상태이며 가장 많이 나오는 관계이다.
  • N : 1 - 1 대 N 과 동일하다.
  • N : N - N 대 N 관계로 실제 사용하기에는 복잡하여 보통 정규화를통해 1:N 이나 N:1로 추가한다.

 

 

1. 다대일 관계

일반적으로 테이블에서는 관계를 쉽게 표현할 수 있기 때문에 단방향, 양방향이라는 단어를 쓰진않는다.

 

하지만 엔티티의 경우 객체끼리의 단방향은 할수는 있어도 서로를 참조하는 양방향은 못한다.

public class Member {

    // Member -> Team 를 단방향 참조
    private Team team;
    
    ...
}

public class Team {

    // Team -> Member 로 단방향 참조
    private Member member;
    
    ...
}

 

그렇기 때문에 JPA는 외래키를 갖고있는 엔티티를 주인으로 선정하여

주인 엔티티를 기준점으로 서로를 참조할 수 있게끔 기능을 지원해준다.

@Entity
public class Member {

    @ManyToOne  // Member가 주인이 되어 Team를 참조
    @JoinColumn(name = "TEAM_ID")
    private Team team;
    
    ...
}

@Entity
public class Team {

    @OneToMany(mappedBy = "team")   // Member 가 주인이며 참조역할만 해준다.
    private List<Member> members = new ArrayList<Member>();
    
    ...
}

 

Member 가 N 이 되며 Team 1 이 되어 N:1 관계를 엔티티로 표현하였다.

 

2. 일대일 관계

1:1 관계에서는 주 테이블과 대상 테이블 중 하나를 택하여 외래키를 주어지게 되는데

예를 들어 MEMBER 와 LOCKER 는 1:1 관계로 맺어져야한다면 

MEMBER는 대상 테이블이 되는 것이고 LOCKER 가 주 테이블이 된다.

 

주 테이블에 외래키를 갖게되는 것은 객체 참조를 쉽게 할수있어 객체지향개발자들이 선호하고

대상 테이블에 외래키를 갖는 것은 DB개발자들이 선호한다.

 

@Entity	// 대상 테이블
public class Member {
    
    @OneToOne
    @JoinColumn(name = "LOCKER_ID")	// 주인
    private Locker locker;
    
    ...
}

@Entity	// 주 테이블 
public class Locker {

    @OneToOne(mappedBy = "locker")	// 주인이 아니여서 mappedBy를 추가
    private Member member;
    
    ...
}

위는 대상 테이블에 외래키를 설정한 코드

다대일과 동일하게 작성하면 된다.

사실상 주인이 아닌 쪽에서 List 가 객체로 바뀐셈

 

3. 다대다 관계

일반적인 정규화된 테이블끼리는 다대다가 나올수가 없다.

관리하기도 복잡하고 실제 사용하기도 불편하기 때문이다.

 

JPA는 이러한 다대다 관계 테이블을 쉽게 사용하도록 제공해주는데.

N:N 용 테이블을 자동으로 생성하여 엔티티끼리는 1:N N:1 로 변경해준다.

 

@Entity // 대상 테이블
public class User {

    @Id
    @Column(name = "USER_ID")
    @GeneratedValue
    private long id;

    @Column(name = "USER_NM")
    private String name;

    @ManyToMany // 주인 
    @JoinTable(name = "USER_ITEM",
                joinColumns = @JoinColumn(name = "USER_ID"),
                inverseJoinColumns = @JoinColumn(name = "ITEM_ID"))
    private List<Item> items = new ArrayList<Item>();

}

@Entity // 주 테이블
public class Item {

    @Id
    @Column(name = "ITEM_ID")
    @GeneratedValue
    private long id;

    @Column(name = "ITEM_NM")
    private String name;

    @ManyToMany(mappedBy = "items") // 주인이 아니여서 mappedBy 를 추가
    private List<User> users = new ArrayList<User>();
}

다른 엔티티 관계 매핑과 비슷해보일수는 있는데

한가지 다른점은 주인 엔티티쪽의 @JoinTable 이라는 어노테이션을 사용했다는것이다.

 

이는 다대다 와 같이 번거로운 관계를 JPA가 1:N - N:1 관계로 풀어사용하려다 보니 중간에 매핑용 엔티티를 추가해야하는데 

별도의 엔티티 추가조작없이 단순하게 어노테이션 속성만으로 테이블명, 매핑 컬럼을 작성하여 매핑용 테이블을 생성해준다.

 

USER_ITEM 은 엔티티를 따로 정의하지않았어도 생성됨

 

4. 다대다 관계 2

실제 설계시 다대다 관계와 같이 설계될리는 없기 때문에 현실적으로는 설계에서도 중간의 매핑용 테이블을 추가하여 정규화를 진행해준다.

@Entity
public class User {

    @Id
    @Column(name = "USER_ID")
    @GeneratedValue
    private long id;

    @Column(name = "USER_NM")
    private String name;

    @OneToMany(mappedBy = "user")
    private List<UserItem> userItems = new ArrayList<UserItem>();

}

@Entity // 주 테이블
public class Item {

    @Id
    @Column(name = "ITEM_ID")
    @GeneratedValue
    private long id;

    @Column(name = "ITEM_NM")
    private String name;

    @OneToMany(mappedBy = "item")
    private List<UserItem> userItems = new ArrayList<UserItem>();
    
}

@Entity
@IdClass(UserItemId.class)	// PK가 2개면 IdClass를 별도로 추가하여 관리
public class UserItem {

    @Id
    @ManyToOne
    @JoinColumn(name = "USER_ID")
    private User user;

    @Id
    @ManyToOne
    @JoinColumn(name = "ITEM_ID")
    private Item item;

}

// 식별용 클래스, 복합키 일때 사용된다.
public class UserItemId implements Serializable {

    private long user;
    private long item;

}

 

서로 다대다 관계인 엔티티를 중간에 정규식을 통해 엔티티를 추가하였다.

단 이는 식별관계가 되어 별도의 식별용 클래스를 생성해야한다.

 

  • 식별관계: 식별관계란 테이블간의 FK가 PK역할을 동시에 수행하는 관계를 말함(PK -> PK +FK)
  • 비식별관계: PK와 FK가 별도로 사용됨 (PK -> FK, PK)

 

식별용 클래스는 @Entity 어노테이션이 필요없으며 필수적으로 Serializable 를 상속받아야하며 equals나 hashcode를 필수로 구현해야하는 등 조건이 여러가지가 있다. (자세한건 나중에..)

 

5. 다대다 관계 3

다대다 관계2에서 처럼 설계 하되 비식별 관계로 많이들 사용한다.

비식별관계는 FK가 별도의 컬럼으로 사용되는 일반적인 관계를 말한다(식별은 PK와 FK를 같이 사용함)

 

이는 식별용 클래스가 필요없기 때문에 구현이 단순하다.

@Entity
public class UserItem {

    @Id
    @Column(name = "USER_ITEM_ID")
    @GeneratedValue
    private long id;

    @ManyToOne  // N:1
    @JoinColumn(name = "USER_ID")
    private User user;

    @ManyToOne  // N:1
    @JoinColumn(name = "ITEM_ID")
    private Item item;

}

 

다대다 관계2 번에서 UserItem 엔티티에 @IdClass 를 제거 후 PK를 하나만 생성 하여 FK용 외래키 필드를 추가하면된다.

관계도

    public static void login(EntityManager em){
        final User user = em.find(User.class, 1l);
        for (UserItem ui : user.getUserItems()) {
            System.out.println("ui = " + ui.getItem().getName());
        }
    }

USER PK 1만 조회하여 매핑된 모든 ITEM를 조회하는 로직

Comments