Spring 抽象的 DAO 体系兼容多种数据访问技术,它们各有特色,各有千秋。

  • Hibernate 是非常优秀的 ORM 实现方案,但对底层 SQL 的控制不太方便

  • MyBatis 则通过模板化技术让我们能方便地控制 SQL,但没有 Hibernate 那样高的开发效率

  • 自由度最高的当然是直接使用 Spring JDBC 莫属了,但是它也是最底层的,灵活的代价是代码的繁复

很难说哪种数据访问技术是最优秀的,只有在某种特定的场景下,才能给出答案。所以在一个应用中,往往采用多个数据访问技术:一般是两种,一种采用 ORM 技术框架,而另一种采用偏 JDBC 的底层技术。


问题

当我们采用:ORM 技术框架+ 偏 JDBC 的底层技术如何应对事务管理的问题呢? 我们知道 Spring 为每种数据访问技术提供了相应的事务管理器,难道需要分别为它们配置对应的事务管理器吗?它们到底是如何协作,如何工作的呢?


解决方案

Spring 事务管理的为我们的提供了解决方案。

当我们采用了一个高端 ORM 技术(Hibernate,JPA,JDO),同时采用一个 JDBC 技术(Spring JDBC,MyBatis),由于前者的会话(Session)是对后者连接(Connection)的封装,Spring 会“足够智能地”在同一个事务线程让前者的会话封装后者的连接。所以,我们只要直接采用前者的事务管理器就可以了。


我们列举下混合数据访问技术所对应的事务管理器:


示例:Hibernate + Spring JDBC

由于一般不会出现同时使用多个 ORM 框架的情况(如 Hibernate + JPA),我们不拟对此命题展开论述,只重点研究 ORM 框架 + JDBC 框架的情况。

Hibernate + Spring JDBC 可能是被使用得最多的组合,我们通过实例来观察事物的运行情况。


User 使用了注解声明的实体类

import javax.persistence.Entity; import javax.persistence.Table; import javax.persistence.Column; import javax.persistence.Id; import java.io.Serializable; @Entity @Table(name="T_USER") public class User implements Serializable{ @Id @Column(name = "USER_NAME") private String userName; private String password; private int score; @Column(name = "LAST_LOGON_TIME") private long lastLogonTime = 0;
}

