深入解析Java的Spring框架中的混合事务与bean的区分

混合事务
在ORM框架的事务管理器的事务内,使用JdbcTemplate执行SQL是不会纳入事务管理的。
下面进行源码分析,看为什么必须要在DataSourceTransactionManager的事务内使用JdbcTemplate。

1.开启事务
DataSourceTransactionManager

     protected void doBegin(Object transaction,TransactionDefinition definition) {
          DataSourceTransactionObjecttxObject = (DataSourceTransactionObject) transaction;
          Connection con = null;

          try {
              if(txObject.getConnectionHolder() == null ||
                        txObject.getConnectionHolder().isSynchronizedWithTransaction()){
                   ConnectionnewCon = this.dataSource.getConnection();
                   if(logger.isDebugEnabled()) {
                        logger.debug("AcquiredConnection [" + newCon + "] for JDBC transaction");
                   }
                   txObject.setConnectionHolder(newConnectionHolder(newCon), true);
              }

              txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
              con =txObject.getConnectionHolder().getConnection();

              IntegerpreviousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con,definition);
              txObject.setPreviousIsolationLevel(previousIsolationLevel);

              // Switch to manualcommit if necessary. This is very expensive in some JDBC drivers,
              // so we don't wantto do it unnecessarily (for example if we've explicitly
              // configured theconnection pool to set it already).
              if(con.getAutoCommit()) {
                   txObject.setMustRestoreAutoCommit(true);
                   if(logger.isDebugEnabled()) {
                        logger.debug("SwitchingJDBC Connection [" + con + "] to manual commit");
                   }
                   con.setAutoCommit(false);
              }
              txObject.getConnectionHolder().setTransactionActive(true);

              int timeout =determineTimeout(definition);
              if (timeout !=TransactionDefinition.TIMEOUT_DEFAULT) {
                   txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
              }

              // Bind the sessionholder to the thread.
              if(txObject.isNewConnectionHolder()) {
                   TransactionSynchronizationManager.bindResource(getDataSource(),txObject.getConnectionHolder());
              }
          }

          catch (Exception ex) {
              DataSourceUtils.releaseConnection(con,this.dataSource);
              throw newCannotCreateTransactionException("Could not open JDBC Connection fortransaction", ex);
          }
     }

doBegin()方法会以数据源名为Key,ConnectionHolder(保存着连接)为Value,将已经开启事务的数据库连接绑定到一个ThreadLocal变量上。

2.绑定连接

     public static void bindResource(Objectkey, Object value) throws IllegalStateException {
          Object actualKey =TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);
          Assert.notNull(value,"Value must not be null");
          Map<Object, Object> map = resources.get();
          // set ThreadLocal Map ifnone found
          if (map == null) {
              map = newHashMap<Object, Object>();
              resources.set(map);
          }
          Object oldValue = map.put(actualKey, value);
          // Transparently suppress aResourceHolder that was marked as void...
          if (oldValue instanceofResourceHolder && ((ResourceHolder) oldValue).isVoid()) {
              oldValue = null;
          }
          if (oldValue != null) {
              throw newIllegalStateException("Already value [" + oldValue + "] for key[" +
                        actualKey+ "] bound to thread [" + Thread.currentThread().getName() +"]");
          }
          if (logger.isTraceEnabled()){
              logger.trace("Boundvalue [" + value + "] for key [" + actualKey + "] to thread[" +
                        Thread.currentThread().getName()+ "]");
          }
     }

resources变量就是上面提到的ThreadLocal变量,这样后续JdbcTemplate就可以用DataSource作为Key,查找到这个数据库连接。

