@OrderBy

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

@OneToMany(mappedBy = "team")
@OrderBy("username desc, id asc")
private Set<Member> members = new HashSet<Member>();

위는 Team.members를 보면 @OrderBy를 적용했다. 그리고 값으로는 username desc, id asc를 사용해서 username필드로 내림차순 id로는 오름차순 정렬을 하였다. @OrderBy의 값은 JPQL의 order by절처럼 엔티티의 필드를 대상으로 한다.

select m.*
from
   Member m
where
   m.team_id = ?
order by
   m.member_name desc,
   m.id asc

참고로 하이버네이트는 Set에 OrderBy를 적용해서 결과를 조회하면 순서를 유지하기 위해 HashSet대신 LinkedHashSet을 내부에서 사용한다.

@Converter

컨버터를 사용하면 엔티티의 데이터를 변환해서 데이터베이스에 저장할 수 있다. 예를들어 회원 VIP여부를 자바의 boolean 타입을 사용하고 싶다고 가정 할 때 JPA를 사용하면 자바의 boolean 타입은 방언에 따라 다르지만 데이터베이스에 저장될 때 0 또는 1인 숫자로 저장된다. 그런데 데이터베이스에 숫자 대신 문자 Y 또는 N으로 저장하고 싶다면 컨버터를 이용하면 된다.

@Entity
@Data
public class Member {

  @Id
  private String id;

  private String name;

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

회원 엔티티의 vip 필드는 boolean 타입이다. @Convert를 적용해서 데이터베이스에 저장되기 직전에 BooleanToYNConverter 컨버터가 동작하도록 했다.

@Converter
public class BooleanToYNConverter implements AttributeConverter<Boolean, String> {

  public String convertToDatabaseColumn(Boolean attribute) {
    return (attribute != null && attribute) ? "Y" : "N";
  }

  public Boolean convertToEntityAttribute(String s) {
    return "Y".equals(s);
  }
}

컨버터 클래스는 @Converter 어노테이션을 사용하고 AttributeConverter 인터페이스를 구현해야 한다. 현재 우리는 Boolean타입을 String 타입으로 변환했다.
AttributeConverter 인터페이스는 두개의 메서드가 존재 한다.

convertToDatabaseColumn() : 엔티티의 데이터를 데이터베이스 컬럼에 저장할 데이터로 변환한다. 위에서는 true이면 Y 아니면 false면 N을 반환하도록 했다.
convertToEntityAttribute() : 데이터베이스에서 조회한 컬럼 데이터를 엔티티의 데이터로 변환한다. 위에서는 Y면 true아니면 false를 반환했다.

컨버터 클래스 레벨로 가능하다.

@Entity
@Data
@Convert(converter = BooleanToYNConverter.class, attributeName = "vip")
public class Member {

  @Id
  private String id;

  private String name;

  private boolean vip;
}

글로벌하게 설정도 가능하다.

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

  public String convertToDatabaseColumn(Boolean attribute) {
    return (attribute != null && attribute) ? "Y" : "N";
  }

  public Boolean convertToEntityAttribute(String s) {
    return "Y".equals(s);
  }
}

이렇게하면 자동으로 boolean 값은 모두 컨버터 된다.

리스너

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

이벤트의 종류
1. PostLoad : 엔티티가 영속성 컨텍스트에 조회된 직후 또는 refresh를 호출한 후
2. PrePersist : persist() 메서드를 호출해서 엔티티를 영속성컨텍스트에 관리하기 직전에 호출 된다. 식별자 생성 전략을 사용한 경우 엔티티에 식별자는 아직 존재 하지 않는다. 새로운 인스턴스를 merge할 때도 수행된다.
3. PreUpdate : flush나 commit을 호출해서 엔티티를 데이터베이스에 수정하기 직전에 호출된다.
4. PreRemove : remove() 메서드를 호출해서 엔티티를 영속성 컨텍스트에서 삭제하기 직전에 호출된다. 또한 삭제 명령어로 영속성 전이가 일어날 때도 호출 된다. orphanRemoval에 대해서는 flush나 commit시 호출 된다.
5. Postpersist : flush나 commit을 호출해서 엔티티를 데이터베이스에 저장한 직후에 호출된다. 식별자가 항상 존재한다. 참고로 식별자 생성 전략이 IDENTITY면 식별자를 생성하기 위해 persist()를 호출한 직후에 바로 Postpersist가 호출 된다.
6. PostUpdate : flush나 commit을 호출해서 엔티티를 데이터베이스에 수정한 직후에 호출 된다.
7. PostRemove : flush나 commit을 호출해서 엔티티를 데이터베이스에 삭제한 직후에 호출된다.

@Entity
@Data
public class Duck {

  @Id @GeneratedValue
  private Long id;

  private String name;

  @PrePersist
  public void prePersist(){
    System.out.println("Duck.prePersist id=" + id);
  }

  @PostPersist
  public void postPersist(){
    System.out.println("Duck.postPersist id=" + id);
  }
  @PostLoad
  public void postLoad(){
    System.out.println("Duck.postLoad");
  }

  @PreRemove
  public void preRemove(){
    System.out.println("Duck.preRemove");
  }

  @PostRemove
  public void postRemove(){
    System.out.println("Duck.postRemove");
  }
}

별도의 리스너 등록

@Entity
@Data
@EntityListeners(DuckListener.class)
public class Duck {

  @Id @GeneratedValue
  private Long id;
}
public class DuckListener {

  @PrePersist
  public void perPersist(Object obj){
    System.out.println("DuckListener.perPersist obj=" + obj);
  }

  @PostPersist
  public void postPersist(Object obj){
    System.out.println("DuckListener.postPersist obj=" + obj);
  }
}

리스너의 파라미터가 특정 타입이 확실하다면 Object이 아닌 특정타입으로 받을 수 있다. 반환타입은 void로 설정해야 한다.

출처 : 자바 ORM 표준 JPA 프로그래밍 (김영한)