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

这篇文章主要介绍了Java的Spring框架中的混合事务与bean的区分,Spring是Java的SSH三大web开发框架之一,需要的朋友可以参考下

混合事务
在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(); } /** * 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

      jetspeed-layouts::VelocityOneColumn

以上就是深入解析Java的Spring框架中的混合事务与bean的区分的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » Java