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

作者:cdai 时间:2021-12-28 19:48:41 

混合事务
在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>
标签:Java,Spring
0
投稿

猜你喜欢

  • WinForm实现读取Resource中文件的方法

    2021-05-25 18:29:45
  • Java中的javaBean、vo、entity、domain和pojo

    2023-03-30 21:41:20
  • 使用java.nio.file 库优雅的操作文件详解

    2022-09-20 00:51:12
  • JFinal实现伪静态的方法

    2023-07-17 12:11:37
  • Mybatis条件if test如何使用枚举值

    2023-11-19 14:15:33
  • Android开发实现模仿微信小窗口功能【Dialog对话框风格窗口】

    2022-01-27 10:11:01
  • Android中的Intent Filter匹配规则简介

    2021-07-29 16:02:30
  • Java实现简易学生管理系统

    2022-10-16 19:26:49
  • Javaweb 鼠标移入移出表格颜色变化的实现

    2021-08-31 00:03:08
  • C#图像伪彩色处理方法

    2022-09-23 10:51:52
  • Android activity动画不生效原因及解决方案总结

    2022-12-18 16:31:41
  • java1.8安装及环境变量配置教程

    2023-04-29 10:36:49
  • 全面了解Java中的内部类和匿名类

    2023-01-05 09:14:23
  • rocketmq client 日志的问题处理方式

    2023-07-15 08:10:32
  • Git工具 conflict冲突问题解决方案

    2023-07-27 10:27:30
  • Spring Boot启动过程全面解析(三)

    2023-09-13 13:16:39
  • C#4.0新特性之协变与逆变实例分析

    2022-02-18 00:17:28
  • java 中的instanceof用法详解及instanceof是什么意思(推荐)

    2023-06-07 13:52:27
  • 使用SmtpClient发送邮件的方法

    2022-12-07 16:32:58
  • C#使用GUID(全局统一标识符)

    2022-08-23 10:25:25
  • asp之家 软件编程 m.aspxhome.com