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

公司的项目已经接近尾声了,总结一下项目中用到的技术,我发现项目中的有些东西还是挺模糊的,只是知道这么用就行了。并不清楚其中的原理。由于公司的项目比较老,是7年前的一个项目了,中间一直有人在维护,也是在这个过程中不断融入了新的东西,比如就项目的持久化这块来说,就用了ibatis、mybatis、hibernate、spring JDBC四种混合的框架。究其原因只能说是历史遗留问题,就不做过多的解释了。但是这么多持久化的框架如何协同工作的,尤其是事务的控制,一个系统中使用如此多的持久化框架是,他们是如何正确的进行事务控制的,甚至有些业务逻辑同时调用了使用这些不同框架的方法。这听起来确实有些困难。我们知道Spring为每种数据访问技术提供了相应的事务管理器,难道需要分别为它们配置对应的事务管理器吗?它们到底是如何协作和工作的呢?这些层出不穷的问题往往压制了开发人员使用联合军种的想法。

Spring事务管理器的应对

Spring抽象的DAO体系兼容多种数据访问技术,它们各有特色,各有千秋。像Hibernate是非常优秀的ORM实现方案,但对底层SQL的控制不太方便;而iBatis则通过模板化技术让你方便地控制SQL,但没有Hibernate那样高的开发效率;自由度最高的当然是直接使用SpringJDBC了,但它也是底层的,灵活的代价是代码的繁复。很难说哪种数据访问技术是最优秀的,只有在某种特定的场景下才能给出答案。所以在一个应用中,往往采用多个数据访问技术:一般是两种,一种采用ORM技术框架,而另一种采用偏JDBC的底层技术,两者珠联璧合,形成联合军种,共同御敌。

其实,在这个问题上,我们低估了Spring事务管理的能力。
如果你采用了一个高端ORM技术(Hibernate、JPA、JDO),同时采用一个JDBC技术(Spring JDBC、iBatis),
由于前者的会话(Session)是对后者连接(Connection)的封装,Spring会“足够智能地”在同一个事务线程让前者的会话封装后者的连接。
所以,我们只要直接采用前者的事务管理器就可以了。

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

package com.baobaotao.mixdao;

…

@Service("userService")
public class UserService extends BaseService {
    @Autowired
    private HibernateTemplate hibernateTemplate;

    @Autowired
    private ScoreService scoreService;

    public void logon(String userName) {

        //①通过Hibernate技术访问数据
        System.out.println("before updateLastLogonTime()..");
        updateLastLogonTime(userName);
        System.out.println("end updateLastLogonTime()..");

        //②通过JDBC技术访问数据
        System.out.println("before scoreService.addScore()..");
        scoreService.addScore(userName, 20);
        System.out.println("end scoreService.addScore()..");
    }

    public void updateLastLogonTime(String userName) {
        User user = hibernateTemplate.get(User.class,userName);
        user.setLastLogonTime(System.currentTimeMillis());
        hibernateTemplate.update(user);

        //③这句很重要,请看下文的分析
        hibernateTemplate.flush();
    }
}

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

package com.baobaotao.mixdao;

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("scoreService")
public class ScoreService extends BaseService{

    @Autowired
    private JdbcTemplate jdbcTemplate;

    public void addScore(String userName, int toAdd) {
        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("[scoreUserService.addScore]激活连接数量:"
                         +basicDataSource.getNumActive());
    }
}

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

 …
    <!--①使用Hibernate事务管理器 -->
    <bean id="hiberManager"
          class="org.springframework.orm.hibernate3.HibernateTransactionManager"
          p:sessionFactory-ref="sessionFactory"/>

    <!--②使UserService及ScoreService的公用方法都拥有事务 -->
    <aop:config proxy-target-class="true">
        <aop:pointcut id="serviceJdbcMethod"
                      expression="within(com.baobaotao.mixdao.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>

