Spring的事务管理难点剖析:联合军种作战的混乱-程序员宅基地

技术标签: Hibernate  Spring  事务  

引述 :Spring给我们带来的最大的一个便利是透明地实现事务管理,但是一般开发者仅仅是了解如何配置,在实际应用过程中却往往遇到很多问题,存在很多困惑。有有鉴于此,我特意总结了各种实际应用中遇到的Spring事务管理的困惑,这些文章摘自于我的《Spring 3.x企业应用开发实战》的第10章,我将通过连载的方式,陆续在此发出。欢迎大家讨论。 
--------------------------------------------------------------------------------------------------------------------------  

Spring事务管理器的应对  


   Spring抽象的DAO体系兼容多种数据访问技术,它们各有特色,各有千秋。像Hibernate是非常优秀的ORM实现方案,但对底层SQL的控制不太方便;而iBatis则通过模板化技术让你方便地控制SQL,但没有Hibernate那样高的开发效率;自由度最高的当然是直接使用Spring JDBC了,但它也是底层的,灵活的代价是代码的繁复。很难说哪种数据访问技术是最优秀的,只有在某种特定的场景下才能给出答案。所以在一个应用中,往往采用多个数据访问技术:一般是两种,一种采用ORM技术框架,而另一种采用偏JDBC的底层技术,两者珠联璧合,形成联合军种,共同御敌。 
   但是,这种联合军种如何应对事务管理的问题呢?我们知道Spring为每种数据访问技术提供了相应的事务管理器,难道需要分别为它们配置对应的事务管理器吗?它们到底是如何协作和工作的呢?这些层出不穷的问题往往压制了开发人员使用联合军种的想法。 
   其实,在这个问题上,我们低估了Spring事务管理的能力。如果你采用了一个高端ORM技术(Hibernate、JPA、JDO),同时采用一个JDBC技术(Spring JDBC、iBatis),由于前者的会话(Session)是对后者连接(Connection)的封装,Spring会“足够智能地”在同一个事务线程让前者的会话封装后者的连接。所以,我们只要直接采用前者的事务管理器就可以了。表10-1给出了混合数据访问技术框架所对应的事务管理器。 
序    号 混合数据访问技术框架 事务管理器
1 Hibernate+ Spring JDBC或iBatis org.springframework.orm.hibernate3.HibernateTransactionManager
2 JPA+Spring JDBC或iBatis org.springframework.orm.jpa.JpaTransactionManager
3 JDO+Spring JDBC或iBatis org.springframework.orm.jdo.JdoTransactionManager


Hibernate+Spring JDBC混合框架的事务管理 


   由于一般不会出现同时使用多个ORM框架的情况(如Hibernate+JPA),我们不拟对此命题展开论述,只重点研究ORM框架+JDBC框架的情况。Hibernate+Spring JDBC可能是被使用得最多的组合,本节我们通过实例观察事务管理的运作情况。 
