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可能是被使用得最多的组合,本节我们通过实例观察事务管理的运作情况。

  1. package com.baobaotao.mixdao;
  2. @Service("userService")
  3. public class UserService extends BaseService {
  4. @Autowired
  5. private HibernateTemplate hibernateTemplate;
  6. @Autowired
  7. private ScoreService scoreService;
  8. public void logon(String userName) {
  9. //①通过Hibernate技术访问数据
  10. System.out.println("before updateLastLogonTime()..");
  11. updateLastLogonTime(userName);
  12. System.out.println("end updateLastLogonTime()..");
  13. //②通过JDBC技术访问数据
  14. System.out.println("before scoreService.addScore()..");
  15. scoreService.addScore(userName, 20);
  16. System.out.println("end scoreService.addScore()..");
  17. }
  18. public void updateLastLogonTime(String userName) {
  19. User user = hibernateTemplate.get(User.class,userName);
  20. user.setLastLogonTime(System.currentTimeMillis());
  21. hibernateTemplate.update(user);
  22. //③这句很重要,请看下文的分析
  23. hibernateTemplate.flush();
  24. }
  25. }

在①处,使用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数据访问技术,其代码如下所示:

  1. package com.baobaotao.mixdao;
  2. import org.springframework.beans.factory.annotation.Autowired;
  3. import org.springframework.jdbc.core.JdbcTemplate;
  4. import org.springframework.stereotype.Service;
  5. import org.apache.commons.dbcp.BasicDataSource;
  6. @Service("scoreService")
  7. public class ScoreService extends BaseService{
  8. @Autowired
  9. private JdbcTemplate jdbcTemplate;
  10. public void addScore(String userName, int toAdd) {
  11. String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";
  12. jdbcTemplate.update(sql, toAdd, userName);
  13. BasicDataSource basicDataSource = (BasicDataSource) jdbcTemplate.getDataSource();
  14. //①查看此处数据库激活的连接数量
  15. System.out.println("[scoreUserService.addScore]激活连接数量:"
  16. +basicDataSource.getNumActive());
  17. }
  18. }

