본문 바로가기
spring/jpa

자바 ORM 표준 JPA 프로그래밍 - 14장 컬렉션과 부가 기능

by 쭈꾸마뇽 2021. 8. 28.

컬렉션

JPA는 자바에서 기본으로 제공하는 Collection, List, Set, Map 컬렉션을 지원한다.

  • @OneToMany, @ManyToMany를 사용해서 엔티티 관계를 매핑
  • @ElementCollection을 사용해서 값 타입을 하나 이상 보관

하이버네이트는 엔티티를 영속 상태로 만들 때 컬렉션 필드를 하이버네이트에서 준비한 컬렉션으로 감싸서 사용한다.

@Test
public void test01() {
    Team team = new Team();
    System.out.println("before persist : " + team.getMembers().getClass());
    em.persist(team);
    System.out.println("after persist : " + team.getMembers().getClass());
}

ArrayList였던 컬렉션이 엔티티를 영속 상태로 만든 직후에 하이버네이터가 제공하는 PersistentBag 타입으로 바뀌었다.  하이버네이트는 컬렉션을 효율적으로 관리하기 위해 엔티티를 영속 상태로 만들 때 원본 컬렉션을 감싸고 있는 내장 컬렉션을 생성해 이 내장 컬렉션을 사용하도록 참조를 변경한다.

하이버네이트는 이런 특징 때문에 컬렉션을 사용할 때 즉시 초기화해서 사용하는 것을 권장한다.

Collection, List

Collection, List는 중복을 허용한다고 가정하여 add() 메소드는 내부에서 어떤 비교도 하지 않고 항상 true를 반환한다.  같은 엔티티가 있는지 찾거나 삭제할 떄는 equals()메소드를 사용한다.

엔티티를 추가해도 지연 로딩된 컬렉션을 초기화하지 않는다.

Set

HashSet으로 초기화하며 중복을 허용하지 않으므로 add() 메소드로 객체를 추갛 ㄹ때 마다 equals() 메소드로 같은 객체가 있는지 비교한다.

엔티티를 추가할 때 지연 로딩된 컬렉션을 초기화한다.

@OrderBy

@OrderBy는 데이터베이스의 ORDER BY절을 사용해서 컬렉션을 정렬한다.  따라서 순서용 컬럼을 매핑하지 않아도 된다.  그리고 @OrderBy는 모든 컬렉션에 사용할 수 있다.

@OneToMany(mappedBy = "team", fetch = FetchType.LAZY)
@OrderBy("name desc, id asc")
private List<Member> members = new ArrayList<>();

...

@Test
public void test02() {
    TypedQuery<Team> query = em.createQuery("select t from Team t", Team.class);

    List<Team> resultList = query.getResultList();

    resultList.forEach(System.out::println);
}

@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);
    }
}

위 코드는 Boolean값을 true, false에 따라 1, 0으로 저장하는 것이 아닌 Y, N으로 저장한다.

@Convert(converter = BooleanToYNConverter.class)
private boolean vip;

@Converter(autoApply = true)
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {

적용은 위처럼 적용하고자 하는 컬럼에 직접 하거나 글로벌 설정으로 컨버터에 직접 설정할 수 있다.

리스너

모든 엔티티를 대상으로 언제 어떤 사용자가 삭제를 요청했는지 모두 로그로 남겨야 하는 요구사항이 있다고 가정할 때 애플리케이션 삭제로직을 하나씩 찾아서 로그를 남기는 것은 너무 비효율적이다.  JPA 리스너 기능을 사용하면 엔티티의 생명 주기에 따른 이벤트 처리가 가능하다.

@PrePersist
public void perPersist() {
	System.out.println("Member.prePersist : " + id);
}

@PostPersist
public void postPersist() {
	System.out.println("Member.postPersist : " + id);
}

Member 엔티티에 위와 같이 설정하였다.

Member를 save하면 prePersiste는 id가 생성되기 전에 호출되고 postPersist는 id가 생성된 후에 호출된다.

댓글