컬렉션
JPA에서는 자바에서 기본으로 제공하는 Collection, List, Set, Map 컬렉션을 지원한다
일대다이나 다대다 엔티티 관계를 매핑할 때, 값 타입 하나 이상을 보관(@ElementCollection)할 때 컬렉션을 사용할 수 있다
JPA 구현체에 따라서 제공하는 기능이 조금씩 다를 수 있다
JPA와 컬렉션
엔티티를 영속으로 만들 때 컬렉션 필드를 하이버네이트에서 준비한 컬렉션으로 감싸서 사용한다
원래 ArrayList 타입이었던 필드가 엔티티를 영속 상태로 만든 후 하이버네이트가 제공하는 PersistentBag 타입으로 변경된다
원본 컬렉션을 감싸는 내장 컬렉션을 생성해 이 컬렉션을 사용하도록 참조를 변경한다
이 내장 컬렉션을 원본 컬렉션을 감싸고 있다는 의미의 래퍼 컬렉션이라고도 한다
컬렉션을 사용할 때 즉시 초기화해서 사용하는 것을 권장한다
Collection, List
중복을 허용하는 컬렉션이다
PersistentBag을 래퍼 컬렉션으로 사용한다
ArrayList로 초기화하면 된다
Collection, List는 중복을 허용하므로 객체를 추가하는 add() 메소드는 내부에서 비교를 하지 않고 true를 반환한다
같은 엔티티가 있는지 찾거나 삭제할 때에는 equals() 메소드를 사용한다
엔티티를 추가할 때 중복된 엔티티가 있는지 비교하지 않고 단순히 저장만 하면 되므로 엔티티를 추가해도 지연 로딩된 컬렉션을 초기화하지 않는다
Set
중복을 허용하지 않는 컬렉션이다
PersistentSet을 컬렉션 래퍼로 사용한다
HashSet으로 초기화하면 된다
중복을 허용하지 않으므로 add()로 객체를 추가할 때마다 equals() 메소드로 같은 객체가 있는지 비교한다
HashSet은 해시 알고리즘을 이용해 hashCode()도 함께 사용해서 비교한다
엔티티 추가 시 중복된 엔티티가 있는지 비교해야 하므로 지연 로딩된 컬렉션을 초기화한다
List + @OrderColumn
List 인터페이스에 @OrderColumn을 추가하면 순서가 있는 특수한 컬렉션으로 인식한다
순서가 있다는 것은 데이터베이스에 순서 값을 저장해 조회할 때 사용한다는 의미이다
PersistentList를 컬렉션 래퍼로 사용한다
@Entity
public class Board {
@Id @GeneratedValue
private Long id;
private String title;
private String content;
@OneToMany(mappedBy = "board")
@OrderColumn(name="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;
...
}
자바가 제공하는 List 컬렉션은 내부에 위치 값을 가지고 있어서 List의 위치 값을 활용할 수 있다
순서가 있는 컬렉션은 데이터베이스에 순서 값도 함께 관리한다
위의 예시에서는 List의 위치 값을 COMMENT 테이블의 POSITION 컬럼에 보관한다
@OrderColumn의 단점
1. @OrderColumn을 Board 엔티티에서 매핑하므로 Comment는 POSITION 값을 알 수 없다
Comment를 insert할 때는 POSITION 값이 저장되지 않고 리스트 내의 위치 값을 이용해 POSITION을 update하는 sql이 추가적으로 발생한다
2. List를 변경하면 연관된 많은 위치 값을 변경해야 한다
Comment 삭제시 삭제한 댓글 뒤의 모든 댓글들의 위치를 하나씩 줄여야 한다
3. 중간에 POSITION 값이 없으면 조회한 List에는 null이 보관된다
댓글을 삭제한 후 뒤의 댓글들의 위치 값을 수정하지 않으면 해당 위치에 null 값이 보관된다
컬렉션 순회 시 NullPointerException이 발생한다
@OrderColumn을 매핑하지 말고 개발자가 직접 POSITION 값을 관리하거나 @OrderBy를 사용하는 것을 권장한다
@OrderBy
데이터베이스의 ORDER BY 절을 사용해서 컬렉션을 정의한다
순서용 컬럼을 매핑하지 않아도 되고 모든 컬렉션에 사용할 수 있다
@Entity
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy="team")
@OrderBy("username desc, id asc")
private Set<Member> Members = new HashSet<Member>();
...
}
@Entity
public class Member {
@Id @GeneratedValue
private Long id;
@Column(name="MEMBER_NAME")
private String username;
@ManyToOne
private Team team;
...
}
Member의 username 필드로 내림차순으로 정렬하고 id로 오름차순으로 정렬했다
@OrderBy의 값은 JPQL의 order by절 처럼 엔티티 필드를 대상으로 한다
@Converter
컨버터를 사용하면 엔티티의 데이터를 변환해서 데이터베이스에 저장할 수 있다
boolean 타입의 데이터를 데이터베이스에 저장될 때 0 또는 1이 아닌 Y 또는 N으로 저장하고 싶다면 컨버터를 사용하면 된다
@Entity
public class Member {
@Id
private String id;
private String username;
@Convert(converter=BooleanToYNConverter.class)
private boolean vip;
//Getter, Setter
}
@Converter
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {
@Override
public String convertToDatabaseColumn(Boolean attribute) {
return (attribute != null && attribute) ? "Y" : "N";
}
@Override
public Boolean convertToEntityAttribute(String dbData) {
return "Y".equals(dbData);
}
}
컨버터 클래스는 @Converter를 사용하고 AttributeConverter 인터페이스를 구현해야 한다
제네릭에는 현재 타입과 변환할 타입을 지정해야 한다
AttributeConverter 인터페이스는 구현해야할 두 개의 메소드가 있다
- convertToDatabaseColumn(): 엔티티의 데이터를 데이터베이스 컬럼에 저장할 데이터로 변환한다
- convertToEntityAttribute(): 데이터베이스에서 조회한 컬럼 데이터를 엔티티 데이터로 변환한다
@Convert는 클래스 레벨에도 설정할 수 있는데 이때 어떤 필드에 컨버터를 적용할 것인지 명시해야 한다
@Entity
@Convert(converter=BooleanToYNConverter.class, attributeName = "vip")
public class Member {
@Id
private String id;
private String username;
private boolean vip;
//Getter, Setter
}
글로벌 설정
모든 Boolean 타입에 컨버터를 적용하려먼 컨버터 클래스에 @Converter(autoApply=true) 옵션을 적용하면 된다
@Convert를 지정하지 않아도 자동으로 모든 Boolean 타입에 대해서 자동으로 컨버터가 적용된다
'스터디 > JPA' 카테고리의 다른 글
웹 애플리케이션과 영속성 관리 - (2) (0) | 2025.03.21 |
---|---|
웹 애플리케이션과 영속성 관리 - (1) (0) | 2025.03.20 |
스프링 데이터 JPA - (2) (0) | 2025.02.20 |
스프링 데이터 JPA - (1) (0) | 2025.02.20 |
객체지향 쿼리 언어 - (7) (1) | 2025.02.19 |