栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

深入解析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 map = resources.get();
   // set ThreadLocal Map ifnone found
   if (map == null) {
map = newHashMap();
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
 

 
  
  
   
  
  
   
  
  ……
 
 
 
  
  
  
   JETSPEED-INF/ojb/page-manager-repository.xml
  
  
  
   
  
  ……
 

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


  
  
  
 
 
 
  
  
  
 
 
 
  
  
   
  
  
   jetspeed-layouts::VelocityOneColumn
  
  

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/150629.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号