Java代码   收藏代码
  1. package com.baobaotao.mixdao;  
  2.   
  3. …  
  4.   
  5. @Service("userService")  
  6. public class UserService extends BaseService {  
  7.     @Autowired  
  8.     private HibernateTemplate hibernateTemplate;  
  9.   
  10.     @Autowired  
  11.     private ScoreService scoreService;  
  12.   
  13.     public void logon(String userName) {  
  14.          
  15.         //①通过Hibernate技术访问数据  
  16.         System.out.println("before updateLastLogonTime()..");  
  17.         updateLastLogonTime(userName);  
  18.         System.out.println("end updateLastLogonTime()..");  
  19.           
  20.         //②通过JDBC技术访问数据  
  21.         System.out.println("before scoreService.addScore()..");  
  22.         scoreService.addScore(userName, 20);  
  23.         System.out.println("end scoreService.addScore()..");  
  24.     }  
  25.       
  26.     public void updateLastLogonTime(String userName) {  
  27.         User user = hibernateTemplate.get(User.class,userName);  
  28.         user.setLastLogonTime(System.currentTimeMillis());  
  29.         hibernateTemplate.update(user);  
  30.   
  31.         //③这句很重要,请看下文的分析  
  32.         hibernateTemplate.flush();  
  33.     }  
  34. }  

  在①处,使用Hibernate操作数据,而在②处调用ScoreService#addScore(),该方法内部使用Spring JDBC操作数据。 
  在③处,我们显式调用了flush()方法,将Session中的缓存同步到数据库中(即马上向数据库发送一条更新记录的SQL语句)。之所以要显式执行flush()方法,原因是在默认情况下,Hibernate对数据的更改只是记录在一级缓存中,要等到事务提交或显式调用flush()方法时才会将一级缓存中的数据同步到数据库中,而提交事务的操作发生在   logon()方法返回前。如果所有针对数据库的更改操作都使用Hibernate,这种数据同步的延迟机制并不会产生任何问题。但是,我们在logon()方法中同时采用了Hibernate和Spring JDBC混合数据访问技术,Spring JDBC无法自动感知Hibernate一级缓存,所以如果不及时调用flush()方法将记录数据更改的一级缓存同步到数据库中,则②处通过Spring JDBC进行数据更改的结果将被Hibernate一级缓存中的更改覆盖掉,因为Hibernate一级缓存要等到logon()方法返回前才同步到数据库! 
   ScoreService使用Spring JDBC数据访问技术,其代码如下所示: 
Java代码   收藏代码
  1. package com.baobaotao.mixdao;  
  2.   
  3. import org.springframework.beans.factory.annotation.Autowired;  
  4. import org.springframework.jdbc.core.JdbcTemplate;  
  5. import org.springframework.stereotype.Service;  
  6. import org.apache.commons.dbcp.BasicDataSource;  
  7.   
  8. @Service("scoreService")  
  9. public class ScoreService extends BaseService{  
  10.   
  11.     @Autowired  
  12.     private JdbcTemplate jdbcTemplate;  
  13.   
  14.     public void addScore(String userName, int toAdd) {  
  15.         String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";  
  16.         jdbcTemplate.update(sql, toAdd, userName);  
  17.         BasicDataSource basicDataSource = (BasicDataSource) jdbcTemplate.getDataSource();  
  18.         //①查看此处数据库激活的连接数量  
  19.         System.out.println("[scoreUserService.addScore]激活连接数量:"  
  20.                          +basicDataSource.getNumActive());  
  21.     }  
  22. }  

   Spring关键的配置文件代码如下所示: 
Xml代码   收藏代码
  1. …  
  2. <!--①使用Hibernate事务管理器 -->  
  3. <bean id="hiberManager"  
  4.       class="org.springframework.orm.hibernate3.HibernateTransactionManager"  
  5.       p:sessionFactory-ref="sessionFactory"/>  
  6.   
  7. <!--②使UserService及ScoreService的公用方法都拥有事务 -->  
  8. <aop:config proxy-target-class="true">  
  9.     <aop:pointcut id="serviceJdbcMethod"  
  10.                   expression="within(com.baobaotao.mixdao.BaseService+)"/>  
  11.     <aop:advisor pointcut-ref="serviceJdbcMethod"  
  12.                  advice-ref="hiberAdvice"/>  
  13. </aop:config>  
  14. <tx:advice id="hiberAdvice" transaction-manager="hiberManager">  
  15.     <tx:attributes>  
  16.         <tx:method name="*"/>  
  17.     </tx:attributes>  
  18. </tx:advice>  
  19.   
  20. /beans>  

启动Spring容器,执行UserService#logon()方法,可以查看到如下的执行日志: 
引用
before userService.logon().. 