UserService 使用 Hibernate 数据访问技术

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.stereotype.Service; import org.springframework.orm.hibernate3.HibernateTemplate; import org.apache.commons.dbcp.BasicDataSource; import user.User; @Service("userService") public class UserService extends BaseService { @Autowired private HibernateTemplate hibernateTemplate; @Autowired private ScoreService scoreService; public void logon(String userName) {
System.out.println("logon method...");
updateLastLogonTime(userName); //①使用Hibernate数据访问技术 scoreService.addScore(userName, 20); //②使用Spring JDBC数据访问技术 } public void updateLastLogonTime(String userName) {
System.out.println("updateLastLogonTime...");
User user = hibernateTemplate.get(User.class,userName);
user.setLastLogonTime(System.currentTimeMillis());
hibernateTemplate.flush(); //③请看下文的分析 }
}

在①处,使用 Hibernate 操作数据,而在②处调用 ScoreService#addScore(),该方法内部使用 Spring JDBC 操作数据。

在③处,我们显式调用了 flush() 方法,将 Session 中的缓存同步到数据库中,这个操作将即时向数据库发送一条更新记录的 SQL 语句

之所以要在此显式执行 flush() 方法,原因是:默认情况下,Hibernate 要在事务提交时才将数据的更改同步到数据库中,而事务提交发生在 logon() 方法返回前。

如果所有针对数据库的更改都使用 Hibernate,这种数据同步延迟的机制不会产生任何问题。但是,我们在 logon() 方法中同时采用了 Hibernate 和 Spring JDBC 混合数据访问技术。

Spring JDBC 无法自动感知 Hibernate 一级缓存,所以如果不及时调用 flush() 方法将数据更改同步到数据库,则②处通过 Spring JDBC 进行数据更改的结果将被 Hibernate 一级缓存中的更改覆盖掉,武汉试管婴儿因为,一级缓存在 logon() 方法返回前才同步到数据库!


ScoreService :使用 Spring JDBC 数据访问技术

import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.apache.commons.dbcp.BasicDataSource; @Service("scoreUserService") public class ScoreService extends BaseService{ @Autowired private JdbcTemplate jdbcTemplate; public void addScore(String userName, int toAdd) {
System.out.println("addScore...");
String sql = "UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";
jdbcTemplate.update(sql, toAdd, userName); //① 查看此处数据库激活的连接数 BasicDataSource basicDataSource = (BasicDataSource) jdbcTemplate.getDataSource();
System.out.println("激活连接数量:"+basicDataSource.getNumActive());
}
}

关键配置文件

<!-- 使用Hibernate事务管理器 --> <bean id="hiberManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager" p:sessionFactory-ref="sessionFactory"/> <!-- 对所有继承BaseService类的公用方法实施事务增强 --> <aop:config proxy-target-class="true"> <aop:pointcut id="serviceJdbcMethod" expression="within(com.artisan.BaseService+)"/> <aop:advisor pointcut-ref="serviceJdbcMethod" advice-ref="hiberAdvice"/> </aop:config> <tx:advice id="hiberAdvice" transaction-manager="hiberManager"> <tx:attributes> <tx:method name="*"/> </tx:attributes> </tx:advice>

日志:

21:37:57,062 (AbstractPlatformTransactionManager.java:365) - Creating new transaction
with name [com.artisan.UserService.logon]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT 21:37:57,093 (SessionImpl.java:220) - opened session at timestamp: 12666407370 21:37:57,093 (HibernateTransactionManager.java:493) - Opened new Session
[org.hibernate.impl.SessionImpl@83020] for Hibernate transaction ① 21:37:57,093 (HibernateTransactionManager.java:504) - Preparing JDBC Connection
of Hibernate Session [org.hibernate.impl.SessionImpl@83020] 21:37:57,109 (JDBCTransaction.java:54) - begin … logon method...
updateLastLogonTime...
… 21:37:57,109 (AbstractBatcher.java:401) - select user0_.USER_NAME as USER1_0_0_,
user0_.LAST_LOGON_TIME as LAST2_0_0_, user0_.password as password0_0_,
user0_.score as score0_0_ from T_USER user0_ where user0_.USER_NAME=? Hibernate: select user0_.USER_NAME as USER1_0_0_,
user0_.LAST_LOGON_TIME as LAST2_0_0_, user0_.password as password0_0_,
user0_.score as score0_0_ from T_USER user0_ where user0_.USER_NAME=? … 21:37:57,187 (HibernateTemplate.java:422) - Not closing pre-bound
Hibernate Session after HibernateTemplate 21:37:57,187 (HibernateTemplate.java:397) - Found thread-bound Session
for HibernateTemplate Hibernate: update T_USER set LAST_LOGON_TIME=?, password=?, score=? where USER_NAME=? … 2017-09-26 21:37:57,203 DEBUG [main] (AbstractPlatformTransactionManager.java:470)
- Participating in existing transaction ②
addScore... 2017-09-26 21:37:57,203 DEBUG [main] (JdbcTemplate.java:785)
- Executing prepared SQL update 2017-09-26 21:37:57,203 DEBUG [main] (JdbcTemplate.java:569)
- Executing prepared SQL statement
[UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?] 2017-09-26 21:37:57,203 DEBUG [main] (JdbcTemplate.java:794)
- SQL update affected 1 rows 激活连接数量:1 ③ 2017-09-26 21:37:57,203 DEBUG [main] (AbstractPlatformTransactionManager.java:752)
- Initiating transaction commit 2017-09-26 21:37:57,203 DEBUG [main] (HibernateTransactionManager.java:652)
- Committing Hibernate transaction on Session
[org.hibernate.impl.SessionImpl@83020] ④ 2017-09-26 21:37:57,203 DEBUG [main] (JDBCTransaction.java:103) - commit ⑤

在①处 UserService#logon() 开启一个新的事务, 
在②处 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 的缓存。

可以将以上结论推广到其它混合数据访问技术的方案中,如 Hibernate+MyBatis,JPA+Spring JDBC,JDO+Spring JDBC 等

Spring JDBC-混合框架的事务管理的更多相关文章

  1. Spring 框架的事务管理

    1. Spring 框架的事务管理相关的类和API PlateformTransactionManager 接口: 平台事务管理器(真正管理事务的类); TransactionDefinition 接 ...

  2. Spring框架之事务管理

    Spring框架之事务管理 一.事务的作用 将若干的数据库操作作为一个整体控制,一起成功或一起失败. 原子性:指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生. 一致性:指事务前后 ...

  3. Java数据库连接--JDBC调用存储过程,事务管理和高级应用

    相关链接:Jdbc调用存储过程 一.JDBC常用的API深入详解及存储过程的调用 1.存储过程的介绍 我们常用的操作数据库语言SQL语句在执行的时候要先进行编译,然后执行,而存储过程是在大型数据库系统 ...

  4. 事务隔离级别与传播机制,spring+mybatis+atomikos实现分布式事务管理

    1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). 原子性(Atomicity):即事务是不可分割的最小工作单 ...

  5. Spring事务隔离级别与传播机制详解,spring+mybatis+atomikos实现分布式事务管理

    原创说明:本文为本人原创作品,绝非他处转载,转账请注明出处 1.事务的定义:事务是指多个操作单元组成的合集,多个单元操作是整体不可分割的,要么都操作不成功,要么都成功.其必须遵循四个原则(ACID). ...

  6. Spring 简单而强大的事务管理功能

    开始之前 关于本教程 本教程将深入讲解 Spring 简单而强大的事务管理功能,包括编程式事务和声明式事务.通过对本教程的学习,您将能够理解 Spring 事务管理的本质,并灵活运用之. 先决条件 本 ...

  7. Spring+JTA+Atomikos+mybatis分布式事务管理

    我们平时的工作中用到的Spring事务管理是管理一个数据源的.但是如果对多个数据源进行事务管理该怎么办呢?我们可以用JTA和Atomikos结合Spring来实现一个分布式事务管理的功能.了解JTA可 ...

  8. spring boot配置mybatis和事务管理

    spring boot配置mybatis和事务管理 一.spring boot与mybatis的配置 1.首先,spring boot 配置mybatis需要的全部依赖如下: <!-- Spri ...

  9. 0045 Spring中使用DataSourceTransactionManager进行事务管理的xml配置

    在一个业务的实现过程中,可能需要多条sql完成对数据库的操作,比如账户登录,需要匹配用户名和密码,然后要增加积分,还要记录登录的ip和时间,这可能需要三个sql语句,这三个语句应当是一个整体,任意一个 ...

