在本周的文章中,我将展示如何调整Hibernate,以便将任何数据库数据类型与任何Java类型进行相互转换,从而使数据库模型与对象模型脱钩。
这是第3 次后在Hibernate中铁的事实集中series.Other职位包括:
- Hibernate硬事实第1部分
- Hibernate硬事实第2部分
- Hibernate硬事实第3部分 (本文)
- Hibernate硬事实-第4部分
- Hibernate硬事实–第5部分
- Hibernate硬事实-第6部分
- Hibernate事实–第7部分
自定义类型映射
在任何需要持久存储数据的应用程序中,Hibernate都是一项非常强大的资产。 举例来说,我本周受命为遗留数据库生成面向对象的模型。 乍一看似乎很简单。 然后,我发现了一个较大的传统设计缺陷:由于历史原因,日期以YYYYMMDD格式存储为数字。 例如,2009年12月11日是20091211。我不能或不愿意更改数据库,但是,我不想使用Integer
而不是java.util.Date
污染整洁的OO模型。
浏览完Hibernate文档后,我相信它可以以非常简单的方式实现这一目标。
创建自定义类型映射器
第一步也是最大的一步,就是创建一个自定义类型。 此类型不是真正的“类型”,而是知道如何从数据库类型转换为Java类型,反之亦然的映射器。 为此,您所要做的就是创建一个实现org.hibernate.usertype.UserType
的类。 让我们详细了解每种实现的方法。
以下方法给出了在读取过程结束时将返回的类。 因为我想要一个Date
而不是Integer
,所以我自然返回Date
类。
publicClassreturnedClass(){
returnDate.class;
}
下一个方法返回要从中读取的列的类型(在Types
常量中)。 有趣的是,Hibernate允许您映射多个列,因此具有与JPA @Embedded
批注相同的功能。 就我而言,我从单个数字列读取,因此我应该返回一个填充有Types.INTEGER
的单个对象数组。
publicint[]sqlTypes(){
returnnewint[]{Types.INTEGER};
}
此方法将检查返回的类实例是不可变的(就像任何普通的Java类型保存原始类型和String一样)还是可变的(像其余的一样)。 这非常重要,因为如果返回false
,则不会检查使用此自定义类型的字段以查看是否应该执行更新。 当然,在所有情况下(可变或不可变)都将替换该字段。 尽管Java API中存在很大争议,但Date
是可变的,因此该方法应返回true
。
publicbooleanisMutable(){
returntrue;
}
我无法猜测如何使用以下方法,但API指出:
返回持久状态的深层副本,在实体和集合处停止。 不必复制不可变的对象或null值,在这种情况下,只需返回参数即可。
由于我们刚刚说过Date
实例是可变的,所以我们不能只返回对象,而必须返回一个克隆:之所以可行,是因为Date
clone()
方法是公共的。
publicObjectdeepCopy(Objectvalue)throwsHibernateException{
return((Date)value).clone();
}
接下来的两种方法可以完成实际工作,分别从数据库读取和读取数据库。 请注意,API如何公开要读取的ResultSet
对象和要写入的PreparedStatement
对象。
publicObjectnullSafeGet(ResultSetrs,String[]names,Objectowner)
throwsHibernateException,SQLException{
Dateresult=null;
if(!rs.wasNull()){
Integerinteger=rs.getInt(names[0]);
if(integer!=null){
try{
result=newSimpleDateFormat("yyyyMMdd").parse(String.valueOf(integer));
}catch(ParseExceptione){
thrownewHibernateException(e);
}
}
}
returnresult;
}
publicvoidnullSafeSet(PreparedStatementstatement,Objectvalue,intindex)
throwsHibernateException,SQLException{
if(value==null){
statement.setNull(index,Types.INTEGER);
}else{
Integerinteger=Integer.valueOf(newSimpleDateFormat("yyyyMMdd").format((String)value));
statement.setInt(index,integer);
}
}
从持久性的角度来看 ,接下来的两个方法是equals()
和hasCode()
的实现 。
publicinthashCode(Objectx)throwsHibernateException{
returnx==null?0:x.hashCode();
}
publicbooleanequals(Objectx,Objecty)throwsHibernateException{
if(x==null){
returny==null;
}
returnx.equals(y);
}
对于equals()
,由于Date
是可变的,因此我们不能仅检查对象是否相等,因为同一对象可能已更改。
replace()
方法用于合并目的。 这再简单不过了。
publicObjectreplace(Objectoriginal,Objecttarget,Objectowner)throwsHibernateException{
Ownero=(Owner)owner;
o.setDate(target);
return((Date)original).clone();
}
我对replace()
方法的实现不可重用:应同时知道拥有类型和setter方法的名称,这使得重用自定义类型有些困难。 如果我想重用它,该方法的主体将需要使用lang.reflect
包,并对所使用的方法名称进行猜测。 因此,用于创建可重用用户类型的算法将遵循以下原则:
- 列出所有设置方法,并以目标类作为参数。
- 如果没有方法匹配,则抛出错误
- 如果单个方法匹配,请使用目标参数进行调用
- 如果有多个方法匹配,请调用关联的getter以检查哪个方法返回了原始对象
接下来的两种方法在缓存过程中分别用于序列化和反序列化。 由于Date
实例是可序列化的,因此易于实现。
publicSerializabledisassemble(Objectvalue)throwsHibernateException
return(Date)((Date)value).clone();
}
publicObjectassemble(Serializablecached,Objectowner)throwsHibernateException{
return((Date)cached).clone();
}
在实体上声明类型
实施自定义UserType
,您需要使该实体可以访问它。
@TypeDef(name="dateInt",typeClass=DateIntegerType.class)
publicclassOwner{
...
}
使用类型
最后一步是对字段进行注释。
@TypeDef(name="dateInt",typeClass=DateIntegerType.class)
publicclassOwner{
privateDatedate;
@Type(type="dateInt")
publicDategetDate(){
returndate;
}
publicvoidsetDate(Datedate){
this.date=date;
}
}
您可以在此处下载本文的资源。