①在执行userService.logon()后,Spring开启一个事务 
Creating new transaction with name [com.baobaotao.mixdao.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT 
opened session at timestamp: 13009379637 
Opened new Session [org.hibernate.impl.SessionImpl@c5f468] for Hibernate transaction 
… 
Exposing Hibernate transaction as JDBC transaction [jdbc:mysql://localhost:3306/sampledb, UserName=root@localhost, MySQL-AB JDBC Driver] 
before userService.updateLastLogonTime().. 

②userService.updateLastLogonTime()执行时自动绑定到①处开启的Session中 
Found thread-bound Session for HibernateTemplate 
loading entity: [com.baobaotao.User#tom] 
about to open PreparedStatement (open PreparedStatements: 0, globally: 0) 
… 
about to close PreparedStatement (open PreparedStatements: 1, globally: 1) 
Not closing pre-bound Hibernate Session after HibernateTemplate 
end updateLastLogonTime().. 

before scoreService.addScore()..
 

③scoreService.addScore()执行时绑定到①处开启的Session中,并加入其所对应的事务中 
Found thread-bound Session [org.hibernate.impl.SessionImpl@c5f468] for Hibernate 
transaction 
Participating in existing transaction 
… 
SQL update affected 1 rows 

④此时数据源只打开了一个连接 
[scoreUserService.addScore]激活连接数量:1 
end scoreService.addScore().. 
Initiating transaction commit 

⑤提交Hibernate的事务,它将触发一级缓存到数据库的同步 
Committing Hibernate transaction on Session [org.hibernate.impl.SessionImpl@c5f468] 
commit 
processing flush-time cascades 
dirty checking collections 
Flushed: 0 insertions, 0 updates, 0 deletions to 1 objects 
Flushed: 0 (re)creations, 0 updates, 0 removals to 0 collections 
listing entities: 
com.baobaotao.User{lastLogonTime=1300937963882, score=10, userName=tom, password=123456} 
re-enabling autocommit 

⑥提效Session底层所绑定的JDBC Connection所对应的事务 
committed JDBC Connection 
transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources! 
Closing Hibernate Session [org.hibernate.impl.SessionImpl@c5f468] after transaction 
Closing Hibernate Session 
releasing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)] 
transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources! 
after userService.logon().. 


   仔细观察这段输出日志,在①处UserService#logon()开启一个新的事务。②处的UserService# updateLastLogonTime() 绑定到事务上下文的Session中。③处ScoreService#addScore()方法加入到①处开启的事务上下文中。④处的输出是ScoreService #addScore()方法内部的输出信息,汇报此时数据源激活的连接数为1,这清楚地告诉我们Hibernate和JDBC这两种数据访问技术在同一事务上下文中“共用”一个连接。在⑤处,提交Hibernate事务,接着在⑥处触发调用底层的Connection提交事务。 
   从以上的运行结果,我们可以得出这样的结论:使用Hibernate事务管理器后,可以混合使用Hibernate和Spring JDBC数据访问技术,它们将工作于同一事务上下文中。但是使用Spring JDBC访问数据时,Hibernate的一级或二级缓存得不到同步,此外,一级缓存延迟数据同步机制可能会覆盖Spring JDBC数据更改的结果。 
   由于混合数据访问技术方案存在“事务同步而缓存不同步”的情况,所以最好用Hibernate进行读写操作,而只用Spring JDBC进行读操作。如用Spring JDBC进行简要列表的查询,而用Hibernate对查询出的数据进行维护。 
   如果确实要同时使用Hibernate和Spring JDBC读写数据,则必须充分考虑到Hibernate缓存机制引发的问题:必须整体分析数据维护逻辑,根据需要及时调用Hibernate的flush()方法,以免覆盖Spring JDBC的更改,在Spring JDBC更改数据库时,维护Hibernate的缓存。由于方法调用顺序的不同都可能影响数据的同步性,因此很容易发生问题,这会极大提高数据访问程序的复杂性。所以笔者郑重建议不要同时使用Spring JDBC和Hibernate对数据进行写操作。 
   可以将以上结论推广到其他混合数据访问技术的方案中,如Hibernate+iBatis、JPA+Spring JDBC、JDO+Spring JDBC等。 

  注:以上内容摘自《Spring 3.x企业应用开发实战》
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/yztezhl/article/details/9612393

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签