3.执行SQL
JdbcTemplate

     public Objectexecute(PreparedStatementCreator psc, PreparedStatementCallback action)
              throwsDataAccessException {

          Assert.notNull(psc,"PreparedStatementCreator must not be null");
          Assert.notNull(action,"Callback object must not be null");
          if (logger.isDebugEnabled()){
              String sql =getSql(psc);
              logger.debug("Executingprepared SQL statement" + (sql != null ? " [" + sql +"]" : ""));
          }

          Connection con = DataSourceUtils.getConnection(getDataSource());
          PreparedStatement ps = null;
          try {
              Connection conToUse= con;
              if(this.nativeJdbcExtractor != null &&
                        this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()){
                   conToUse =this.nativeJdbcExtractor.getNativeConnection(con);
              }
              ps =psc.createPreparedStatement(conToUse);
              applyStatementSettings(ps);
              PreparedStatementpsToUse = ps;
              if(this.nativeJdbcExtractor != null) {
                   psToUse =this.nativeJdbcExtractor.getNativePreparedStatement(ps);
              }
              Object result =action.doInPreparedStatement(psToUse);
              handleWarnings(ps);
              return result;
          }
          catch (SQLException ex) {
              // ReleaseConnection early, to avoid potential connection pool deadlock
              // in the case whenthe exception translator hasn't been initialized yet.
              if (psc instanceofParameterDisposer) {
                   ((ParameterDisposer)psc).cleanupParameters();
              }
              String sql =getSql(psc);
              psc = null;
              JdbcUtils.closeStatement(ps);
              ps = null;
              DataSourceUtils.releaseConnection(con,getDataSource());
              con = null;
              throwgetExceptionTranslator().translate("PreparedStatementCallback", sql,ex);
          }
          finally {
              if (psc instanceofParameterDisposer) {
                   ((ParameterDisposer)psc).cleanupParameters();
              }
              JdbcUtils.closeStatement(ps);
              DataSourceUtils.releaseConnection(con,getDataSource());
          }
     }

4.获得连接
DataSourceUtils

    public static Connection doGetConnection(DataSourcedataSource) throws SQLException {
          Assert.notNull(dataSource,"No DataSource specified");

          ConnectionHolder conHolder = (ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);
          if (conHolder != null&& (conHolder.hasConnection() ||conHolder.isSynchronizedWithTransaction())) {
              conHolder.requested();
              if(!conHolder.hasConnection()) {
                   logger.debug("Fetchingresumed JDBC Connection from DataSource");
                   conHolder.setConnection(dataSource.getConnection());
              }
              returnconHolder.getConnection();
          }
          // Else we either got noholder or an empty thread-bound holder here.

          logger.debug("FetchingJDBC Connection from DataSource");
          Connection con =dataSource.getConnection();

          if (TransactionSynchronizationManager.isSynchronizationActive()){
              logger.debug("Registeringtransaction synchronization for JDBC Connection");
              // Use sameConnection for further JDBC actions within the transaction.
              // Thread-boundobject will get removed by synchronization at transaction completion.
              ConnectionHolderholderToUse = conHolder;
              if (holderToUse ==null) {
                   holderToUse= new ConnectionHolder(con);
              }
              else {
                   holderToUse.setConnection(con);
              }
              holderToUse.requested();
              TransactionSynchronizationManager.registerSynchronization(
                        newConnectionSynchronization(holderToUse, dataSource));
              holderToUse.setSynchronizedWithTransaction(true);
              if (holderToUse !=conHolder) {
                   TransactionSynchronizationManager.bindResource(dataSource,holderToUse);
              }
          }

          return con;
     }

由此可见,DataSourceUtils也是通过TransactionSynchronizationManager获得连接的。所以只要JdbcTemplate与DataSourceTransactionManager有相同的DataSource,就一定能得到相同的数据库连接,自然就能正确地提交、回滚事务。
 
再以Hibernate为例来说明开篇提到的问题,看看为什么ORM框架的事务管理器不能管理JdbcTemplate。

5 ORM事务管理器
HibernateTransactionManager

if(txObject.isNewSessionHolder()) {
     TransactionSynchronizationManager.bindResource(getSessionFactory(),txObject.getSessionHolder());
}

因为ORM框架都不是直接将DataSource注入到TransactionManager中使用的,而是像上面Hibernate事务管理器一样,使用自己的SessionFactory等对象来操作DataSource。所以尽管可能SessionFactory和JdbcTemplate底层都是一样的数据源,但因为在TransactionSynchronizationManager中绑定时使用了不同的Key(一个是sessionFactory名,一个是dataSource名),所以JdbcTemplate执行时是拿不到ORM事务管理器开启事务的那个数据库连接的。

