深入解析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<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()+ "]"); } }
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
<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.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
<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>
上一篇: 简单讲解Java的Socket网络编程的多播与广播实现
下一篇: 自制PHP框架之模型与数据库