In depth analysis of the distinction between mixed transactions and beans in Java's Spring framework

  • 2020-04-01 04:37:20
  • OfStack

Mixed transaction
Executing SQL using JdbcTemplate within the transaction manager of the ORM framework does not incorporate transaction management.
The following source code analysis, see why must use JdbcTemplate within DataSourceTransactionManager transactions.

1. Start a transaction
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);
          }
     }

The doBegin() method binds a database connection that has already started a transaction to a ThreadLocal variable with the data source name Key and the ConnectionHolder(which holds the connection) as its Value.

2. Bind connections


     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()+ "]");
          }
     }

The resources variable is the ThreadLocal variable mentioned above, so that the subsequent JdbcTemplate can find the database connection using the DataSource as the Key.

3. Execute 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. Get connected
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;
     }

Thus, also get connected by TransactionSynchronizationManager DataSourceUtils. So as long as there is the same DataSource JdbcTemplate and DataSourceTransactionManager, we will be able to get the same database connection, nature can correctly commit, rollback transaction.
 
Using Hibernate as an example to illustrate the problems mentioned at the beginning, see why the ORM framework's transaction manager cannot manage the JdbcTemplate.

5 ORM transaction manager
HibernateTransactionManager


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

Because the ORM framework does not directly inject the DataSource into the TransactionManager for use, but USES its own SessionFactory and other objects to manipulate the DataSource, just like the Hibernate TransactionManager above. So although perhaps SessionFactory JdbcTemplate and the bottom are the same data source, but because in TransactionSynchronizationManager binding when using different Key (a SessionFactory, one is the dataSource name), so the JdbcTemplate execution time is can't get the ORM transaction manager to open the database connection transaction.


The distinction between the bean
A Spring configuration file in a public project may be referenced by multiple projects. Because each project may require only a portion of the beans in the common project, the Spring container for these projects needs to distinguish which beans to create when it starts.
1. Application examples
Take, for example, a configuration in Jetspeed, the Apache open source framework: 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. The Bean filter
JetspeedBeanDefinitionFilter analytical each Bean definition in the Spring container, will take out the above Bean configuration j2: cat corresponding value, such as dbPageManageror pageSerializer. This part is then matched as a regular expression with the current Key(read from the configuration file). Only matched beans are created by the Spring container.
 
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);
        }
      }
    }
  }

The value of CATEGORY_META_KEY in the match() method is j2:cat, and the matcher class holds the current Key and is responsible for matching the current Key with the regular expression for each Bean.
RegisterDynamicAlias does this: after a successful Bean match, the custom Spring container invokes this method to register the alias for the Bean. See the source code in 1.3 below.

3. Customize the Spring container
Customize a Spring container, override the registerBeanDefinition() method, and intercept when Spring registers beans.


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();
  }
 
  
  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. Alias beans
Use the BeanReferenceFactoryBean factory Bean to wrap the two beans configured above (xmlPageManager and dbPageManager). Match the Key into its own, and the implementation switches between the two implementations by configuring the current Key. The aliases are matched together so that the Bean that references them directly references the alias. For example, the following 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> 


Related articles: