Home 10. 엔티티 콜렉션 매핑
Post
Cancel

10. 엔티티 콜렉션 매핑

01. 엔티티 콜렉션 매핑과 연관 관리

엔티티 콜렉션 매핑은 코드를 복잡하게 만들고, 얻을 수 있는 장점은 크지 않다. 게다가 잘못 사용하면 성능에 영향까지 줄 수 있다. 하지만 콜렉션 매핑이 필요한 순간이 있기 때문에 어떻게 설정해야하는지 알 필요가 있다.

예) 스포츠 팀과 소속 선수와의 관계

1
2
3
4
5
6
7
public class Team{
    private Set<Player> players = new HashSet<>();

    public void addPlayer(player p){
        this.players.add(p);
    }
}
1
2
3
4
5
6
//Team에서 Player로의 1:N 연관이 단방향인 경우 Player의 팀을 옮기는 코드
Team team1 = new Team("팀1");
Team team2 = new Team("팀2");
Player player1 = new Player("선수1");
team1.removePlayer(player1);
team2.addPlayer(player1);
1
2
3
4
5
6
//Player에서 Team으로의 연관이 필요한 경우
public class Player{
    private Team team;

    //...
}
1
2
3
4
5
6
7
8
9
Team team1 = findTeam("팀1");
Team team2 = findTeam("팀2");
Player player1 = findPlayer("선수1");

team1.removePlayer(player1);

//양방향인 경우 관련된 엔티티의 연관을 알맞게 처리해야 한다.
player1.setTeam(team2);
team2.addPlayer(player1);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//양방향 연관을 addPlayer()에서 관리하도록 구현할 수 있다.
public class Team{
    private Set<Player> players = new HashSet<>();

    public void addPlayer(Player player) {
        Team current = player.getTeam();
        if(current == this) return;
        //player가 속한 현재 팀에서 제거
        if(current != null) {
            current.remove(player);
        }
        //새로운 팀으로 변경
        this.players.add(player);
        player.setTeam(this);
    }
}

Team team2 = new Team("탐2");
Player player1 = new Player("선수1");
team2.addPlayer(player1);

M:N 연관은 1:N 연관과 유사하지만 조금 더 복잡하다. 대표적인 예로 상품과 카테고리가 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Product {
    private Set<Category> categories;
    //...

    public void changeCategory(Set<Category> newCategories) {
        for(Category cat : this.categories) {
            if(!newCategories.contains(cat)) {
                //기존 카테고리가 변경할 카테고리에 속하지 않으면 기존 카테고리에서 상품으로의 연관 제거
                cat.removeProduct(this);
            }
        }
        //변경할 카테고리에 제품 연관 추가
        for(category nawCat : newCategories) {
            newCat.add(this);
        }
        //카테고리에 대한 연관 변경
        this.categories = newCategories;
    }
}

public class Category {
    private Set<Product> products;
    //...
}

02. 1:N 단방향 엔티티 Set 매핑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//Team 엔티티에서 Player 엔티티로의 1:N 단방향 연관설정
@Entity
public class Team {
    @Id
    private String id;
    private String name;

    @OneToMany
    @JoinColumn(name="team_id")
    priavate Set<Player> players = new HashSet<>();

    //….
}

@Entity
public class Player{
    @Id
    @Column(name="player_id")
    private String id;
    private String name;

    //….
    //equals, hashcode
}

Player 엔티티는 Set에 저장되므로 equals() 메서드와 hashCode() 메서드를 구현했다.

2.1 1:N 연관의 저장과 변경

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//엔티티간에 연관을 맺는 코드
em.getTransaction().begin();

Player p3 = em.find(Player.class, "P3");
Player p4 = new Player("P4", "선수4");
Player p5 = new Player("P5", "선수5");
em.persist(p4);
em.persist(p5);

Team t3 = new Team("T3", "팀3");
t3.addPlayer(p4);
t3.addPlayer(p5);
t3.addPlayer(p3);
em.persist(t3);
em.getTransaction().commit();

//Player의 팀을 변경하는 코드
em.getTransaction().begin();
Team t1 = em.find(Team.class, "T1");
Team t2 = em.find(Team.class, "T2");
Player p2 = null;
for(Player p : t1.getPlayers()) {
    if(p.getId().equlas("P2")) {
        p2 = p;
        break;
    }
}
t1.removePlayer(p2);
t2.addPlayer(p2);

중요한 것은 @OneToMany 연관에 저장되는 대상이 관리 상태의 엔티티여야 한다는 것이다.

2.2 1:N 연관의 조회

@OneToMany의 기본 로딩 방식은 지연 로딩이므로 연관 콜렉션을 실제로 사용하는 시점에 연관 엔티티를 조회한다.

2.3 연관에서 제외하기

