欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2024-03-08 23:36:40
混合事务 在orm框架的事务管理器的事务内,使用jdbctemplate执行sql是不会纳入事务管理的。 下面进行源码分析,看为什么必须要在datasourcetran...

混合事务
在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>