配色: 字号:
深入解析Java的Spring框架中的混合事务与bean的区分
2016-10-14 | 阅:  转:  |  分享 
  
深入解析Java的Spring框架中的混合事务与bean的区分

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

混合事务

在ORM框架的事务管理器的事务内,使用JdbcTemplate执行SQL是不会纳入事务管理的。

下面进行源码分析,看为什么必须要在DataSourceTransactionManager的事务内使用JdbcTemplate。

1.开启事务

DataSourceTransactionManager

protectedvoiddoBegin(Objecttransaction,TransactionDefinitiondefinition){

DataSourceTransactionObjecttxObject=(DataSourceTransactionObject)transaction;

Connectioncon=null;

try{

if(txObject.getConnectionHolder()==null||

txObject.getConnectionHolder().isSynchronizedWithTransaction()){

ConnectionnewCon=this.dataSource.getConnection();

if(logger.isDebugEnabled()){

logger.debug("AcquiredConnection["+newCon+"]forJDBCtransaction");

}

txObject.setConnectionHolder(newConnectionHolder(newCon),true);

}

txObject.getConnectionHolder().setSynchronizedWithTransaction(true);

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

IntegerpreviousIsolationLevel=DataSourceUtils.prepareConnectionForTransaction(con,definition);

txObject.setPreviousIsolationLevel(previousIsolationLevel);

//Switchtomanualcommitifnecessary.ThisisveryexpensiveinsomeJDBCdrivers,

//sowedon''twanttodoitunnecessarily(forexampleifwe''veexplicitly

//configuredtheconnectionpooltosetitalready).

if(con.getAutoCommit()){

txObject.setMustRestoreAutoCommit(true);

if(logger.isDebugEnabled()){

logger.debug("SwitchingJDBCConnection["+con+"]tomanualcommit");

}

con.setAutoCommit(false);

}

txObject.getConnectionHolder().setTransactionActive(true);

inttimeout=determineTimeout(definition);

if(timeout!=TransactionDefinition.TIMEOUT_DEFAULT){

txObject.getConnectionHolder().setTimeoutInSeconds(timeout);

}

//Bindthesessionholdertothethread.

if(txObject.isNewConnectionHolder()){

TransactionSynchronizationManager.bindResource(getDataSource(),txObject.getConnectionHolder());

}

}

catch(Exceptionex){

DataSourceUtils.releaseConnection(con,this.dataSource);

thrownewCannotCreateTransactionException("CouldnotopenJDBCConnectionfortransaction",ex);

}

}

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

2.绑定连接

publicstaticvoidbindResource(Objectkey,Objectvalue)throwsIllegalStateException{

ObjectactualKey=TransactionSynchronizationUtils.unwrapResourceIfNecessary(key);

Assert.notNull(value,"Valuemustnotbenull");

Mapmap=resources.get();

//setThreadLocalMapifnonefound

if(map==null){

map=newHashMap();

resources.set(map);

}

ObjectoldValue=map.put(actualKey,value);

//TransparentlysuppressaResourceHolderthatwasmarkedasvoid...

if(oldValueinstanceofResourceHolder&&((ResourceHolder)oldValue).isVoid()){

oldValue=null;

}

if(oldValue!=null){

thrownewIllegalStateException("Alreadyvalue["+oldValue+"]forkey["+

actualKey+"]boundtothread["+Thread.currentThread().getName()+"]");

}

if(logger.isTraceEnabled()){

logger.trace("Boundvalue["+value+"]forkey["+actualKey+"]tothread["+

Thread.currentThread().getName()+"]");

}

}

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

3.执行SQL

JdbcTemplate

publicObjectexecute(PreparedStatementCreatorpsc,PreparedStatementCallbackaction)

throwsDataAccessException{

Assert.notNull(psc,"PreparedStatementCreatormustnotbenull");

Assert.notNull(action,"Callbackobjectmustnotbenull");

if(logger.isDebugEnabled()){

Stringsql=getSql(psc);

logger.debug("ExecutingpreparedSQLstatement"+(sql!=null?"["+sql+"]":""));

}

Connectioncon=DataSourceUtils.getConnection(getDataSource());

PreparedStatementps=null;

try{

ConnectionconToUse=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);

}

Objectresult=action.doInPreparedStatement(psToUse);

handleWarnings(ps);

returnresult;

}

catch(SQLExceptionex){

//ReleaseConnectionearly,toavoidpotentialconnectionpooldeadlock

//inthecasewhentheexceptiontranslatorhasn''tbeeninitializedyet.

if(pscinstanceofParameterDisposer){

((ParameterDisposer)psc).cleanupParameters();

}

Stringsql=getSql(psc);

psc=null;

JdbcUtils.closeStatement(ps);

ps=null;

DataSourceUtils.releaseConnection(con,getDataSource());

con=null;

throwgetExceptionTranslator().translate("PreparedStatementCallback",sql,ex);

}

finally{

if(pscinstanceofParameterDisposer){

((ParameterDisposer)psc).cleanupParameters();

}

JdbcUtils.closeStatement(ps);

DataSourceUtils.releaseConnection(con,getDataSource());

}

}



4.获得连接

DataSourceUtils

publicstaticConnectiondoGetConnection(DataSourcedataSource)throwsSQLException{

Assert.notNull(dataSource,"NoDataSourcespecified");

ConnectionHolderconHolder=(ConnectionHolder)TransactionSynchronizationManager.getResource(dataSource);

if(conHolder!=null&&(conHolder.hasConnection()||conHolder.isSynchronizedWithTransaction())){

conHolder.requested();

if(!conHolder.hasConnection()){

logger.debug("FetchingresumedJDBCConnectionfromDataSource");

conHolder.setConnection(dataSource.getConnection());

}

returnconHolder.getConnection();

}

//Elseweeithergotnoholderoranemptythread-boundholderhere.

logger.debug("FetchingJDBCConnectionfromDataSource");

Connectioncon=dataSource.getConnection();

if(TransactionSynchronizationManager.isSynchronizationActive()){

logger.debug("RegisteringtransactionsynchronizationforJDBCConnection");

//UsesameConnectionforfurtherJDBCactionswithinthetransaction.

//Thread-boundobjectwillgetremovedbysynchronizationattransactioncompletion.

ConnectionHolderholderToUse=conHolder;

if(holderToUse==null){

holderToUse=newConnectionHolder(con);

}

else{

holderToUse.setConnection(con);

}

holderToUse.requested();

TransactionSynchronizationManager.registerSynchronization(

newConnectionSynchronization(holderToUse,dataSource));

holderToUse.setSynchronizedWithTransaction(true);

if(holderToUse!=conHolder){

TransactionSynchronizationManager.bindResource(dataSource,holderToUse);

}

}

returncon;

}

由此可见,DataSourceUtils也是通过TransactionSynchronizationManager获得连接的。所以只要JdbcTemplate与DataSourceTransactionManager有相同的DataSource,就一定能得到相同的数据库连接,自然就能正确地提交、回滚事务。



再以Hibernate为例来说明开篇提到的问题,看看为什么ORM框架的事务管理器不能管理JdbcTemplate。

5ORM事务管理器

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

















厖b











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











……



2.Bean过滤器

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



JetspeedBeanDefinitionFilter

publicbooleanmatch(BeanDefinitionbd)

{

StringbeanCategoriesExpression=(String)bd.getAttribute(CATEGORY_META_KEY);

booleanmatched=true;

if(beanCategoriesExpression!=null)

{

matched=((matcher!=null)&&matcher.match(beanCategoriesExpression));

}

returnmatched;

}

publicvoidregisterDynamicAlias(BeanDefinitionRegistryregistry,StringbeanName,BeanDefinitionbd)

{

Stringaliases=(String)bd.getAttribute(ALIAS_META_KEY);

if(aliases!=null)

{

StringTokenizerst=newStringTokenizer(aliases,",");

while(st.hasMoreTokens())

{

Stringalias=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时进行拦截。

?

publicclassFilteringXmlWebApplicationContextextendsXmlWebApplicationContext

{

privateJetspeedBeanDefinitionFilterfilter;

publicFilteringXmlWebApplicationContext(JetspeedBeanDefinitionFilterfilter,String[]configLocations,PropertiesinitProperties,ServletContextservletContext)

{

this(filter,configLocations,initProperties,servletContext,null);

}

publicFilteringXmlWebApplicationContext(JetspeedBeanDefinitionFilterfilter,String[]configLocations,PropertiesinitProperties,ServletContextservletContext,ApplicationContextparent)

{

super();

if(parent!=null)

{

this.setParent(parent);

}

if(initProperties!=null)

{

PropertyPlaceholderConfigurerppc=newPropertyPlaceholderConfigurer();

ppc.setIgnoreUnresolvablePlaceholders(true);

ppc.setSystemPropertiesMode(PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_FALLBACK);

ppc.setProperties(initProperties);

addBeanFactoryPostProcessor(ppc);

}

setConfigLocations(configLocations);

setServletContext(servletContext);

this.filter=filter;

}

protectedDefaultListableBeanFactorycreateBeanFactory()

{

returnnewFilteringListableBeanFactory(filter,getInternalParentBeanFactory());

}

}

publicclassFilteringListableBeanFactoryextendsDefaultListableBeanFactory

{

privateJetspeedBeanDefinitionFilterfilter;

publicFilteringListableBeanFactory(JetspeedBeanDefinitionFilterfilter,BeanFactoryparentBeanFactory)

{

super(parentBeanFactory);

this.filter=filter;

if(this.filter==null)

{

this.filter=newJetspeedBeanDefinitionFilter();

}

this.filter.init();

}

/

OverrideoftheregisterBeanDefinitionmethodtooptionallyfilteroutaBeanDefinitionand

ifrequesteddynamicallyregisteranbeanalias

/

publicvoidregisterBeanDefinition(StringbeanName,BeanDefinitionbd)

throwsBeanDefinitionStoreException

{

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






















class="org.apache.jetspeed.layout.www.visa158.comimpl.PageLayoutComponentImpl">











jetspeed-layouts::VelocityOneColumn



























献花(0)
+1
(本文系白狐一梦首藏)