Team 엔티티에 속한 Player 엔티티를 Team에서 제외하고 싶다면 단순히 콜렉션에서 삭제하면 된다.

2.4 콜렉션 지우기

Team 엔티티의 players 콜렉션을 모두 삭제하면 연된된 Player 엔티티와의 연관이 끊긴다. 콜렉션을 삭제한다는 것은 콜렉션을 통한 연관을 삭제하는 것이지 콜렉션에 포함된 엔티티를 삭제하는 것은 아니다.

03. 1:N 양방향 Set 매핑

1:N 단방향 연관을 1:N 양방향 연관으로 바꾸면 1:N 단방향 연관과 N:1 연관을 함께 설정하면 된다. 단방향 연관과 차이점이 있다면 @JoinColumn 대신에 @OneToMany의 mapperedBy 속성을 사용한다는 것이다.

1
2
3
4
5
6
7
8
9
10
@Entity
public class Player{
  //...

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

  //...
}

1:1 연광에서 양방향 연관은 DB 테이블에서 참조키를 갖는 쪽이 연관을 소유한다고 했다. 그리고 연관을 소유한 엔티티의 속성을 지정하기 위해 mappedBy 속성을 사용했었다. 1:N 양방향 연관도 마찬가지다. 위 예에서는 참조키를 들고있는 엔티티는 Player이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Entity
public class Team {
  //...

  //mappedBy는 연관의 소유의 주체가 Player team 속성이라고 지정한다.
  @OneToMany(mappedBy="team")
  private Set<Player> players = new HashSet<>();

  //...
}

em.getTransaction().begin();

Team t3 = new Team("T3", "팀3");
Player p3 = em.find(Player.class, "P3");

//연관을 소유한 쪽은 Player이기 때문에 실제로 Player의 연관만 맞게 지정해도 DB테이블에는 연관을 위한 데이터가 반영된다.
t3.addPlayer(p3);
p3.setTeam(t3);

em.persist(t3);
em.getTransacion().commit();

양방향 연관의 소유를 Player가 갖고 있으므로 Team과 Player의 연관을 제거하려면 다음과 같은 콜렉션에 속한 모든 Player에서 Team으로의 연관을 제거해야 한다.

1
2
3
4
5
6
7
8
em.getTransaction().begin();

Team t1 = em.find(Team.class, "T1");
for(Player p : t1.getPlayer()){
  p.setTeam(null);
}
t1.getPlayer().clear();
em.getTransacion().commit();

04. 조인 테이블을 이용한 1:N 단방향 엔티티 List 매핑

예) 전국을 대상으로 가전 기기 수리를 제공하는 서비스의 경우 지역별로 여러 명의 서비스 수리 담당자가 존재하고 순번이 있다고 하는 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
public class Location {
  @Id
  private String id;
  private String name;

  @OneToMany
  @JoinTable(name="loc_eng",  //조인 테이블 이름
          joinColumns=@JoinColumn(name="location_id"),  //조인 테이블에서 Location 엔티티를 참조할 때 사용할 컬럼
          inverseJoinColumns=@JoinColumn(name="engineer_id")  //조인 테이블에서 콜렉션에 포함될 Engineer 엔티티를 참조할 때 사용할 컬럼
          )
  @OrderColumn(name="list_idx")
  private List<Engineer> engineers = new ArrayList<>();
}

05. 조인 테이블을 이용한 1:N 단방향 엔티티 Map 매핑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Entity
public class Location{
  @Id
  private String id;
  private String name;

  @OneToMany
  @JoinTable(name="loc_eng",  //조인 테이블 이름
          joinColumns=@JoinColumn(name="location_id"),  //조인 테이블에서 Location 엔티티를 참조할 때 사용할 컬럼
          inverseJoinColumns=@JoinColumn(name="engineer_id")  //조인 테이블에서 콜렉션에 포함될 Engineer 엔티티를 참조할 때 사용할 컬럼
          )
  @MapKeyColumn(name = "map_key")
  private Map<String, Engineer> engineers = new HashMap<>();
}

06. M:N 단방향 연관

M:N 연관은 많은 부분에서 객체 모델을 복잡하게 만들기 때문에 가능하면 사용하지 말자.

1
2
3
4
5
6
7
8
9
10
11
12
//예) 공연과 캐스팅된 사람과의 관게
@Entity
public class Performance{
  @Id
  private String id;
  private String name;
  @ManyToMany
  @JoinTable(name="perf_person",
          joinColumns=@JoinColumn(name="performance_id"),
          inverseJoinColumns=@JoinColumn(name="person_id")
          )
  private Set<Person> cast = new HashSet<>();

07. M:N 양방향 연관

양방향 연관은 연관의 소유를 누가할지 결정하고 연관을 소유한 쪽에 @JoinTable을 설정해주면 된다.

This post is licensed under CC BY 4.0 by the author.