UserType만들기

클래스 구현하기



public class MonetaryAmountUserType implements UserType {

public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return cached;
}

public Object deepCopy(Object value) throws HibernateException {
return value;
}

public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}

public boolean equals(Object x, Object y) throws HibernateException {
if (x == y)
return true;
if (x == null || y == null)
return false;
return x.equals(y);
}

public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}

public boolean isMutable() {
return false;
}

public Object nullSafeGet(ResultSet resultSet, String[] names, Object owner)
throws HibernateException, SQLException {

BigDecimal valueInUSD = resultSet.getBigDecimal(names[0]);
if (resultSet.wasNull())
return null;

// DB에서 가져온 다음에 원하는 값으로 캐스팅 해서 반환.

return null;
}

public void nullSafeSet(PreparedStatement statement, Object value, int index)
throws HibernateException, SQLException {

// SQL을 DB에 보내기 전에 statement의 특정 위치에 들어갈 값을 변경한다.

}

public Object replace(Object original, Object target, Object owner)
throws HibernateException {
return original;
}

public Class returnedClass() {
return MonetaryAmount.class;
}

public int[] sqlTypes() {
return new int[] { Hibernate.BIG_DECIMAL.sqlType() };
}

}



  • sqlTypes() 메소드는 하이버네이트가 어떤 SQL 컬럼 타입으로 DDL 스키마를 생성할 지 알려준다.


  • returnClass() 메소드는 어떤 자바 타입을 맵핑할 것인지 알려준다.


  • deepCopy()는 스냅샷 객체을 전달해 준다. 따라서, immutable한 타입일 경우에는 그냥 넘기면 된다.


  • disassemble()은 MonetaryAmount를 하이버네이트 2차 캐쉬에 집어 넣을 때 호출된다. 이곳에서 직렬화된 상태로 데이터를 캐슁한다.


  • assemble()은 캐쉬된 상태의 데이터를 MonetaryAmount 객체로 변환한다.


  • replace()는 detached 상태의 객체를 merging할 때 사용한다.


  • equals()는 dirty checking 할 때 사용한다.


  • nullSafeGet()은 ResultSet에서 데이터를 가져올 때 사용한다.


  • nullSafeSet()은 PreparedStatement에 저장할 값을 설정할 때 사용한다.

맵핑하기



@org.hibernate.annotations.Type(
type = ” persistence.MonetaryAmountUserType”
)
@Column(name = “INITIAL_PRICE”)
private MonetaryAmount initialPrice;

단점



  • 하이버네이트는 Monetary Amount의 속성을 모른다. 따라서 그 안에 있는 amount나 currency를 가져오는 쿼리를 작성할 수 없다.

해결책



  • CompositeUserType을 사용하면 된다.

맵핑 타입 사용하기

  • type 속성을 명시하지 않아도 된다.
  • java.lang.String 타입의 변수는 기본 맵핑으로 string을 사용한다.
  • java.util.Date는 timestamp를 사용한다.
  • JPA를 사용할 때는 @Temporal을 사용해서 정확도를 명시해 준다.
  • 기본값은 TemporalType.TIMESTAMP, (가용한 값은, TIEM, DATE)
  • 하이버네이트 맵핑 타입을 명시적으로 표현하고 싶을 때는 하이버네이트의 @Type 애노테이션 사용.
  • 커스텀 타입도 만들 수 있다.

내장된 맵핑 타입

특징

  • ORM은 자바의 타입과 SQL 데이터타입을 중개해 주는 역할을 해야한다.
  • 하이버는 보통 자바 타입 이름을 따르고 있다. 하지만 자바 타입 하나에 여러 하이버네이트 타입이 있을 수 있다.
  • Custom Value Type을 정의할 수 있다.

자바 기본 타입 맵핑

  • 벤더가 ANSI 표준을 따르지 않을 수도 있다. 그래도 JDBC 드라이버는 추상화 계층을 제공해 주니까 이걸 하이버네이트가 읽어 들여서 벤더에 적당한 타입으로 변환해준다.
  • 즉, 사용자는 타입 걱정하지 않아도 된다.
  • 문자열의 길이 제한 설정(length 속성 값)에 따라서 하이버가 적당한 타입으로 결정한다.
  • 이런 전략 자체도 사용자 맘대로 변경할 수 있다.
