2008年12月27日星期六

Hibernate 的乐观锁@Version和 MySQL 的两个小问题

MySQL的设计者一直没有支持精度到毫秒或者纳秒的Timestamp类型,过去一直只能支持到秒。这虽然不太方便但是也凑合着能用,比如我通过程序代码插入long int也可以。但是Hibernate的实体如果用了@Version在MySQL一个时间字段上做乐观锁,就有问题了。Hibernate比较实体对象是否改变过,是根据@version字段的值,由于MySQL的DATETIME类型不能包含毫秒,纳秒,所以比较的时候会有问题,导致Hibernate认为实体Version不相等,抛HibernateOptimisticLockingFailureException给你。

MySQL 6 才会支持毫秒,所以目前你只能用INT或者BIGINT来保存Version了。
但是就算是你用了INT你可能仍然遇到别的错误
Caused by: java.lang.NullPointerException
at org.hibernate.type.IntegerType.next(IntegerType.java:59)
at org.hibernate.engine.Versioning.increment(Versioning.java:108)
at org.hibernate.event.def.DefaultFlushEntityEventListener.getNextVersion(DefaultFlushEntityEventListener.java:365)
at org.hibernate.event.def.DefaultFlushEntityEventListener.scheduleUpdate(DefaultFlushEntityEventListener.java:257)
at org.hibernate.event.def.DefaultFlushEntityEventListener.onFlushEntity(DefaultFlushEntityEventListener.java:128)

这个错误一看就头大了吧,Hibernate的源代码是这样的
public Object next(Object current, SessionImplementor session) {
return new Integer( ( (Integer) current ).intValue() + 1 );
}
初步分析原因是前面事件通知的时候传入的当前version是空的,具体看这里 http://opensource.atlassian.com/projects/hibernate/browse/HHH-3030

最简单的解决办法是给@version字段在数据库里设置一个默认值,比如0

Opensource意味着你不能发现bug的时候打电话对售后大喊大叫,没办法,要么自己写一个hotfix,要么等官方fix吧。