bean的区分
一个公共工程中的Spring配置文件,可能会被多个工程引用。因为每个工程可能只需要公共工程中的一部分Bean,所以这些工程的Spring容器启动时,需要区分开哪些Bean要创建出来。
1.应用实例
以Apache开源框架Jetspeed中的一段配置为例:page-manager.xml

 <bean name="xmlPageManager"class="org.apache.jetspeed.page.psml.CastorXmlPageManager"init-method="init" destroy-method="destroy">
  <meta key="j2:cat" value="xmlPageManager orpageSerializer" />
  <constructor-arg index="0">
   <ref bean="IdGenerator"/>
  </constructor-arg>
  <constructor-arg index="1">
   <refbean="xmlDocumentHandlerFactory" />
  </constructor-arg>
  ……
 </bean>

 <bean id="dbPageManager"class="org.apache.jetspeed.page.impl.DatabasePageManager"init-method="init" destroy-method="destroy">
  <meta key="j2:cat" value="dbPageManager orpageSerializer" />
  <!-- OJB configuration file resourcepath -->
  <constructor-arg index="0">
   <value>JETSPEED-INF/ojb/page-manager-repository.xml</value>
  </constructor-arg>
  <!-- fragment id generator -->
  <constructor-arg index="1">
   <ref bean="IdGenerator"/>
  </constructor-arg>
  ……
 </bean>

2.Bean过滤器
JetspeedBeanDefinitionFilter在Spring容器解析每个Bean定义时,会取出上面Bean配置中j2:cat对应的值,例如dbPageManageror pageSerializer。然后将这部分作为正则表达式与当前的Key(从配置文件中读出)进行匹配。只有匹配上的Bean,才会被Spring容器创建出来。
 
JetspeedBeanDefinitionFilter

  public boolean match(BeanDefinition bd)
  {
    String beanCategoriesExpression = (String)bd.getAttribute(CATEGORY_META_KEY);
    boolean matched = true;
    if (beanCategoriesExpression != null)
    {
      matched = ((matcher != null)&& matcher.match(beanCategoriesExpression));
    }
    return matched;
}

  public void registerDynamicAlias(BeanDefinitionRegistry registry, String beanName,BeanDefinition bd)
  {
    String aliases =(String)bd.getAttribute(ALIAS_META_KEY);
    if (aliases != null)
    {
      StringTokenizer st = newStringTokenizer(aliases, " ,");
      while (st.hasMoreTokens())
      {
        String alias = st.nextToken();
        if (!alias.equals(beanName))
        {
          registry.registerAlias(beanName, alias);
        }
      }
    }
  }

match()方法中的CATEGORY_META_KEY的值就是j2:cat,matcher类中保存的就是当前的Key,并负责将当前Key与每个Bean的进行正则表达式匹配。
registerDynamicAlias的作用是:在Bean匹配成功后,定制的Spring容器会调用此方法为Bean注册别名。详见下面1.3中的源码。

3.定制Spring容器
定制一个Spring容器,重写registerBeanDefinition()方法,在Spring注册Bean时进行拦截。

public class FilteringXmlWebApplicationContextextends XmlWebApplicationContext
{
  private JetspeedBeanDefinitionFilterfilter;

  publicFilteringXmlWebApplicationContext(JetspeedBeanDefinitionFilter filter, String[]configLocations, Properties initProperties, ServletContext servletContext)
  {
    this(filter, configLocations,initProperties, servletContext, null);
  }