Java Type Hibernate Mapping Type SQL Type
int, Integer integer INTEGER
long, Long long BIGINT
short, Short short SMALLINT
float, FLOAT float FLOAT
double, Double double DOUBLE
BigDecimal big_decimal NUMERIC
String character CHAR(1)
String string VARCHAR
byte, Byte byte TINYINT
boolean, Boolean boolean BIT
boolean, Boolean yes_no CHAR(1) (‘Y’ or ‘N’)
boolean, Boolean true_false CHAR(1) (‘T’ or ‘F’)

날짜와 시간 맵핑

  • java.util.Date 타입을 timestamp로 맵핑할 경우 DB에서 나노초까지 값을 가져온다.
  • 책을 쓸 시점에서는 나노초를 짤라주지 않아서, equlas() 메소드로 비교할 경우 Date.getTime() 으로 비교하거나 Custom Mapping Type을 만들어서 사용해야 한다.
Java Type Hibernate Mapping Type SQL Type
java.util.Data, java.sql.Date date DATE
java.util.Date, Java.sql.Time time TIME
java.util.Date, java.sql.Timestamp timetamp TIMESTAMP
java.util.Calendar calendar TIMESTAMP
java.util.Calendar calendar_date DATE

바이너리와 큰 값 맵핑

  • binary 맵핑 타입만 식별자 속성으로 사용할 수 있다.
  • byte[]일 경우에는 VARBINARY로 맵핑하고, String일 경우에는 CLOB로 맵핑할 수 있다.
  • 이 두 경우에 Entity 객체가 속성 값을 읽어들일 때, 초기화를 하려고 할텐데, 너무 큰 값이라서 Lazy loading 하고 싶을 수 있다.
  • 특정 속성을 lazy loading 하는 두 가지 방법이 있다.
    • 13.1.6에서 다루는 Lazy loading with interception
    • java.sql.Clob 나 java.sql.Blob 타입을 사용한다.(로딩하는 시점에 Entity는 Persistent 상태여야 한다.)
  • JPA를 사용할 때 String 타입은 기본으로 VARCHAR로 맵핑 된다. Clob 타입으로 맵핑하려면 @Lob 애노테이션을 사용한다.
  • Serializable 타입은 변수의 값을 바이트 스트림으로 바꿔서 VARBINARY 타입 컬럼에 저장한다. 로딩할 때는 역직렬화한다.
Java Type Hibernate Mapping Type SQL Type
byte[] binary VARBINARY
String text CLOB
java.sql.Clob clob CLOB
java.sql.Blob blob BLOB
java.io.Serializable serializable VARBINARY

JDK 맵핑 타입

  • <property>만 type 속성을 가진 것은 아니다.
Java Type Hibernate Mapping Type SQL Type
java.lang.Class class VARCHAR
java.util.Locale locale VARCHAR
java.util.TimeZone timezone VARCHAR
java.util.Currency currency VARCHAR

Entity와 Value type 복습

특징

Entity Value Type
Coarse-grained Fine-grained
도메인 설명에 잘 나타난다. 잘 나타나지 않는다.
Persistent id를 가지고 있다. 그런거 없고, Entity 클래스에 종속된다.
상태(transient, detached, persistent)가 있다. 이런 상태를 신경쓰지 않는다.
자신만의 라이프 사이클을 가지고 있다. 라이프 사이클은 자신을 소유한 Entity를 따른다.
공유 된다. 공유되지 않는다. Entity는 고유한 Value Type을 가지고 있다.
@Entity로 맵핑한다. @Embeddable로 맵핑한다.

상속 맵핑 전략 선택하기

맵핑 전략

  • 다형적인 관계나 쿼리가 필요하지 ㅇ낳다면, Table Per Concrete Class를 사용하라. 즉, 상위 타입으로 객체들을 가져다 쓰지 않을거라면…
  • 다형적인 관계나 쿼리가 필요하고, 하위 클래스들의 속성이 몇 개 되지 않는 다면, Table Per Class Hierarchy를 사용하라.
  • 다형적인 관계나 쿼리가 필요하고, 하위 클래스들에 속성들이 많다면 Table Per SubClass를 사용하라.

특징

  • 간단한 경우에는 Table Per Class Hierarchy를 사용하라. 그 다음에 좀 더 복잡한 경우에
    Table Per Subclass를 고려하라. 하지만 그렇게 하기 전에 상속구조를 Deligation으로 대체할 수 없을지
    고민해 봐라.
  • 하이버네이트가 도메인과 관계형 모델의 버퍼 역할을 해주긴 하지만, 그렇다고 해서 Persistence Concern을 몰라도 된다는 뜻은 아니다.