[Hibernate求助]如何构造自己的映射类型?

我在数据库中存储的字段是TINYINT类型,想映射成java类中的Boolean(或者boolean)对象。我看了hibernate文档的4.2.4. Custom value types节,并自己写了扩展了UserType类,如下:


/**
* 从java对象Boolean到sql中的TINYINT的映射
*/

public class BooleanMappingType implements UserType
{
private static final int [] TYPE = {Types.TINYINT};

public int[] sqlTypes()
{
return TYPE;
}

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

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

public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws net.sf.hibernate.HibernateException, java.sql.SQLException
{
Byte b = (Byte)Hibernate.BYTE.nullSafeGet(rs, names[0]);
if( b == null)
return Boolean.FALSE;
if(b.intValue() != 0)
return Boolean.TRUE;
return Boolean.FALSE;
}

public void nullSafeSet(PreparedStatement psmt, Object value, int index) throws net.sf.hibernate.HibernateException, java.sql.SQLException
{
Boolean b = (value == null) ? Boolean.FALSE : (Boolean)value;
Hibernate.BYTE.nullSafeSet(psmt, b, index);
}

public Object deepCopy(Object x) throws net.sf.hibernate.HibernateException
{
if(x.equals(Boolean.TRUE))
return new Boolean(true);
if(x.equals(Boolean.FALSE))
return new Boolean(false);
return null;
}

public boolean isMutable()
{
return true;
}
}

还没有测试,因为有一点疑问,这个类中,并没有什么方法表明数据库中存储的TINYINT值怎么转换成对应的Boolean对象,我自然是想数据库中存储1时转换成TRUE,存储0时转换成FALSE。但在这个类中并没有什么方法申明啊。不知道如何解决这个问题。
另外,如果想把TINYINT列映射成java的原始类型boolean,又该如何解决。
想了很久,一直没有弄出来,请大家帮忙看看,谢谢!

如果guty、robbin 、Jevang这些高人能回答就好了~

不知道你用的是哪个数据库?

有些数据库里面本来就没有boolean型的字段,在这样的数据库里面往往就是用整数类型的0表示false,1表示true,比如Oracle数据库里面,如果在某数值字段里面填0,然后PreparedStatement取出来 getBoolean(1,fieldName),就是当做boolean取的,取出来就是false。

这种转换应该是由JDBC来完成的,和Hibernate无关。不过Hibernate也可以做,请看hibernate.properties里面有一行:

hibernate.query.substitutions true 1, false 0, yes 'Y', no 'N'

你自己写程序应该不用去管它了。

说的再明确一点:

很多数据库都没有boolean字段类型,往往用整数类型替代,1就代表true,0就代表false。这种把Java中的true/false值 转换为数据库中1/0是由厂商提供的JDBC驱动类库来完成的,该过程对程序员是透明的。你指管把true/false往数据库里面插,实际上插入的就是1/0。

当然Hibernate也可以完成这样的转换,需要你在Hibernate配置文件里面定义,默认的hibernate.properties已经定义好了该转换规则。

总之像你那种情况,你就直接在Java里面当做boolean来运算就对了,什么多余的工作都不需要做。

robbin你好,谢谢你的回答!我用的是mysql。

上面那个问题可以那样解决。但是如果我把自定义映射的类型再扩展一下,不局限于boolean的映射,而扩展到一个字段和任意一个java对象的映射。
例如User表,有一个字段"CREATE_TIME"类型是VARCHAR(15),对应User类的属性createTime的类型是java.util.Date。数据库中存储的是Date转换成长整形再补零到15位长度的字符串(参考jive的时间字段处理)。就是说,每次从数据库到类字段都需要用一个Util类进行转换。这样的情况如何处理?我看hibernate的文档是扩展CompositeUserType或者UserType接口,但不知道是这个接口如何调用我的Util转化方法。而且,这样作对象的比较是否需要重载。(当然,把数据库字段类型改为DATETIME就很容易解决这个问题了,但我想了解清楚这种hibernate自定义映射转换的方法)
或者说,这样的转化是在User类的getter,setter方法中作?


转换的工作是在nullSafeGet和nullSafeSet这两个方法中进行的。

nullSafeGet是从数据库取出结果集ResultSet,然后把JDBC Type转换为你自定义的类型。

nullSafeSet是把你自定义的类型转换为JDBC Type,然后设到PreparedStatement里面去。

你上面那个程序中的nullSafeSet方法转换中有错误,而你写的nullSafeGet倒是转换对了。

Hibernate应该是在执行PreparedStatement前set自定义类型的参数的时候调用自定义类型的nullSafeSet;取出ResultSet后,get自定义类型值的时候调用自定义类型的nullSafeGet。

哦 set方法确实错了。


public void nullSafeSet(PreparedStatement psmt, Object value, int index) throws net.sf.hibernate.HibernateException, java.sql.SQLException
{
Byte b = (value == null) ? new Byte((byte)0) : (Byte)value ;
Hibernate.BYTE.nullSafeSet(psmt, b, index);
}