</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 [[email protected]] for Hibernate transaction 
… 
Exposing Hibernate transaction as JDBC transaction [jdbc:mysql://localhost:3306/sampledb, [email protected], 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 [[email protected]] 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 [[email protected]] 
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 [[email protected]] 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和SpringJDBC数据访问技术,它们将工作于同一事务上下文中。但是使用SpringJDBC访问数据时,Hibernate的一级或二级缓存得不到同步,此外,一级缓存延迟数据同步机制可能会覆盖SpringJDBC数据更改的结果。

由于混合数据访问技术方案存在“事务同步而缓存不同步”的情况,所以最好用Hibernate进行读写操作,而只用SpringJDBC进行读操作。如用Spring JDBC进行简要列表的查询,而用Hibernate对查询出的数据进行维护。

如果确实要同时使用Hibernate和Spring JDBC读写数据,则必须充分考虑到Hibernate缓存机制引发的问题:必须整体分析数据维护逻辑,根据需要及时调用Hibernate的flush()方法,以免覆盖Spring JDBC的更改,在Spring JDBC更改数据库时,维护Hibernate的缓存。由于方法调用顺序的不同都可能影响数据的同步性,因此很容易发生问题,这会极大提高数据访问程序的复杂性。所以笔者郑重建议不要同时使用Spring JDBC和Hibernate对数据进行写操作。

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

http://blog.csdn.net/hy6688_/article/details/44783865

时间: 2024-12-24 09:13:13

Spring事务管理--多个ORM框架在使用时的情况分析的相关文章

框架 day37 Spring事务管理,整合web,SSH整合,SSH整合注解

1     事务管理 1.1   回顾事务     事务:一组业务操作,要么全部成功,要么全部不成功.     事务特性:ACID 原子性:整体 一致性:数据(完整) 隔离性:并发(多个事务) 持久性:结果     隔离问题:脏读.不可重复读.幻读(虚读)     隔离级别:4个 readuncommitted 读未提交,存在3个问题. readcommitted 读已提交,解决:脏读:存在2个. repeatableread 可重复读,解决:脏读.不可重复读:存在1个 serializ

[Spring框架]Spring 事务管理基础入门总结.

前言:在之前的博客中已经说过了数据库的事务, 不过那里面更多的是说明事务的一些锁机制, 今天来说一下Spring管理事务的一些基础知识. 之前的文章: [数据库事务与锁]详解一: 彻底理解数据库事务一, 什么是事务 事务是逻辑上一组操作,这组操作要么全都成功,要么全都失败. 事务的属性: ACID原子性(Atomicity): 事务作为一个整体被执行,包含在其中的对数据的操作要么全部被执行,要么都不执行.一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态.

Spring事务管理(详解+实例)

写这篇博客之前我首先读了<Spring in action>,之后在网上看了一些关于Spring事务管理的文章,感觉都没有讲全,这里就将书上的和网上关于事务的知识总结一下,参考的文章如下: Spring事务机制详解 Spring事务配置的五种方式 Spring中的事务管理实例详解 1 初步理解 理解事务之前,先讲一个你日常生活中最常干的事:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是要么都执行要么都

Spring事务管理

写这篇博客之前我首先读了<spring in action>,之后在网上看了一些关于Spring事务管理的文章,感觉都没有讲全,这里就将书上的和网上关于事务的知识总结一下,参考的文章如下: Spring事务机制详解 Spring事务配置的五种方式 Spring中的事务管理实例详解 1 初步理解 理解事务之前,先讲一个你日常生活中最常干的事:取钱. 比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱:然后ATM出1000元钱.这两个步骤必须是要么都执行要么都

Spring 事务管理高级应用难点剖析--转

第 1 部分 http://www.ibm.com/search/csass/search/?q=%E4%BA%8B%E5%8A%A1&sn=dw&lang=zh&cc=CN&en=utf&hpp=20&dws=cndw&lo=zh 概述 Spring 最成功,最吸引人的地方莫过于轻量级的声明式事务管理,仅此一点,它就宣告了重量级 EJB 容器的覆灭.Spring 声明式事务管理将开发者从繁复的事务管理代码中解脱出来,专注于业务逻辑的开发上,这是一件

Spring 事务管理详情介绍

一.事务管理介绍 事务是现代数据库理论中的核心概念之一,是逻辑上的一组操作,这组操作要么全都成功,要么全都失败.如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务.当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交.由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚到最初的系统状态.分别是:提交事务(调用commit()方法).回滚事务(失败提交-调用rollBack()方法).有如下优点: 1.为不同的事务API提供一致的编程模型,如JTA,JD

Spring源码阅读:Spring事务管理的基础

上一节了解了全局事务与局部事务以及Spring提供的两种事务模式:编程式事务与声明式事务. 不论是编程式的事务处理,还是声明式的事务处理.他们都要对局部事务和全局事务以支持,也就是说要对JDBC进行支持.ORM框架,同时也要对JTA进行支持.他们的公共部分是commit,rollback.通过这一节的了解,我相信以后配置Spring事务时,就不需要在去网上查资料了或者去查Spring的参考文档了. 因此,Spring设计了如下的事务管理框架: 从上面的类图中和容易可以看出分为三部分:Platfo

【转】Spring事务管理

原文链接 在 Spring 中,事务是通过 TransactionDefinition 接口来定义的.该接口包含与事务属性有关的方法.具体如清单 1 所示: 清单 1. TransactionDefinition 接口中定义的主要方法 public interface TransactionDefinition{ int getIsolationLevel();//isolation 隔离 int getPropagationBehavior();//Propagation 传播 int get

Spring事务管理 ——》java service

java的事务处理,如果对数据库进行多次操作,每一次的执行或步骤都是一个事务.如果数据库操作在某一步没有执行或出现异常而导致事务失败,这样有的事务被执行有的就没有被执行,从而就有了事务的回滚,取消先前的操作..... 注:在Java中使用事务处理,首先要求数据库支持事务.如使用MySQL的事务功能,就要求MySQL的表类型为Innodb才支持事务.否则,在Java程序中做了commit或rollback,但在数据库中根本不能生效. 1.JavaBean中使用JDBC方式进行事务处理 public