(1) 컬렉션
Last update: a year ago by nowwaterReading time: 2 min
14.1.1 JPA와 컬렉션
@OneToMany
,@ManyToMany
를 사용해서 일대다나 다대다 엔티티 관계를 매핑할 때 사용
@ElementCollection
을 사용해서 값 타입을 하나 이상 보관할 때 사용
하이버네이트는 엔티티를 영속 상태로 만들 때 컬렉션 필드를 하이버네이트에서 제공하는 컬렉션(PersistentBag)으로 감싸서 사용한다.
컬렉션을 효율적으로 관리하기 위해 엔티티를 영속 상태로 만들 때 원본 컬렉션을 감싸는 내장 컬렉션을 생성해서 사용.
이를 래퍼 컬렉션이라고도 부른다.
따라서 컬렉션 사용 시 즉시 초기화해서 사용하는 것을 권장한다.
Collection<Member> members = new ArrayList<Member>();
하이버네이트 내장 컬렉션과 특징
컬렉션 인터페이스 | 내장 컬렉션 | 중복 허용 | 순서 보관 |
---|---|---|---|
Collection, List | PersistenceBag | O | X |
Set | PersistenceSet | X | X |
List + @OrderColumn | PersistentList | O | O |
14.1.2 Collection, List
중복을 허용하는 컬렉션.
PersistentBag
을 래퍼 컬렉션으로 사용 ->ArrayList
로 초기화한다.엔티티를 추가할 때 중복된 엔티티가 있는지 비교하지 않고 단순히 저장만 한다.
- 엔티티를 추가해도 지연 로딩된 컬렉션을 초기화하지 않는다.
- 같은 엔티티가 있는지 찾거나 삭제 시
equals()
메소드를 사용한다. =>.contains(인스턴스)
,.remove(인스턴스)
14.1.3 Set
- 중복을 허용하지 않는 컬렉션.
PersistentSet
을 래퍼 컬렉션로 사용 ->HashSet
으로 초기화한다.
중복을 허용하지 않으므로
add()
메소드로 객체를 추가할 때 마다equals()
메소드와hashcode()
로 같은 객체가 있는지 비교한다.- 같은 객체가 없으면 객체를 추가하고
true
반환, 같은 객체가 있으면 추가에 실패하고false
반환
- 중복된 엔티티가 있는지 비교하기 때문에 엔티티를 추가할 때 지연 로딩된 컬렉션을 초기화한다.
- 같은 객체가 없으면 객체를 추가하고
14.1.4 List + @OrderColumn
List
인터페이스에@OrderColumn
을 추가하면 순서가 있는 특수한 컬렉션으로 인식한다. -> DB에 순서 값을 저장해서 조회할 때 사용한다.
import javax.annotation.processing.Generated;import java.util.ArrayList;@Entitypublic class Board {@Id@GeneratedValueprivate Long id;private String title;private String Content;@OneToMany(mappedBy = "board")@OrderColumn(name = "POSITION") // List의 위치 값을 테이블의 POSITION 컬럼에 보관. 일대다 관계여서 다쪽에 저장private List<Comment> comments = new ArrayList<Comment>();...}@Entitypublic class Comment {@Id @GeneratedValueprivate Long id;private String comment;@ManyToOne@JoinColumn(name = "BOARD_ID")private Board board;...}
@OrderColumn 사용 코드
Board board = new Board("제목1", "내용1");em.persist(board);Comment comment1 = new Comment("댓글1");comment1.setBoard(board);board.getComments().add(comment1); // POSITION 0em.persist(comment1);Comment comment2 = new Comment("댓글2");comment2.setBoard(board);board.getComments().add(comment2); // POSITION 1em.persist(comment2);Comment comment3 = new Comment("댓글3");comment3.setBoard(board);board.getComments().add(comment3); // POSITION 2em.persist(comment3);Comment comment4 = new Comment("댓글4");comment4.setBoard(board);board.getComments().add(comment4); // POSITION 3em.persist(comment4);
결과
BOARD
ID | TITLE | CONTENT |
---|---|---|
1 | 제목1 | 내용1 |
COMMENT
ID | COMMENT | COMMENTS_ID(FK) | POSITION (@OrderColumn) |
---|---|---|---|
1 | 댓글1 | 1 | 0 |
2 | 댓글2 | 1 | 1 |
3 | 댓글3 | 1 | 2 |
4 | 댓글4 | 1 | 3 |
@OrderColumn 단점
여러 단점들로 실무에서는 잘 사용하지 않는다.
@OrderColumn
을Board
엔티티에서 매핑하므로Comment
는POSITION
의 값을 알 수 없다.- 그래서
Comment
를INSERT
할 때는POSITION
값이 저장되지 않는다. POSITION
은Board.comments
의 위치 값이므로, 이 값을 사용해서POSITION
의 값을UPDATE
하는 SQL 이 추가로 발생한다.
- 그래서
List
를 변경하면 연관된 많은 위치 값을 변경해야 한다.- ex) 댓글 2 를 삭제하면 댓글 3, 댓글 4 의
POSITION
값을 각각 하나씩 줄이는UPDATE SQL
이 2번 추가로 실행된다.
- ex) 댓글 2 를 삭제하면 댓글 3, 댓글 4 의
중간에
POSITION
값이 없으면 조회한List
에는null
이 보관된다. 이 경우List
를 조회하면 컬렉션을 순회할 때NullPointerException
이 발생한다.- 예를 들어 댓글 2를 데이터베이스에서 강제로 삭제하고 다른 댓글의 POSITION 값을 수정하지 않으면, POSITION 값은
[0, 2, 3]
이 되어서 1번 위치 조회 시 예외 발생
- 예를 들어 댓글 2를 데이터베이스에서 강제로 삭제하고 다른 댓글의 POSITION 값을 수정하지 않으면, POSITION 값은
이러한 단점들 때문에 대신에 개발자가 직접
POSITION
값을 관리하거나@OrderBy
를 사용하길 권장한다.
14.1.5 @OrderBy
- 데이터베이스의
ORDER BY
절을 사용해서 컬렉션을 정렬한다. 따라서 순서용 컬럼을 매핑하지 않아도 된다.
- 모든 컬렉션에 사용할 수 있다.
- 엔티티의 필드를 대상으로 한다.
import java.util.HashSet;@Entitypublic class Team {@Id@GeneratedValueprivate Long id;private String name;@OneToMany(mappedBy = "team")@OrderBy("username desc, id asc") // Member의 username 필드로 내림차순, id로 오름차순 정렬private Set<Member> members = new HashSet<Member>(); // 이때 순서 유지를 위해 LinkedHashSet을 내부에서 사용...}@Entitypublic class Member {@Id@GeneratedValueprivate Long id;@Column(name = "MEMBER_NAME")private String username;@ManyToOneprivate Team team;...}