  publicFilteringXmlWebApplicationContext(JetspeedBeanDefinitionFilter filter, String[]configLocations, Properties initProperties, ServletContext servletContext,ApplicationContext parent)
  {
    super();
    if (parent != null)
    {
      this.setParent(parent);
    }
    if (initProperties != null)
    {
      PropertyPlaceholderConfigurer ppc =new PropertyPlaceholderConfigurer();
      ppc.setIgnoreUnresolvablePlaceholders(true);
      ppc.setSystemPropertiesMode(PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_FALLBACK);
      ppc.setProperties(initProperties);
      addBeanFactoryPostProcessor(ppc);
    }
    setConfigLocations(configLocations);
    setServletContext(servletContext);
    this.filter = filter;
  }

  protected DefaultListableBeanFactorycreateBeanFactory()
  {
    return new FilteringListableBeanFactory(filter,getInternalParentBeanFactory());
  }
}

public classFilteringListableBeanFactory extends DefaultListableBeanFactory
{
  private JetspeedBeanDefinitionFilterfilter;

  public FilteringListableBeanFactory(JetspeedBeanDefinitionFilterfilter, BeanFactory parentBeanFactory)
  {
    super(parentBeanFactory);
    this.filter = filter;
    if (this.filter == null)
    {
      this.filter = newJetspeedBeanDefinitionFilter();
    }
    this.filter.init();
  }

  /**
   * Override of the registerBeanDefinitionmethod to optionally filter out a BeanDefinition and
   * if requested dynamically register anbean alias
   */
  public void registerBeanDefinition(StringbeanName, BeanDefinition bd)
      throws BeanDefinitionStoreException
  {
    if (filter.match(bd))
    {
      super.registerBeanDefinition(beanName, bd);
      if (filter != null)
      {
        filter.registerDynamicAlias(this, beanName, bd);
      }
    }
  }
}

4.为Bean起别名
使用BeanReferenceFactoryBean工厂Bean,将上面配置的两个Bean(xmlPageManager和dbPageManager)包装起来。将Key配成各自的,实现通过配置当前Key来切换两种实现。别名都配成一个,这样引用他们的Bean就直接引用这个别名就行了。例如下面的PageLayoutComponent。
 
page-manager.xml

<bean class="org.springframework.beans.factory.config.BeanReferenceFactoryBean">
  <meta key="j2:cat"value="xmlPageManager" />
  <meta key="j2:alias"value="org.apache.jetspeed.page.PageManager" />
  <propertyname="targetBeanName" value="xmlPageManager" />
 </bean>

 <bean class="org.springframework.beans.factory.config.BeanReferenceFactoryBean">
  <meta key="j2:cat"value="dbPageManager" />
  <meta key="j2:alias"value="org.apache.jetspeed.page.PageManager" />
  <propertyname="targetBeanName" value="dbPageManager" />
 </bean>

 <bean id="org.apache.jetspeed.layout.PageLayoutComponent"
  class="org.apache.jetspeed.layout.impl.PageLayoutComponentImpl">
  <meta key="j2:cat"value="default" />
  <constructor-arg index="0">
   <refbean="org.apache.jetspeed.page.PageManager" />
  </constructor-arg>
  <constructor-arg index="1">
   <value>jetspeed-layouts::VelocityOneColumn</value>
  </constructor-arg>
 </bean> 
时间: 2016-01-24

Java 基础之事务详细介绍