这样差不多了吧。

robbin这么详细的讲解,转换的原理基本上明白了 谢谢:)

如果robbin有空能再写一些介绍Hibernate的中文资料,或者和guty合出一本关于java持久层甚至j2ee框架技术的书,畅谈你们宝贵的经验就好了~ 期待哦,呵呵~0)

也不对。

public void nullSafeSet(...) ... {
Byte b = null;
if (value == null || !((Boolean) value).booleanValue()) {
b = new Byte((byte)0);
} else {
b= new Byte((byte)1);
}
Hibernate.BYTE.nullSafeSet(psmt, b, index);
}

你没有把Boolean型的value转换到Byte上去。

我写了一篇Java字符集的文章,包括字符集处理,中文乱码问题,繁简转换和多国语言支持开发等内容,还没有写完,不知道大家有没有兴趣。

Oh My god,忘了参数value是Boolean型的了,转换就是在这里作啊!

字符集的文章网上七零八碎的有很多,但少有提纲挈领的大作,或者里面有些理论过时了。想必robbin的大作必定不同反响。如果结合google的国际化思路,从理论结合实际,提出java的国际化的优秀方案,一定很棒!期待中......~*)

hibernate的稍微复杂一点的例子,不知道robbin有没有,谢谢赐教!

嗯,是啊,因为自己也曾经被中文问题困扰过。网络上的方法虽然多,但是很多都是治标不治本的办法。比如说代码内转换,Servlet Filter转换,甚至还有更改Tomcat源码等等,不能放之四海而皆准。

我从是操作系统字符集,浏览器字符集,数据库字符集和JRE的字符集分析的,关键是要搞清楚JRE中的UTF-8编码,Unicode编码,ASCII编码,Latin-1编码,GBK编码,Big5编码字符集之间如何进行转换,以及操作系统,浏览器,数据库对这些字符集的支持,核心就是UTF-8编码。明白了这个道理,就很容易做到开发的程序部署在任何操作系统环境,任何数据库环境和任何应用服务器环境下都能够不做任何修改正常显示中文。多国语言支持也是同样的道理。

说到Google,其实在Java中你自己开发一个Web应用很容易做到这一点的,根据不同国家的浏览器访问,动态显示不同的语言,简单步骤如下:

秘诀就是两点:

1、所有HTML/JSP页面全部采用UTF-8编码

2、客户端浏览器完全支持UTF-8编码

步骤:
1、首先把所有的HTML/JSP的ContentType都设为UTF-8

2、然后对于JSP程序中的非ASCII码提示信息都不应该写在程序里面,都应该放在application.properties里面统一管理(采用Struts)

3、对HTML用native2ascii工具统一做一次处理,把HTML中的非ASCII码都转换为ASCII码表示的Unicode编码。

4、针对不同的语言,写不同的application.properties,比如说简体中文是
application_zh_CN.properties,繁体中文是application_zh_TW.properties这样,然后对这些配置信息文件同样用native2ascii工具处理一次,把非ASCII码统统转为ASCII码表示的Unicode编码。

5、在Servlet的request.getCharacterEncoding()获得客户端的操作系统默认编码,然后set到Struts的HTTPSession的Locale中。

OK!现在不同的客户访问,就会显示不同的语言版本了。你可以看看此时你的浏览器的字符集,就是UTF-8。现在你的网站和Google一样了,嘿嘿,其实你有心的话,看看你的浏览器访问Google的时候是什么字符集吧

切记:所有的HTML/JSP都要设为UTF-8编码,所有的文件中的非ASCII码字符都要用native2ascii工具转为用ASCII表示的Unicode编码。

补充一点:

不用struts也可以,只不过自己要用ResourceBundle来管理JSP中的提示信息,比较麻烦。

使用了Hibernate的Project在SourceForge上有好几个,搜索一下就知道了。

其实把struts的国际化资源处理部分抽取出来也不难。界面上根据浏览器的accept-language显示不同的资源这个不难作到。

有一些难点和容易出错的地方是:
1.要确定request.getParameter()得到用户输入的字符集是什么,这个好像跟应用服务器的处理有关吧。现在也可以通过request.setCharacterEncoding()或者作一个filter来设置。不知道robbin的意见是什么,浏览器的ContentType设为UTF-8 ,那么request得到的字符集也应该是UTF-8吗?

2.要保持request得到的字符集编码和服务器端程序处理的编码一致。

3.要保持存入数据库的编码和取出时的编码一致。
对数据库编码我一直很困惑。现在用的mysql不知道支不支持utf-8的编码,反正一般都在jdbc连接url加上参数?useUnicode=true&characterEncoding=GBK,说实话,一直不太明白这个参数的意义(似乎不支持utf的存储)。那是不是SQL server,DB2,Oracle这些都用utf-8存储?robbin的文章能详细介绍当前主流数据库编码的问题一定不错。

嘿嘿,这些问题我的文章都准备讲到 :)

