Close
Close full mode
logo만렙 개발자 키우기

(1) 컬렉션

Git RepositoryEdit on Github
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, ListPersistenceBagOX
SetPersistenceSetXX
List + @OrderColumnPersistentListOO

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;
@Entity
public class Board {
@Id
@GeneratedValue
private Long id;
private String title;
private String Content;
@OneToMany(mappedBy = "board")
@OrderColumn(name = "POSITION") // List의 위치 값을 테이블의 POSITION 컬럼에 보관. 일대다 관계여서 다쪽에 저장
private List<Comment> comments = new ArrayList<Comment>();
...
}
@Entity
public class Comment {
@Id @GeneratedValue
private 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 0
em.persist(comment1);
Comment comment2 = new Comment("댓글2");
comment2.setBoard(board);
board.getComments().add(comment2); // POSITION 1
em.persist(comment2);
Comment comment3 = new Comment("댓글3");
comment3.setBoard(board);
board.getComments().add(comment3); // POSITION 2
em.persist(comment3);
Comment comment4 = new Comment("댓글4");
comment4.setBoard(board);
board.getComments().add(comment4); // POSITION 3
em.persist(comment4);

결과

BOARD

IDTITLECONTENT
1제목1내용1

COMMENT

IDCOMMENTCOMMENTS_ID(FK)POSITION (@OrderColumn)
1댓글110
2댓글211
3댓글312
4댓글413

@OrderColumn 단점

여러 단점들로 실무에서는 잘 사용하지 않는다.

  • @OrderColumnBoard 엔티티에서 매핑하므로 CommentPOSITION의 값을 알 수 없다.

    • 그래서 CommentINSERT 할 때는 POSITION 값이 저장되지 않는다.
    • POSITIONBoard.comments 의 위치 값이므로, 이 값을 사용해서 POSITION의 값을 UPDATE 하는 SQL 이 추가로 발생한다.
  • List를 변경하면 연관된 많은 위치 값을 변경해야 한다.

    • ex) 댓글 2 를 삭제하면 댓글 3, 댓글 4 의 POSITION 값을 각각 하나씩 줄이는 UPDATE SQL 이 2번 추가로 실행된다.
  • 중간에 POSITION 값이 없으면 조회한 List 에는 null이 보관된다. 이 경우 List 를 조회하면 컬렉션을 순회할 때 NullPointerException이 발생한다.

    • 예를 들어 댓글 2를 데이터베이스에서 강제로 삭제하고 다른 댓글의 POSITION 값을 수정하지 않으면, POSITION 값은 [0, 2, 3] 이 되어서 1번 위치 조회 시 예외 발생

이러한 단점들 때문에 대신에 개발자가 직접 POSITION 값을 관리하거나 @OrderBy를 사용하길 권장한다.


14.1.5 @OrderBy

  • 데이터베이스의 ORDER BY 절을 사용해서 컬렉션을 정렬한다. 따라서 순서용 컬럼을 매핑하지 않아도 된다.
  • 모든 컬렉션에 사용할 수 있다.
  • 엔티티의 필드를 대상으로 한다.
import java.util.HashSet;
@Entity
public class Team {
@Id
@GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
@OrderBy("username desc, id asc") // Member의 username 필드로 내림차순, id로 오름차순 정렬
private Set<Member> members = new HashSet<Member>(); // 이때 순서 유지를 위해 LinkedHashSet을 내부에서 사용
...
}
@Entity
public class Member {
@Id
@GeneratedValue
private Long id;
@Column(name = "MEMBER_NAME")
private String username;
@ManyToOne
private Team team;
...
}
🚀 JPA — Previous
14장. 컬렉션과 부가 기능
Next — 🚀 JPA
(2) Converter