Spring JDBC-混合框架的事务管理

简介: Spring JDBC-混合框架的事务管理

概述


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 会“足够智能地”在同一个事务线程让前者的会话封装后者的连接。所以,我们只要直接采用前者的事务管理器就可以了。


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


20170927092202940.jpg


示例: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 等


相关文章
|
8天前
|
前端开发 Java 物联网
智慧班牌源码,采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署
智慧班牌系统是一款基于信息化与物联网技术的校园管理工具,集成电子屏显示、人脸识别及数据交互功能,实现班级信息展示、智能考勤与家校互通。系统采用Java + Spring Boot后端框架,搭配Vue2前端技术,支持SaaS云部署与私有化定制。核心功能涵盖信息发布、考勤管理、教务处理及数据分析,助力校园文化建设与教学优化。其综合性和可扩展性有效打破数据孤岛,提升交互体验并降低管理成本,适用于日常教学、考试管理和应急场景,为智慧校园建设提供全面解决方案。
157 70
|
12天前
|
Java 关系型数据库 MySQL
深入解析 @Transactional——Spring 事务管理的核心
本文深入解析了 Spring Boot 中 `@Transactional` 的工作机制、常见陷阱及最佳实践。作为事务管理的核心注解,`@Transactional` 确保数据库操作的原子性,避免数据不一致问题。文章通过示例讲解了其基本用法、默认回滚规则(仅未捕获的运行时异常触发回滚)、因 `try-catch` 或方法访问修饰符不当导致失效的情况,以及数据库引擎对事务的支持要求。最后总结了使用 `@Transactional` 的五大最佳实践,帮助开发者规避常见问题,提升项目稳定性与可靠性。
122 11
|
1月前
|
前端开发 Java 数据库连接
Spring MVC 扩展和SSM框架整合
通过以上步骤,我们可以将Spring MVC扩展并整合到SSM框架中。这个过程包括配置Spring MVC和Spring的核心配置文件,创建控制器、服务层和MyBatis的Mapper接口及映射文件。在实际开发中,可以根据具体业务需求进行进一步的扩展和优化,以构建更加灵活和高效的企业级应用程序。
49 5
|
1月前
|
存储 监控 数据可视化
SaaS云计算技术的智慧工地源码,基于Java+Spring Cloud框架开发
智慧工地源码基于微服务+Java+Spring Cloud +UniApp +MySql架构,利用传感器、监控摄像头、AI、大数据等技术,实现施工现场的实时监测、数据分析与智能决策。平台涵盖人员、车辆、视频监控、施工质量、设备、环境和能耗管理七大维度,提供可视化管理、智能化报警、移动智能办公及分布计算存储等功能,全面提升工地的安全性、效率和质量。
|
2月前
|
存储 人工智能 开发框架
Spring AI Alibaba 应用框架挑战赛圆满落幕,恭喜获奖选手
第二届开放原子大赛 Spring AI Alibaba 应用框架挑战赛决赛于 2 月 23 日在北京圆满落幕。
160 17
|
2月前
|
前端开发 Java 数据库连接
Spring框架初识
Spring 是一个分层的轻量级开源框架,核心功能包括控制反转(IOC)和面向切面编程(AOP)。主要模块有核心容器、Spring 上下文、AOP、DAO、ORM、Web 模块和 MVC 框架。它通过 IOC 将配置与代码分离,简化开发;AOP 提供了声明性事务管理等增强功能。
99 21
Spring框架初识
|
2月前
|
XML Java 开发者
通过springboot框架创建对象(一)
在Spring Boot中,对象创建依赖于Spring框架的核心特性——控制反转(IoC)和依赖注入(DI)。IoC将对象的创建和管理交由Spring应用上下文负责,开发者只需定义依赖关系。DI通过构造函数、setter方法或字段注入实现依赖对象的传递。Spring Boot的自动配置机制基于类路径和配置文件,自动为应用程序配置Spring容器,简化开发过程。Bean的生命周期包括定义扫描、实例化、依赖注入、初始化和销毁回调,均由Spring容器管理。这些特性提高了开发效率并简化了代码维护。
|
3月前
|
SQL Java 数据库连接
对Spring、SpringMVC、MyBatis框架的介绍与解释
Spring 框架提供了全面的基础设施支持,Spring MVC 专注于 Web 层的开发,而 MyBatis 则是一个高效的持久层框架。这三个框架结合使用,可以显著提升 Java 企业级应用的开发效率和质量。通过理解它们的核心特性和使用方法,开发者可以更好地构建和维护复杂的应用程序。
179 29
|
3月前
|
开发框架 运维 监控
Spring Boot中的日志框架选择
在Spring Boot开发中,日志管理至关重要。常见的日志框架有Logback、Log4j2、Java Util Logging和Slf4j。选择合适的日志框架需考虑性能、灵活性、社区支持及集成配置。本文以Logback为例,演示了如何记录不同级别的日志消息,并强调合理配置日志框架对提升系统可靠性和开发效率的重要性。
|
4月前
|
Java 开发者 Spring
理解和解决Spring框架中的事务自调用问题
事务自调用问题是由于 Spring AOP 代理机制引起的,当方法在同一个类内部自调用时,事务注解将失效。通过使用代理对象调用、将事务逻辑分离到不同类中或使用 AspectJ 模式,可以有效解决这一问题。理解和解决这一问题,对于保证 Spring 应用中的事务管理正确性至关重要。掌握这些技巧,可以提高开发效率和代码的健壮性。
274 13