java 事务详解 一.什么是事务 事务是访问数据库的一个操作序列,数据库应用系统通过事务集来完成对数据库的存取.事务的正确执行使得数据库从一种状态转换成另一种状态. 事务必须服从ISO/IEC所制定的ACID原则.ACID是原子性(atomicity).一致性(consistency).隔离性(isolation)和持久性(durability)的缩写事务必须服从ISO/IEC所制定的ACID原则.ACID是原子性(atomicity).一致性(consistency).隔离性(isolati

详解Java的Spring框架中的事务管理方式

数据库事务是被当作单个工作单元的操作序列.这些操作要么全部完成或全部不成功.事务管理是面向企业应用程序,以确保数据的完整性和一致性RDBMS中的重要组成部分.事务的概念可以用下面的描述为ACID四个关键属性来描述: 原子性: 一个事务应该被视为单个操作单元表示的操作的任一整个序列是成功的或不成功的. 一致性: 这代表了数据库的参照完整性,在桌等唯一主键的一致性 隔离性: 可能有很多事务处理相同的数据集的同时,每个事务都应由他人隔离,以防止数据损坏. 持久性: 一旦事务完成,本次事务的结果必须作出

Java中JDBC事务与JTA分布式事务总结与区别

Java事务的类型有三种:JDBC事务.JTA(Java Transaction API)事务.容器事务.常见的容器事务如Spring事务,容器事务主要是J2EE应用服务器提供的,容器事务大多是基于JTA完成,这是一个基于JNDI的,相当复杂的API实现.所以本文暂不讨论容器事务.本文主要介绍J2EE开发中两个比较基本的事务:JDBC事务和JTA事务. JDBC事务 JDBC的一切行为包括事务是基于一个Connection的,在JDBC中是通过Connection对象进行事务管理.在JDBC中,

Java事务的个人理解小结

一.什么是Java事务 通常的观念认为,事务仅与数据库相关.        事务必须服从ISO/IEC所制定的ACID原则.ACID是原子性(atomicity).一致性(consistency).隔离性(isolation)和持久性(durability)的缩写.事务的原子性表示事务执行过程中的任何失败都将导致事务所做的任何修改失效.一致性表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态.隔离性表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见.持久性表示

Java 事务详解及简单应用实例

Java事务的简单使用  Java事务在一些面试中会被问到. 面试的时候,我们首先要回答的是:事务能够保证数据的完整性和一致性. 如果功力深厚点的话:就说一些原理(任务开始前先设置不提交任务,在所有任务完成后再提交任务, 如果任务在中间断开,就执行回滚,撤销前面执行的任务),简单一点就举个的例子(比如存钱和取钱的问题. 比如:银行在两个账户之间转账,从A账户转入B账户1000元,系统先减少A账户的1000元,然后再为B账号增加1000元.如果全部执行成功,数据库处于一致性:如果仅执行完A账户金额

详解Java的MyBatis框架中的事务处理

一.MyBatis单独使用时,使用SqlSession来处理事务: public class MyBatisTxTest { private static SqlSessionFactory sqlSessionFactory; private static Reader reader; @BeforeClass public static void setUpBeforeClass() throws Exception { try { reader = Resources.getResourc

深入理解Java事务的原理与应用

一.什么是JAVA事务 通常的观念认为,事务仅与数据库相关. 事务必须服从ISO/IEC所制定的ACID原则.ACID是原子性(atomicity).一致性(consistency).隔离性 (isolation)和持久性(durability)的缩写.事务的原子性表示事务执行过程中的任何失败都将导致事务所做的任何修改失效.一致性表示 当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态.隔离性表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见.持 久性表示已提交的

Java Spring 事务回滚详解

spring 事务回滚 1.遇到的问题 当我们一个方法里面有多个数据库保存操作的时候,中间的数据库操作发生的错误.伪代码如下: public method() { Dao1.save(Person1); Dao1.save(Person2); Dao1.save(Person2);//假如这句发生了错误,前面的两个对象会被保存到数据库中 Dao1.save(Person2); } 期待的情况:发生错误之前的所有数据库保存操作都回滚,即不保存 正常情况:前面的数据库操作会被执行,而发生数据库操作错

在Java的JDBC使用中设置事务回滚的保存点的方法

新的JDBC3.0保存点的接口提供了额外的事务控制.他们的环境中,如Oracle的PL/ SQL中的大多数现代的DBMS支持保存点. 当设置一个保存点在事务中定义一个逻辑回滚点.如果发生错误,过去一个保存点,则可以使用rollback方法来撤消要么所有的改变或仅保存点之后所做的更改. Connection对象有两个新的方法,可帮助管理保存点: setSavepoint(String savepointName): 定义了一个新的保存点.它也返回一个Savepoint 对象. releaseSav

详解Java的JDBC API中事务的提交和回滚

如果JDBC连接是在自动提交模式下,它在默认情况下,那么每个SQL语句都是在其完成时提交到数据库. 这可能是对简单的应用程序,但有三个原因,你可能想关闭自动提交和管理自己的事务: 为了提高性能 为了保持业务流程的完整性 使用分布式事务 若要控制事务,以及何时更改应用到数据库.它把单个SQL语句或一组SQL语句作为一个逻辑单元,而且如果任何语句失败,整个事务失败. 若要启用,而不是JDBC驱动程序默认使用auto-commit模式手动事务支持,使用Connection对象的的setAutoComm

详解Java的JDBC API的存储过程与SQL转义语法的使用

正如一个Connection对象创建Statement和PreparedStatement对象,它也创造了CallableStatement对象这将被用来执行调用数据库存储过程. 创建CallableStatement对象: 假设,需要执行以下Oracle存储过程: CREATE OR REPLACE PROCEDURE getEmpName (EMP_ID IN NUMBER, EMP_FIRST OUT VARCHAR) AS BEGIN SELECT first INTO EMP_FIRS

mysql实现事务的提交与回滚的实例详解

最近要对数据库的数据进行一个定时迁移,为了防止在执行过程sql语句因为某些原因报错而导致数据转移混乱,因此要对我们的脚本加以事务进行控制. 首先我们建一张tran_test表 CREATE TABLE tran_test( f1 VARCHAR(10) NOT NULL, f2 INT(1) DEFAULT NULL, PRIMARY KEY (f1) )ENGINE=INNODB CHARSET=utf8 我想对tran_test插入两条数据,但是为了防止插入中报错,因此我要把插入语句控制在一

详解Java字符串在内存中的存储位置

在JDK6的时候在Java虚拟机(这里指的是HotSpot)中内存区域分为本地方法栈.虚拟机栈.堆.程序计数器.方法区等,方法区又被称作永久代. 这里只说一下字符串的存储位置,在Java虚拟机内存中有个区域叫做运行时常量池,是方法区的一部分.在JDK6中其中存放的有类的版本.字段.方法.接口等描述信息以及常量池,常量池用来存放编译期间生成的各种字面量和符号引用,字符串就存储在这个位置.下面通过代码来看下现象. 这是JDK6的实验现象,Java虚拟机的配置如下: -XX:PermSize=5M -

详解Java如何改变字符串中的字符

今天做某度的笔试题遇到一个编程题需要用到字符串中的字符的即时改变.题中给出的一个String字符串.绞尽脑汁试图使用构建一个新的String的方式来做,而后发觉十分麻烦. 做题的时候只觉得StringBuilder/StringBuffer(由于这两个类的方法基本完全相同,下面只说其中一个,另一个也一样)中会有操作某字符的方法,想了半天没什么印象,所以这个题应该是直接挂了. 做完之后查了一下API.果然发现一个方法,如下图所示: cStringBuilder.setCharAt() 可以实现很方

详解Java的Hibernate框架中的Interceptor和Collection

Interceptor 讲到Interceptor,相信熟悉struts2的童鞋肯定不会陌生了,struts2可以自定义拦截器进行自己想要的一系列相关的工作.而这里我们说的Interceptor也是差不多相似的功能.  废话不说,直接来代码:  下面这个是MyInterceptor类,它实现了Interceptor接口: public String onPrepareStatement(String arg0) { return arg0; } public boolean onSave(Obj

详解Java去除json数据中的null空值问题

1.描述 @JsonInclude(JsonInclude.Include.NON_NULL)标记是jackson包提供的json序列化方法,已经集成于Springboot2.0中,此方法的配置意在可以对实体json序列化的时候进行对应的数值处理. 2.使用 用注解的方式放在标记类或者属性 @JsonInclude(JsonInclude.Include.NON_NULL) public class User implements Serializable { private String us

mysql实现事务的提交和回滚实例

mysql创建存储过程的官方语法为: 复制代码 代码如下: START TRANSACTION | BEGIN [WORK]COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]SET AUTOCOMMIT = {0 | 1} 我这里要说明的mysql事务处理多个SQL语句的回滚情况.比如说在一个存储过程中启动一个事务,这个事务同时往三个表中插入数据,每插完一张表需要