当前位置: 代码迷 >> SQL >> 难经五:当IBatis、Hibernate混合事务时,注意你的SQL
  详细解决方案

难经五:当IBatis、Hibernate混合事务时,注意你的SQL

热度:100   发布时间:2016-05-05 13:11:05.0
难经5:当IBatis、Hibernate混合事务时,注意你的SQL

【问题】

现在,在开发所谓多层JavaEE应用时,数据持久层总是不可或缺的,而“全自动型”的ORM--Hibernate,和“全手动型”的SqlMap--IBatis,就相当于是数据持久层的哥俩好。这兄弟俩各有优势,也各有缺点,总体来说,Hibernate很好很强大,但细节多,学习曲线陡峭;而IBatis很快很简单,但不能跨数据库,要自己写很多SQL,包括分页等。因此,在不少项目里,我们同时使用了这两个组件,并使用Spring来统一管理事务。

?

最近参与的一个项目,就是使用了这种方式,但是却遇到了一个意向不到的问题:有一个包含IBatisDAO和HibernateDAO操作的Service方法,执行过程中总是抛空指针异常;而且,这个异常只在Sybase数据库上测试时抛出,而用MySQL则没有问题;更奇怪的是,单独测试IBatisDAO和HibernateDAO时,不论什么数据库,均没有问题;

?

【探幽】

空指针异常抛出的位置,是在用Hibernate执行一个P对象的查询后,再查询P懒加载的一个关联对象集后抛出的。

?

反应一:这个问题,让我想到了之前碰到过的一个Sybase的问题(见难经一:http://liuu.iteye.com/admin/blogs/288660),难道又是Sybase跟Hibernate有什么不兼容?但是,由于单独测试DAO都没有问题,我否定了这个可能。

?

反应二:?是不是IBatis和Hibernate的事务混和出的问题?但是只在Sybase下碰到,就很奇怪了(没办法,这是项目的生产环境),该死的Sybase到底在闹什么别扭呢。

?

IBatis和Hibernate的混合使用,其实配置起来比较简单,网上也有很多示例,大致如下三步:只要在Spring中公用一个数据源,并配置使用同一个DataSource事务管理器,再配置允许Hibernate和IBatis使用外部的事务管理即可。再次检查这些配置,也没有看出什么异常来,而空指针异常也每次顽固的抛出,绝对重现......

?

打开Hibernate的SQL日志,我在出异常的地方反复调试,看看执行的SQL到底有什么问题:

1、执行对象P的操作查询,OK

2、执行对象P的关联集合属性(包括对象M和C)的查询,OK

3、使用对象M,OK

4、使用C对象是,却发现C依然是null,FAILED

?

问题出现在4,C对象查询后居然为空,难道2查询结果不对?

于是,我将2执行的SQL拷出来用直接连接数据库执行,却发现查询结果正常!

?

问题到底在哪?

?

我郁闷的回过头来再看日志,突然发现,2查询返回的结果只有一条记录!不对啊,刚才查询是有两条的啊,一条对应对象M,另一条对应对象C,如果只返回一条,那么C对象肯定为空了!

?

呼呼,空指针异常的原因找到了,但是,新的问题又来了,为什么Hibernate执行查询2时,只返回一条记录呢。归纳一下现在的情况:

1、这个问题只在Sybase下出现,其他数据库正常

2、HibernateDAO和IBatisDAO单独执行都没有问题

3、在执行Hibernate查询之前,执行过IBatis的SQL

4、事务边界划在Service的方法上,因此,Hibernate和IBatis执行的所有操作,都在同一个连接的同一个事务里

?

结合这四条,我的头脑映出了一条Sybase专用的限制结果集语句:“set rowcount 1”,对,应该就是它,它会限制当前连接的所有查询都只返回不多于一条的记录,而其他的数据库如MySQL之limit、Oracle之rownum、MSSQL之top(Sybase的新版本也支持top),都只对当前查询SQL有效,没有副作用。

?

可以肯定,在Sybase下,在做这个Hibernate查询之前,在执行的某个IBatis的SQL查询操作中,使用了set rowcount 1,却没有在查询后改回 rowcount的设置,从而导致后续的查询结果集都是1条,从而导致了后面的C对象为null的异常。

?

【解难】

当把问题的焦点从抛异常的HibernateDAO处,移到Service类执行该操作之前的其他IBatisDAO的操作时,通过一一排查时,很快在IBatisDAO的Sybase实现版本的某个映射配置文件中,找到了罪魁祸首:那条“set rowcount 1”语句,而在select之后,确实没有“set rowcount 0”来将rowcount复原。

?

在那条select语句后面,加上?“set rowcount 0”,再次测试Service,OK,JUnit绿条通过!!

?

【小结】

回头来看,这次碰到的看似一个小问题,找起来却费了大麻烦,归结起来有如下原因:

1、错误游离和假相:IBatis中SQL编写的错误,却在Hibernate实现的DAO中才引起异常,导致总是认为错误在后者,而想不到前者。

2、缺乏沟通和代码检查:由于一部分人用IBatis开发,一部分人用Hibernate开发,两边都不了解两个部分之于整体的关系,而单独的DAO测试,却检验不出这类问题来,因而在集成时难以真正定位问题

?

总之一句话,如果使用IBatis开发,不管有没有混合使用Hibernate,一定要注意SQL本身的检查,包括当前连接属性的设置和恢复,以及不同数据库之间的差异。

  相关解决方案