JPA는 자바에서 기본으로 제공하는 Collection, List, Set, Map 컬렉션을 지원하고 다음 경우에 이 컬렉션을 사용할 수 있다.
@OneToMany
,@ManyToMany
를 사용해서 일대다나 다대다 엔티티 관계를 매핑할 때@ElementCollection
을 사용해서 값 타입을 하나 이상 보관할 때Map
의 경우,@MapKey
를 사용해서 매핑할 수 있다.
@Entity
@Getter
public class Team {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany
@JoinColumn(name = "TEAM_ID")
private Collection<Member> members = new ArrayList<>();
}
public static void main(String[] args) {
Team team = new Team();
System.out.println("before persist = " + team.getMembers().getClass())
em.persist(team);
System.out.println("after persist = " + team.getMembers().getClass())
}
// before persist = class java.util.ArrayList
// after persist = class org.hibernate.collection.internal.PersistentBag
컬렉션 인터페이스 | 하이버네이트 내장 컬렉션 | 중복 허용 | 순서 보관 |
---|---|---|---|
Collection, List | PersistenceBag | ✅ | ❌ |
Set | PersistentSet | ❌ | ❌ |
List + @OrderColumn | PersistentList | ✅ | ✅ |
equals()
메서드를 사용public class Parent {
@Id
@GeneratedValue
private Long id;
@OneToMany
@JoinColumn
private Collection<Child> childs = new ArrayList<>();
}
equals()
, hascode()
메서드를 함께 사용해서 비교한다.public class Parent {
@Id
@GeneratedValue
private Long id;
@OneToMany
@JoinColumn
private Set<Child> childs = new HashSet<>();
}
@OrderColumn
사용 시, 순서가 있는 컬렉션으로 인식한다.public class Parent {
@Id
@GeneratedValue
private Long id;
@OneToMany
@OrderColumn(name = "INDEX")
private Collection<Child> childs = new ArrayList<>();
}
public class Child {
@Id
@GeneratedValue
private Long id;
@ManyToOne
@JoinColumn(name = "PARENT_ID")
private Parent parent;
}
@OrderColumn
은 Parent 엔티티에서 매핑하므로, Child는 index 값을 알 수 없다.null
이 보관된다.ORDER BY
절을 사용해서 컬렉션 순서를 정렬한다.@OrderBy
를 적용하면, 순서를 유지하기 위해 HashSet 대신에 LinkedHashSet을 내부에서 사용한다.public class Team {
@Id
@GeneratedValue
private Long id;
@OneToMany
@OrderBy("username desc, age asc")
private Set<Member> members = new HashSet<>();
}
public class Member {
@Id
@GeneratedValue
private Long id;
private String username;
private Integer age;
@ManyToOne
@JoinColumn(name = "TEAM_ID")
private Team team;
}
@Converter
를 사용하면, 엔티티의 데이터를 변환해서 데이터베이스에 저장할 수 있다.boolean
값을 'Y'
혹은 'N'
문자로 저장하고 싶을 때!!!!@Entity
public class Member {
@Id
private Long id;
private String username;
@Convert(converter = BooleanToYNConverter.class)
private boolean vip;
}
@Converter
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {
@Override
public String convertToDatabaseColumn(Boolean attribute) {
return (attribute != null && attribute) ? "Y" : "N";
}
public Boolean convertToEntityAttribute(String dbData) {
return "Y".equals(dbData);
}
}
AttributeConveter<X, Y>
인터페이스public interface AttributeConverter<X,Y> {
public Y convertToDatabaseColumn (X attribute);
public X convertToEntityAttribute (Y dbData);
}
@Convert
클래스 레벨에 설정하기attributeName
속성을 설정해줘야 한다.@Entity
@Convert(converter = BooleanToYNConverter.class, attributeName = "vip")
public class Member {
@Id
private Long id;
private String username;
private boolean vip;
}
@Converter(autuApply = true)
옵션을 적용하면,@Convert
설정하지 않아도 자동으로 컨버터가 적용된다.@Converter(autuApply = true)
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {
@Override
public String convertToDatabaseColumn(Boolean attribute) {
return (attribute != null && attribute) ? "Y" : "N";
}
public Boolean convertToEntityAttribute(String dbData) {
return "Y".equals(dbData);
}
}
@Convert
속성 정리속성 | 기능 | 기본값 |
---|---|---|
converter | 사용할 컨버터 클래스를 지정한다. | |
attributeName | 컨버터를 적용할 클래스 멤버 필드를 지정한다. | |
disableConversion | 글로벌 컨버터나 상속받은 컨버터를 사용하지 않는다. | false |
번호 | Event | Event 시점 |
---|---|---|
1 | PostLoad | - 엔티티가 영속성 컨텍스트에 조회된 직후 - refresh 를 호출한 후 |
2 | PrePersist | persist 호출 직전 (영속성 컨텍스트에 관리되기 직전) |
3 | PreUpdate | flush 나 commit 호출해서, 엔티티를 데이터베이스에 반영하기 직전 |
4 | PreRemove | - remove 를 호출해서 엔티티를 영속성 컨텍스트에서 삭제하기 직전- 삭제 명령어로 영속성 전이가 일어날 때 - orphanRemoval 에 대해서 flush 나 commit 시 |
5 | PostPersist | flush 나 commit 을 호출해서 엔티티를 데이터베이스에 저장한 직후 |
6 | PostUpdate | flush 나 commit 을 호출해서 엔티티를 데이터베이스에 수정한 직후 |
7 | PostRemove | flush 나 commit 을 호출해서 엔티티를 데이터베이스에 삭제한 직후 |
@Entity
@Slf4j
public class Duck {
@Id
@GeneratedValue
private Long id;
private String name;
@PrePersist
public void prePersist() {
log.info("Duck.prePersist id = " + id);
}
@PostPersist
public void postPersist() {
log.info("Duck.postPersist id = " + id);
}
@PostLoad
public void postLoad() {
log.info("Duck.postLoad id = " + id);
}
@PreUpdate
public void preUpdate() {
log.info("Duck.preUpdate id = " + id);
}
@PostUpdate
public void postUpdate() {
log.info("Duck.postUpdate id = " + id);
}
@PreRemove
public void preRemove() {
log.info("Duck.preRemove id = " + id);
}
@PostRemove
public void postRemove() {
log.info("Duck.postRemove id = " + id);
}
}
@EntityListeners
)@Entity
@EntityListeners(DuckListener.class)
public class Duck {
...
}
public class DuckListener {
@PrePersist
public void prePersist(Object object) {
log.info("Duck.prePersist id = " + id);
}
@PostPersist
public void postPersist(Duck duck) {
log.info("Duck.postPersist id = " + id);
}
}
<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings xmlns="http://java.sun.com/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_1.xsd"
version="2.1">
<persistence-unit-metadata>
<persistence-unit-defaults>
<entity-listeners>
<entity-listener class="org.sterl.example.EntityListener">
<!-- optional, annotations will work too -->
<pre-persist method-name="fooMethod"/>
</entity-listener>
</entity-listeners>
</persistence-unit-defaults>
</persistence-unit-metadata>
</entity-mappings>
@ExcludeDefaultListners
@ExcludeSuperclassListeners