Hibernate 的那些坑

来源:互联网 发布:斗鱼主播qqq的淘宝店是 编辑:程序博客网 时间:2024/05/01 18:19

昨天下午,同事在开发的过程中遇到一个奇怪的问题:在控制器方法中查询出了一个对象,然后把这个对象传递到 service 层中,修改这个对象最后保存,但保存之后在数据库中相应的记录并没有发生变化,同时也没有抛出异常,真是日了狗了。

我喜欢找 bug ,让同事把他的代码打了个补丁发给我,我在自己的机器上解决这个问题。应用补丁之后,这个问题在我这儿可以稳定地重现。

我起初怀疑是不是 VO 对应的 get/set 方法生成的有问题,但我在其他地方的类中是可以修改这个对象并成功地保存到数据库中的,所以可以排除是 VO 类的 get/set 方法的问题,如果是它有问题,在其他地方保存这个对象也会有问题。很奇怪,同事写的这几个类和我在其他地方做测试写的类有什么不同之处?修改对象并保存,经常这样搞,没出现过什么问题啊。太奇怪了,但我相信一切问题都是有原因的,只不过是暂时没找到问题的所在而已。继续埋头找问题的所在,突然脑袋灵光一闪,会不会与 Hibernate 的 open session in view 特性有关?因为之前也踩过这个坑。
在这里先简单地介绍一下什么是 open session in view 。

open session in view

用过 Hibernate 的同学肯定知道 Hibernate 的懒加载异常,open session in view 就是为了解决这个问题而存在的。启用了 open session in view 之后,修改数据库的相关记录并不会马上就写入到数据库中而是在一次请求结束之后才会把相应的记录写入到数据库中。什么是一次请求?可以简单地理解为从服务器接收到一个 http 请求到响应这个请求的全部过程。什么是一次请求结束?可以简单地理解为执行完控制器中的方法后一次请求就结束了(严格来说是不正确的)。

再回到上面的话题吧,在 web.xml 中配置了 open session in view 过滤器,它会拦截以 *.do 结尾的所有 URL 请求。我看了下,同事的那个请求地址确实是以 .do 结尾的。我把 .do 后缀去掉之后,奇迹发生了,那个被更新的对象成功地保存到了数据库中。

spring 事务与 open session in view

给 service 类或者是类上的方法加上一个 @Transactional 注解后,事务就会生效。在 service 类上加上注解之后,该类的每个方法都会应用这个注解。当然,也可以分别单独地给每一个方法加上注解。在这里并不讨论,事务的隔离级别和传播行为。

如果某次请求应用了 open session in view 同时这次请求访问的 service 没有应用事务的话,对数据库的修改是在这次请求结束之后才会把相关的所有改动一次性地提交到数据库中的。在不加事务的前提下,注意,得出这些结论的前提是不加事务,后来通过反复试验,得出了以下几个结论:

  • 在没有应用事务,同时也没有应用 open session in view 时 add/update(没有尝试 delete ,项目中都是假删,和 update 是一样的效果,大家感兴趣的话可以做个 delete 的试验) 都会立即生效,即立即写入数据库。
  • 在没有应用事务,同时应用了 open session in view 时 add 一个对象会立即写入数据库。假如有个 jdbcDao 负责与数据库直接打交道,其他的 service 再通过调用 jdbcDao 来实现增、删、改、查的功能。立即写入是指 service 中的方法调用了 jdbcDao 的相应的方法后,jdbcDao 的这个方法执行完后就会把对象保存到数据库中而不是指 service 中的相应方法执行完之后才保存到数据库中。有意思的是 add 一个对象时调用了 jdbcDao.add(); 方法后会立即把这个对象保存到数据库中,接下来如果再对这个对象做了修改,等到这次请求结束时也会把修改的内容同步保存到数据库中。
  • 在没用应用事务,同时应用了 open seesion in view 时 update 一个对象不会立即写入数据库,而是等一次请求结束后才会写入数据库。

由此可以看出在没有应用事务时不管有没有应用 open session in view ,add 一个对象都会立即写入数据库,而 update 一个对象就不同了,update 会与有没有应用 open session in view 有关系。

为什么会是这样呢?因为 add 一个对象只能 add 一次,但可以 update 一个对象多次。所以 add 会立即写入数据库。应用了 open session in view 之后 update 一个对象哪怕在 service 的方法中多次显式地调用 jdbcDao.update(); update 多次,实际上在这次请求结束后才会把这个对象同步保存到数据库中,只会真正地 update 这个对象一次。所以,在一个控制器的方法中查询出一个对象,然后又把这个对象传递到 service 的方法中对这个对象做了修改并调用 jdbcDao.update(); 显式地保存。service 中的方法调用完之后如果又对这个对象做了修改,这次请求结束之后,Hibernate 才会真正地把这个对象保存到数据库中。所以在控制器中对这个对象做的修改也会被同步保存到数据库中。

在昨天的试验过程中貌似发现了 Hibernate 的一个 bug 。在没有应用事务同时应用了 open session in view 同时在控制器的方法中使用了客户端重定向之后本来应该被 update 的对象却没有被 update 到数据库中同时也没有报错。其他条件不变,只修改控制器中相应方法的最后一行代码 return “redirect:xxxxx”; 重定向语句,把其替换为 “return yyyyy”; 突然就好了,被 update 的对象也能同步保存到数据库中。所以我认为这应该是 Hibernate 的一个 bug ,只不过这个 bug 发生的几率小,不容易出现,要重现这个 bug 需要同时满足 3 个条件:

  • 没有应用事务
  • 应用了 open session in view
  • 使用了客户端重定向

结论

看似再诡异的问题也肯定有其发生的原因,揭开神秘地面纱之后发现其实也没什么不可思议的。

0 0