表现
使用Timestamp类型的字段作为乐观锁的版本号,在一个事务中更新两次(save,get,update)之后,抛出OptimisticLockingFailureException异常;
解决历程
在排除了配置、事务等的原因之后,实在没有办法,引入log4jdbc,查看真正执行的SQL语句;
发现的问题是,应用中更新的时候Timestamp的值与数据库中的值会相差一秒钟,版本号不一致导致Hibernate抛出OptimisticLockingFailureException异常;
Debug查看Timestamp字段的值,发现毫秒的部分是有值的,想到MySql的Timestamp的值是精确到秒的,基本可以确认是这里的问题导致的时间不一致;
再次Debug意外发现,Hibernate在取Timestamp字段的值的时候,会将毫秒部分会四舍五入到秒;
于是查看Hibernate的文档,找一下是否有格式化时间的方案,没有找到,却发现了下面的注解
@Source
The @Source annotation is used in conjunction with a @Version timestamp entity attribute indicating the SourceType of the timestamp value.
The SourceType offers two options:
DB
Get the timestamp from the database.
VM
Get the timestamp from the current JVM.
突然想到,数据库中的Timestamp字段是自动更新的;Hibernate默认在应用中会生成基于JVM的时间值给Timestamp字段赋值,肯定是这两个时间不一致导致的;
@Version
@Column(columnDefinition="timestamp")
@Source(value = SourceType.DB)
private Timestamp ts;
指定了Source之后,测试,发现应用中Timestamp字段的值的毫秒部分都是0了,测试通过;
原因
数据库中的Timestamp字段是自动更新的;Hibernate默认在应用中会生成基于JVM的时间值给Timestamp字段赋值,肯定是这两个时间格式不一致导致的时间值不一致,从而导致了更新的时候比较的时候,Hibernate报OptimisticLockingFailureException异常;
解决方法
使用Source注解指定时间戳的值在数据库端生成;
@Version
@Column(columnDefinition="timestamp")
@Source(value = SourceType.DB)
private Timestamp ts;
附
<dependency>
<groupId>com.googlecode.log4jdbc</groupId>
<artifactId>log4jdbc</artifactId>
<version>${log4jdbc.version}</version>
</dependency>