创建CompositeUserType
在XML映射文件和注解映射中,现在引用被定义的类型名称,而不是定制类型完全限定的类名:
public class MonetaryAmountCompositeUserType implements CompositeUserType{
@SuppressWarnings("unchecked")
public Class returnedClass() {
return MonetaryAmount.class;
}
public boolean isMutable() {
return false;
}
//创建值的快照
public Object deepCopy(Object value) throws HibernateException {
return value;
}
//以序列化的形式保存信息的高速缓存
public Serializable disassemble(Object value, SessionImplementor session)
throws HibernateException {
return (Serializable)value;
}
//高速缓存的数据转变为MonetaryAmount的一个实例
public Object assemble(Serializable cached, SessionImplementor session,
Object owner) throws HibernateException {
return cached;
}
//处理脱管对象状态的合并
public Object replace(Object original, Object target,
SessionImplementor session, Object owner) throws HibernateException {
return original;
}
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y)
return true;
if (null == x || null == y)
return false;
return x.equals(y);
}
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
//从JDBC的ResultSet获取属性值
public Object nullSafeGet(ResultSet rs, String[] names,
SessionImplementor session, Object owner)
throws HibernateException, SQLException {
BigDecimal value = rs.getBigDecimal(names[0]);
if(rs.wasNull()) return null;
Currency currency = Currency.getInstance(rs.getString(names[1]));
return new MonetaryAmount(value,currency);
}
//把属性值写到JDBC的PreparedStatement
public void nullSafeSet(PreparedStatement st, Object value, int index,
SessionImplementor session) throws HibernateException, SQLException {
if(null == value){
st.setNull(index, BigDecimalType.INSTANCE.sqlType());
st.setNull(index+1, CurrencyType.INSTANCE.sqlType());
}else{
MonetaryAmount amount = (MonetaryAmount)value;
String currencyCode = amount.getCurrency().getCurrencyCode();
st.setBigDecimal(index,amount.getAmount());
st.setString(index+1, currencyCode);
}
}
//公开值类型属性
public String[] getPropertyNames() {
return new String[] { "amount", "currency" };
}
//属性类型
public Type[] getPropertyTypes() {
return new Type[]{BigDecimalType.INSTANCE,CurrencyType.INSTANCE};
}
//返回MonetaryAmount的单个属性的值
public Object getPropertyValue(Object component, int property)
throws HibernateException {
MonetaryAmount monetaryAmount = (MonetaryAmount)component;
if(property == 0){
return monetaryAmount.getAmount();
}else{
return monetaryAmount.getCurrency();
}
}
public void setPropertyValue(Object component, int property, Object value)
throws HibernateException {
throw new UnsupportedOperationException("Immutable MonetaryAmount!");
}
}
initialPrice属性现在映射到两个列,因此在映射文件中要对两者都进行声明。第一列保存值;第二列保存MonetaryAmount的货币类型:
<property name="initialPrice"
type="persistence.MonetaryAmountCompositeUserType">
<column name="INITIAL_PRICE"/>
<column name="INITIAL_PRICE_CURRENCY"/>
</property>
如果通过注解映射Item,就必须给这个属性声明几个列。无法多次使用javax.persistence.Column注解,因此需要一个特定于Hibernate的新注解:
@org.hibernate.annotations.Type(
type = "persistence.MonetaryAmountUserType"
)
@org.hibernate.annotations.Columns(columns = {
@Column(name = "INITIAL_PRICE"),
@cOLUMN(NAME = "INITIAL_PRICE_CURRENCY", length = 2)
})
在Hibernate查询中,现在可以引用定制类型的amount和currency属性,即使它们没有作为单独的属性出现在映射文件的任何地方:
from Item i
where i.initialPrice.amount > 100.0
and i.initiaPrice.currency = 'AUD'
参数化定制类型
假设你再次面临最初的问题:当把金额保存到数据库中时把它转化为一种不同的货币类型。这些问题经常比一般的转化更为微妙;例如,可以在一些表中保存美元,而在其他表中保存欧元。你仍然想要给它编写单个定制的映射类型,使它可以进行任意的转化。如果把ParameterizedType接口添加到UserType或者CompositeUserType类,这是可能的:public class MonetaryAmountConversionType implements UserType,
ParameterizedType {
private Currency convertTo;
public void setParameterValues(Properties parameters) {
this.convertTo = Currency.getInstance(parameters
.getProperty("convertTo"));
}
public int[] sqlTypes() {
return new int[] { BigDecimalType.INSTANCE.sqlType(),
StringType.INSTANCE.sqlType() };
}
@SuppressWarnings("unchecked")
public Class returnedClass() {
return MonetaryAmount.class;
}
public boolean isMutable() {
return false;
}
// 创建值的快照
public Object deepCopy(Object value) throws HibernateException {
return value;
}
// 以序列化的形式保存信息的高速缓存
public Serializable disassemble(Object value, SessionImplementor session)
throws HibernateException {
return (Serializable) value;
}
// 高速缓存的数据转变为MonetaryAmount的一个实例
public Serializable disassemble(Object value) throws HibernateException {
return (Serializable) value;
}
public Object assemble(Serializable cached, Object owner)
throws HibernateException {
return cached;
}
// 处理脱管对象状态的合并
public Object replace(Object original, Object target, Object owner)
throws HibernateException {
return original;
}
public boolean equals(Object x, Object y) throws HibernateException {
if (x == y)
return true;
if (null == x || null == y)
return false;
return x.equals(y);
}
public int hashCode(Object x) throws HibernateException {
return x.hashCode();
}
public Object nullSafeGet(ResultSet rs, String[] names, Object owner)
throws HibernateException, SQLException {
BigDecimal value = rs.getBigDecimal(names[0]);
if(rs.wasNull()) return null;
Currency currency = Currency.getInstance(rs.getString(names[1]));
return new MonetaryAmount(value, currency);
}
public void nullSafeSet(PreparedStatement st, Object value, int index)
throws HibernateException, SQLException {
if(null == value){
st.setNull(index, BigDecimalType.INSTANCE.sqlType());
st.setNull(index+1, CurrencyType.INSTANCE.sqlType());
}else{
MonetaryAmount amount = (MonetaryAmount)value;
st.setBigDecimal(index, amount.convert(convertTo));
st.setString(index+1, amount.getCurrency().getCurrencyCode());
}
}
}
应用定制映射类型时,现在必须在映射文件中设置配置参数。一种简单的解决方案是属性中嵌套着的<type>映射:
<property name="initialPrice">
<column name="INITIAL_PRICE"/>
<column name="INITIAL_PRICE_CUR"/>
<type name="persistence.MonetaryAmountConversionType">
<param name="convertTo">USD</param>
</type>
</property>
然而,如果在你的领域模型中有许多个货币金额时,这样很不方便,并且需要复制。一种更好的策略是使用类型的单个定义,包括所有参数,用一个可以在所有映射中重用的唯一名称。通过单个的<typedef>元素(你也可以不通过参数使用它)完成这一占为:
<typedef name="monetary_amount_usd" class="persistence.MonetaryAmountConversionType">
<param name="convertTo">USD</param>
</typedef>
<typedef name="monetary_amount_eur" class="persistence.MonetaryAmountConversionType">
<param name="convertTo">EUR</param>
</typedef>
这里介绍的是,把带有一些参数的定制映射类型绑定到名称monetary_amount_usd和monetary_amount_eur上。这个定义可以放在映射文件中的任何位置;它是<hibernate-mapping>的一个子元素。利用Hibernate扩展,可以在注解中定义带有参数定制类型:
@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")}
)
})
这个注解元数据也是全局的,因此可以放在任何Java类声明之外或者在一个单独的文件package-info.java中。
在XML映射文件和注解映射中,现在引用被定义的类型名称,而不是定制类型完全限定的类名:
<property name = "initialPrice"
type = "monetary_amount_usd">
<column name="INITIAL_PRICE"/>
<column name="INITIAL_PRICE_CUR"/>
</property>
@org.hibernate.annotations.Type(type = "monetary_amount_eur")
@org.hibernate.annotations.Columns({
@Column(name = "BID_AMOUNT"),
@Column(name = "BID_AMOUNT_CUR")
})
private MonetaryAmount bidAmount;