深入解析Java的Spring框架中的混合事务与bean的区分
混合事务
在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");
Map<Object,Object>map=resources.get();
//setThreadLocalMapifnonefound
if(map==null){
map=newHashMap<Object,Object>();
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
<beanname="xmlPageManager"class="org.apache.jetspeed.page.psml.CastorXmlPageManager"init-method="init"destroy-method="destroy"> <metakey="j2:cat"value="xmlPageManagerorpageSerializer"/> <constructor-argindex="0"> <refbean="IdGenerator"/> </constructor-arg> <constructor-argindex="1"> <refbean="xmlDocumentHandlerFactory"/> </constructor-arg> …… </bean> <beanid="dbPageManager"class="org.apache.jetspeed.page.impl.DatabasePageManager"init-method="init"destroy-method="destroy"> <metakey="j2:cat"value="dbPageManagerorpageSerializer"/> <!--OJBconfigurationfileresourcepath--> <constructor-argindex="0"> <value>JETSPEED-INF/ojb/page-manager-repository.xml</value> </constructor-arg> <!--fragmentidgenerator--> <constructor-argindex="1"> <refbean="IdGenerator"/> </constructor-arg> …… </bean>
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
<beanclass="org.springframework.beans.factory.config.BeanReferenceFactoryBean"> <metakey="j2:cat"value="xmlPageManager"/> <metakey="j2:alias"value="org.apache.jetspeed.page.PageManager"/> <propertyname="targetBeanName"value="xmlPageManager"/> </bean> <beanclass="org.springframework.beans.factory.config.BeanReferenceFactoryBean"> <metakey="j2:cat"value="dbPageManager"/> <metakey="j2:alias"value="org.apache.jetspeed.page.PageManager"/> <propertyname="targetBeanName"value="dbPageManager"/> </bean> <beanid="org.apache.jetspeed.layout.PageLayoutComponent" class="org.apache.jetspeed.layout.impl.PageLayoutComponentImpl"> <metakey="j2:cat"value="default"/> <constructor-argindex="0"> <refbean="org.apache.jetspeed.page.PageManager"/> </constructor-arg> <constructor-argindex="1"> <value>jetspeed-layouts::VelocityOneColumn</value> </constructor-arg> </bean>