Spring 抽象的 DAO 体系兼容多种数据访问技术,它们各有特色,各有千秋。
很难说哪种数据访问技术是最优秀的,只有在某种特定的场景下,才能给出答案。所以在一个应用中,往往采用多个数据访问技术:一般是两种,一种采用 ORM 技术框架,而另一种采用偏 JDBC 的底层技术。
当我们采用:ORM 技术框架+ 偏 JDBC 的底层技术如何应对事务管理的问题呢? 我们知道 Spring 为每种数据访问技术提供了相应的事务管理器,难道需要分别为它们配置对应的事务管理器吗?它们到底是如何协作,如何工作的呢?
Spring 事务管理的为我们的提供了解决方案。
当我们采用了一个高端 ORM 技术(Hibernate,JPA,JDO),同时采用一个 JDBC 技术(Spring JDBC,MyBatis),由于前者的会话(Session)是对后者连接(Connection)的封装,Spring 会“足够智能地”在同一个事务线程让前者的会话封装后者的连接。所以,我们只要直接采用前者的事务管理器就可以了。
我们列举下混合数据访问技术所对应的事务管理器:
由于一般不会出现同时使用多个 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 等
来源: http://blog.csdn.net/yangshangwei/article/details/78109151