随机推荐

  1. Ubuntu14.04使用samba服务器共享Home目录

    这里记录一下,以Ubuntu 14.04为例.   1.安装samba服务器. sudo apt-get install samba 2.修改配置文件 sudo vim /etc/samba/smb. ...

  2. CSDN 夏令营程序 试题分析 (3)

    首先大家先来看题目: 分析: 三维数组存储以行为主序列,计算公式例如以下: Loc(Ai,j,k)=Loc(Ac1c2c3)+[(i-c1)V2V3+(j-c2)V3+(k-c3)]*L 当中c1.c ...

  3. python——深刻理解Python中的元类(metaclass)

    译注:这是一篇在Stack overflow上 很热的帖子.提问者自称已经掌握了有关Python OOP编程中的各种概念,但始终觉得元类(metaclass)难以理解.他知道这肯定和自省有关,但仍然觉 ...

  4. libevent2源码分析之三:信号的初始化流程

    libevent2对信号的响应也进行了封装,使之与socket操作一样对外提供统一的接口.这里的信号一般指linux的信号.由于信号与socket相关的编程接口有较大的不同,因此在内部实现也有一些区别 ...

  5. 【English】口头禅

    1. Absolutely! 毫无疑问! 2. Adorable! 可爱极了! 3. Amazing! 太神奇了! 4. Anytime! 随时吩咐! 5. Almost! 差不多了! 6. Awfu ...

  6. iOS之基于FreeStreamer的简单音乐播放器(模仿QQ音乐)

    代码地址如下:http://www.demodashi.com/demo/11944.html 天道酬勤 前言 作为一名iOS开发者,每当使用APP的时候,总难免会情不自禁的去想想,这个怎么做的?该怎 ...

  7. C#微信公众号学习 - (一)测试账号申请

    主要分为两部分: 1.创建C#的项目,并发布, 2.微信填写发布的地址进行开发者验证. 一. VS环境为VS2017,创建项目时,自带的一些东西发布会导致各种错误,无奈,创建了空项目aspx窗体,如下 ...

  8. Mac OS X 控制键符号

  9. 551. Student Attendance Record I【easy】

    551. Student Attendance Record I[easy] You are given a string representing an attendance record for ...

  10. intent 启动activity、service的方法

    1.通过intent启动service. 通过传递一个Intent对象至Context.startService()将启动一个服务(或给予正在运行的服务以一个新的指令).Android调用服务的onS ...