>1.要确定request.getParameter()得到用户输入的字符集是什么,这个好像跟应用服务器的处理有关吧
>2.要保持request得到的字符集编码和服务器端程序处理的编码一致

这就是我强调为什么要把HTML的contentType也设为UTF-8的原因所在了。

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

因为浏览器也支持UTF-8,所以当HTML设为UTF-8之后,当你在HTML的form里面填写了中文之后,进行提交,浏览器会把中文转成UTF-8提交给服务器的。

而服务器拿到UTF-8编码的中文字符串以后,是不会做任何转换的(这是因为UTF-8编码是经过特别的设计的,具有自识别能力,在二进制上不可能和任何非ASCII码字符集的编码重叠),直接进行处理。

因而只要你把所有的JSP/HTML都设为UTF-8,就可以做到和应用服务器环境,甚至操作系统环境无关,request得到的编码总是UTF-8编码。

总之秘诀就是两点:
1、HTML/JSP都是UTF-8
2、浏览器支持UTF-8编码

在纯web应用中,字符串无非就是在浏览器和App Server之间交换,Java内部统一用UTF-8处理字符串,而你再把浏览器环境设为纯UTF-8环境,就可以保证字符串在浏览器和App Server之间不管怎么处理,自始至终都是以UTF-8的形态存在的,那么当然不会出现中文乱码问题,国际化问题也可迎刃而解。而在UTF-8和GBK字符集编码之间进行转换的工作其实是由浏览器来做的,而不是App Server来做,所以当然可以做到App Server无关性了。

>3.要保持存入数据库的编码和取出时的编码一致

Java内部是使用UTF-8编码的,在Java内存中和数据库中进行转换的规则取决于JDBC驱动的设定。一般来说有两种情形

1)以Oracle为代表的JDBC驱动类
这类JDBC驱动的转换规则是:数据库默认字符集是什么,就按照什么转换。比如说如果Oracle数据库默认字符集是GBK,那么写数据库的时候,JDBC驱动会把字符串由UTF-8转换为GBK,读数据库亦然。
这类驱动可以说省心省力,只要DBA安装数据库的时候把字符集设对,就不存在乱码问题。但是最怕DBA把字符集设为Latin-1(Latin-1就是ISO8859_1),由于数据库支持双字节,就算是Latin-1的字符集,数据库也能够正常处理中文,但是此时JDBC可不管这一套,肯定是把中文字符串的UTF-8编码往Latin-1上转,这样肯定完蛋,所以请扁DBA吧。

2)以MySQL的mmjdbc为代表的JDBC驱动类
这类驱动的转换规则是:不管数据库默认字符集是什么,总是默认的往Latin-1上转,所以中文肯定乱码。所幸的是这类驱动一定会提供一个连接参数,让你自己设定转换规则:
characterEncoding=GBK
这就声明了把UTF-8字符串往GBK上转换。所以就解决了中文乱码问题。

如果数据库就支持UTF-8编码,那么把数据库默认编码设为UTF-8,可以做到读写数据库也支持多国语言编码,但是有一个麻烦的地方是,数据库的客户端程序未必如浏览器那样能够自动进行UTF-8和操作系统字符集编码的转换,所以当你用数据库自带的客户端连接数据库的时候,可能会看到数据库里面全是乱码。

摘录

这就是我强调为什么要把HTML的contentType也设为UTF-8的原因所在了。
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
因为浏览器也支持UTF-8,所以当HTML设为UTF-8之后,当你在HTML的form里面填写了中文之后,进行提交,浏览器会把中文转成UTF-8提交给服务器的。

妙!我看到goolge页面也有<meta http-equiv="content-type" content="text/html; charset=UTF-8">的申明了,以前老以为这句没用,现在总算明白了。

总结一下我的理解:
1. 浏览器能显示utf-8的字符集,因为浏览器能自动把utf-8转换成系统或浏览器默认的字符集显示。所以java程序可放心把utf-8的内容送到浏览器端。

2. 申明了html的charset后,浏览器负责把用户输入自动转化成utf-8的编码再发送到服务器,(难怪在状态栏看到google的q参数是乱码)能保证从客户端传来的都是uft-8编码,所以不用作任何request的编码设置。

3. 存储到数据库前,jdbc驱动会根据本身特性进行转化,mmmysql驱动根据characterEncoding=GBK参数转成GBK存储。(有个疑问,我看mysql数据库当前采用的字符集是Latin-1,它支持的字符集包括GBK,就是说把GBK编码的字符送进字符集为Latin-1的mysql数据库,mysql会自动处理这样的转换?)

4.jdbc驱动从数据库读出数据会根据characterEncoding=GBK参数知道读入的数据是GBK编码的。


个人觉得:如果不需要多国字符集的支持,可以把浏览器和jsp的charset都设成GBK,GBK已经能满足大部分应用,只有一些不常用的特殊符号(例如欧元符号)不支持。当然为了国际化,utf-8是真正彻底的方案。

这些在纯web的环境中很有效,如果是web程序和GUI程序混合的应用,robbin有什么高见~~期待robbin完整的大作:)