Enum Type 만들기

클래스 구현하기.



  • EnhancedUserType와 ParameterizedType 인터페이스를 구현한다.


private Class<Enum> enumClass;

public void setParameterValues(Properties parameters) {
String enumClassName =
parameters.getProperty(“enumClassname”);
try {
enumClass = ReflectHelper.classForName(enumClassName);
} catch (ClassNotFoundException cnfe) {
throw new
HibernateException(“Enum class not found”, cnfe);
}
}

public Class returnedClass() {
return enumClass;
}

public Object fromXMLString(String xmlValue) {
return Enum.valueOf(enumClass, xmlValue);
}

public String objectToSQLString(Object value) {
return ‘\” + ( (Enum) value ).name() + ‘\”;
}

public String toXMLString(Object value) {
return ( (Enum) value ).name();
}

public Object nullSafeGet(ResultSet rs,
String[] names,
Object owner)
throws SQLException {
String name = rs.getString( names[0] );
return rs.wasNull() ? null : Enum.valueOf(enumClass, name);
}

public void nullSafeSet(PreparedStatement st,
Object value,
int index)
throws SQLException {
if (value == null) {
st.setNull(index, Hibernate.STRING.sqlType());
} else {
st.setString( index, ( (Enum) value ).name() );
}
}



  • setParameterValues()는 클래스 이름 가져와서 Class 변수에 세팅한다.


  • fromXMLString(), objectToSQLString(), toXMLString()는 XML과 Enum의 value를 맵핑한다.


  • nullSafeGet()는 DB에서 value를 읽은 다음 Enum으로 반환한다.


  • nullSafeSet()는 Enum에서 DB로 저장할 value를 뽑아낸다.

맵핑하기



  • JPA의 경우 커스텀 타입 클래스 만들지 않고도 String으로 저장하거나, 선택된 Enum의 value를 저장할 수 있다.


public class Comment {

@Enumerated(EnumType.STRING)
@Column(name = “RATING”, nullable = false, updatable = false)
private Rating rating;

}

쿼리 작성하기



  • 다음과 같이 쿼리를 작성할 수 있다.


Query q =
session.createQuery(
“from Comment c where c.rating = auction.model.Rating.BAD”
);


  • c.rating의 rating은 DB 컬럼의 이름이 아니라 객체의 속성이름이다.


“from Member m where m.memberType = ‘” + MemberType.ADMIN + “‘”
“from Member m where m.memberType = chapter5.customType.example.MemberType.ADMIN”

ParameterizedType 만들기

클래스 구현하기



  • DB에 값을 저장할 때 특정 조건에 따라 다른 값으로 변환하여 저장할 필요가 있다면, 파라미터가 필요하다. 이럴 때 유요하다.


  • ParameterizedType 인터페이스를 구현한다.


private Currency convertTo;

@Override
public void setParameterValues(Properties parameters) {
this.convertTo = Currency.getInstance(
parameters.getProperty(“convertTo”));
}


맵핑하기



  • 같은 Value Type 클래스 타입의 객체들 여럿을 맵핑 할 때는 TypeDef 애노테이션을 사용해서, 전역에서 사용할 설정을 정의해 둘 수 있다.


@org.hibernate.annotations.TypeDefs({
@org.hibernate.annotations.TypeDef(
name=“monetary_amount_usd”,
typeClass = persistence.MonetaryAmountConversionType.class,
parameters = { @Parameter(name=“convertTo”, value=“USD”) }
),
@org.hibernate.annotations.TypeDef(
name=“monetary_amount_eur”,
typeClass = persistence.MonetaryAmountConversionType.class,
parameters = { @Parameter(name=“convertTo”, value=“EUR”) }
)
})


  • 위의 설정은 import문 바로 아래 또는 class정의 부분 위나, 별도의 자바 파일, package-info.java 같은 클래스에 둔다.(2.2.1 참조)


  • 다음과 같이 참조해서 사용할 수 있다.


@org.hibernate.annotations.Type(type = “monetary_amount_eur”)
@org.hibernate.annotations.Columns({
@Column(name = “BID_AMOUNT”),
@Column(name = “BID_AMOUNT_CUR”)
})
private MonetaryAmount bidAmount;

CompositeUserType 만들기

클래스 구현하기



public class MonetaryAmountCompositeUserType implements CompositeUserType {

public String[] getPropertyNames() {
return new String[] { “amount”, “currency” };
}

public Type[] getPropertyTypes() {
return new Type[] { Hibernate.BIG_DECIMAL, Hibernate.CURRENCY };
}

public Object getPropertyValue(Object component, int property)
throws HibernateException {
MonetaryAmount monetaryAmount = (MonetaryAmount) component;
if (property == 0)
return monetaryAmount.getAmount();
else
return monetaryAmount.getCurrency();
}

public Object nullSafeGet(ResultSet resultSet, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
BigDecimal value = resultSet.getBigDecimal(names[0]);
if (resultSet.wasNull())
return null;
Currency currency = Currency.getInstance(resultSet.getString(names[1]));
return new MonetaryAmount(value, currency);
}

public void nullSafeSet(PreparedStatement statement, Object value,
int index, SessionImplementor arg3) throws HibernateException,
SQLException {
if (value == null) {
statement.setNull(index, Hibernate.BIG_DECIMAL.sqlType());
statement.setNull(index + 1, Hibernate.CURRENCY.sqlType());
} else {
MonetaryAmount amount = (MonetaryAmount) value;
String currencyCode = amount.getCurrency().getCurrencyCode();
statement.setBigDecimal(index, amount.getAmount());
statement.setString(index + 1, currencyCode);
}
}

public void setPropertyValue(Object arg0, int arg1, Object arg2)
throws HibernateException {
throw new UnsupportedOperationException(“Immutable MonetaryAmount!”);
}

}



  • CompositeUserType 인터페이스를 구현한다.


  • nullSafeSet()은 ResultSet에 담겨있는 두 개의 값을 Monetary 객체의 속성값으로 변환하면 된다.


  • nullSafeSet()은 객체가 가진 두 개의 값을 DB에 저장하도록 statement를 수정한다.


  • getPropertyNames()를 사용해서 Value Type의 속성들을 알려준다.


  • getPropertyValue()를 사용해서 Value Type의 각각의 속성이 가진 값을 알려준다.


  • setPropertyValue()를 사용해서 Value Type의 속성에 값을 설정한다.

맵핑하기



@org.hibernate.annotations.Type(
type = “persistence.MonetaryAmountUserType”
)
@org.hibernate.annotations.Columns(columns = {
@Column(name=“INITIAL_PRICE”),
@Column(name=“INITIAL_PRICE_CURRENCY”, length = 2)
})
private MonetaryAmount initialPrice;


  • 다음과 같이 쿼리를 작성할 수 있다.


from Item i
where i.initialPrice.amount > 100.0
and i.initialPrice.currency = ‘AUD’

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을 사용하면 된다.