Spring关键的配置文件代码如下所示:

  1. <!--①使用Hibernate事务管理器 -->
  2. <bean id="hiberManager"
  3. class="org.springframework.orm.hibernate3.HibernateTransactionManager"
  4. p:sessionFactory-ref="sessionFactory"/>
  5. <!--②使UserService及ScoreService的公用方法都拥有事务 -->
  6. <aop:config proxy-target-class="true">
  7. <aop:pointcut id="serviceJdbcMethod"
  8. expression="within(com.baobaotao.mixdao.BaseService+)"/>
  9. <aop:advisor pointcut-ref="serviceJdbcMethod"
  10. advice-ref="hiberAdvice"/>
  11. </aop:config>
  12. <tx:advice id="hiberAdvice" transaction-manager="hiberManager">
  13. <tx:attributes>
  14. <tx:method name="*"/>
  15. </tx:attributes>
  16. </tx:advice>
  17. /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事务管理器的应对的更多相关文章

  1. spring事务管理器设计思想(二)

    上文见<spring事务管理器设计思想(一)> 对于第二个问题,涉及到事务的传播级别,定义如下: PROPAGATION_REQUIRED-- 如果当前没有事务,就新建一个事务.这是最常见 ...

  2. spring事务管理器设计思想(一)

    在最近做的一个项目里面,涉及到多数据源的操作,比较特殊的是,这多个数据库的表结构完全相同,由于我们使用的ibatis框架作为持久化层,为了防止每一个数据源都配置一套规则,所以重新实现了数据源,根据线程 ...

  3. spring事务管理器设计思想(2)

    spring事务管理器设计思想(二) 上文见<spring事务管理器设计思想(一)> 对于第二个问题,涉及到事务的传播级别,定义如下: PROPAGATION_REQUIRED-- 如果当 ...

  4. Spring事务管理器分类

    Spring并不直接管理事务,事实上,它是提供事务的多方选择.你能委托事务的职责给一个特定的平台实现,比如用JTA或者是别的持久机制.Spring的事务管理器可以用下表表示: 事务管理器的实例 目标 ...

  5. spring事务管理器的源码和理解

    原文出处: xieyu_zy 以前说了大多的原理,今天来说下spring的事务管理器的实现过程,顺带源码干货带上. 其实这个文章唯一的就是带着看看代码,但是前提你要懂得动态代理以及字节码增强方面的知识 ...

  6. Spring事务管理器

    1.创建实体和接口 public class Bank { private Integer id; private String name; private String manay; public ...

  7. 【面试】足够“忽悠”面试官的『Spring事务管理器』源码阅读梳理(建议珍藏)

    PS:文章内容涉及源码,请耐心阅读. 理论实践,相辅相成 伟大领袖毛主席告诉我们实践出真知.这是无比正确的.但是也会很辛苦. 就像淘金一样,从大量沙子中淘出金子一定是一个无比艰辛的过程.但如果真能淘出 ...

  8. Spring事务管理--多个ORM框架在使用时的情况分析

    公司的项目已经接近尾声了,总结一下项目中用到的技术,我发现项目中的有些东西还是挺模糊的,只是知道这么用就行了.并不清楚其中的原理.由于公司的项目比较老,是7年前的一个项目了,中间一直有人在维护,也是在 ...

  9. 跟我学Spring3(9.2):Spring的事务之事务管理器

    原文出处: 张开涛9.2.1 概述 Spring框架支持事务管理的核心是事务管理器抽象,对于不同的数据访问框架(如Hibernate)通过实现策略接口PlatformTransactionManage ...

随机推荐

  1. django之DB操作

    先来区分一下什么是一对一.多对多 一对一:子表从母表中选出一条数据一一对应,母表中选出来一条就少一条,子表不可以再选择母表中已被选择的那条数据: 一对多:子表从母表中选出一条数据一一对应,但母表的这条 ...

  2. What technical details should a programmer of a web application consider before making the site public?

    What things should a programmer implementing the technical details of a web application consider bef ...

  3. Java生成带小图标的二维码-google zxing 工具类

    近期一直忙于开发微信商城项目,应客户要求,要开发个有图标的二维码.经过两次改版,终于实现了该功能(第一次没有小图标,这次才整合好的),如下是完整代码 . 该代码使用Java7开发,另外使用 core- ...

  4. Making my own Autonomous Robot in ROS / Gazebo, Day 1: Building the static model

    Day 1: Setting up ROS: Indigo OS: Ubuntu 14.04 OS: Gazebo 7.0.0 Initialize the workspace To create t ...

  5. python起的 simpleHTTPServer服务传输文件

    python起的 simpleHTTPServer服务传输文件 经同事的介绍,在Linux上传输文件的一种特别方便的方法: python -m SimpleHTTPServer [端口] 端口不填 默 ...

  6. scss编译

    SASS?SASS是一种CSS的开发工具,提供了许多便利的写法,大大节省了设计者的时间,使得CSS的开发,变得简单和可维护. sass有两种后缀名文件: sass(不使用大括号和分号)---不建议使用 ...

  7. web前端网站收藏

    参考 w3school:html,css,js等各种参考 W3schools:较之w3school界面更华丽 webplatform:学习最新的web技术 MDN:mozilla developer ...

  8. mysql 命令(二)

    1.创建数据库,并制定默认的字符集是utf8. CREATE DATABASE IF NOT EXISTS yourdbname DEFAULT CHARSET utf8 COLLATE utf8_g ...

  9. Spring官网下载dist.zip的几种方法

    Spring官网下载dist.zip的几种方法   Spring官网改版后,很多项目的完整zip包下载链接已经隐掉了,虽然Spring旨在引导大家用更“高大上”的maven方式来管理所依赖的jar包, ...

  10. [SoapUI] 在SoapUI里用Java语言判断Excel单元格为空或者Null时出错

    我取Excel数据时先判断cell是否为"": if(cellValue != ""){     listNumber.add(i);     cellValu ...