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

Mybaits 源码解析 (十二)----- Mybatis的事务如何被Spring管理?Mybatis和Spring事务中用的Connection是同一个吗?

程序员文章站 2023-11-08 19:39:34
不知道一些同学有没有这种疑问,为什么Mybtis中要配置dataSource,Spring的事务中也要配置dataSource?那么Mybatis和Spring事务中用的Connection是同一个吗?我们常用配置如下

不知道一些同学有没有这种疑问,为什么mybtis中要配置datasource,spring的事务中也要配置datasource?那么mybatis和spring事务中用的connection是同一个吗?我们常用配置如下

<!--会话工厂 -->
<bean id="sqlsessionfactory" class="org.mybatis.spring.sqlsessionfactorybean">
  <property name="datasource" ref="datasource" />
</bean>

<!--spring事务管理 -->
<bean id="transactionmanager" class="org.springframework.jdbc.datasource.datasourcetransactionmanager">
  <property name="datasource" ref="datasource" />
</bean>

<!--使用注释事务 -->
<tx:annotation-driven  transaction-manager="transactionmanager" />

看到没,sqlsessionfactory中配置了datasource,transactionmanager也配置了datasource,我们来回忆一下sqlsessionfactorybean这个类

 1 protected sqlsessionfactory buildsqlsessionfactory() throws ioexception {
 2 
 3     // 配置类
 4    configuration configuration;
 5     // 解析mybatis-config.xml文件,
 6     // 将相关配置信息保存到configuration
 7    xmlconfigbuilder xmlconfigbuilder = null;
 8    if (this.configuration != null) {
 9      configuration = this.configuration;
10      if (configuration.getvariables() == null) {
11        configuration.setvariables(this.configurationproperties);
12      } else if (this.configurationproperties != null) {
13        configuration.getvariables().putall(this.configurationproperties);
14      }
15     //资源文件不为空
16    } else if (this.configlocation != null) {
17      //根据configlocation创建xmlconfigbuilder,xmlconfigbuilder构造器中会创建configuration对象
18      xmlconfigbuilder = new xmlconfigbuilder(this.configlocation.getinputstream(), null, this.configurationproperties);
19      //将xmlconfigbuilder构造器中创建的configuration对象直接赋值给configuration属性
20      configuration = xmlconfigbuilder.getconfiguration();
21    } 
22    
23     //略....
24 
25    if (xmlconfigbuilder != null) {
26      try {
27        //解析mybatis-config.xml文件,并将相关配置信息保存到configuration
28        xmlconfigbuilder.parse();
29        if (logger.isdebugenabled()) {
30          logger.debug("parsed configuration file: '" + this.configlocation + "'");
31        }
32      } catch (exception ex) {
33        throw new nestedioexception("failed to parse config resource: " + this.configlocation, ex);
34      }
35    }
36     
37    if (this.transactionfactory == null) {
38      //事务默认采用springmanagedtransaction,这一块非常重要
39      this.transactionfactory = new springmanagedtransactionfactory();
40    }
41     // 为sqlsessionfactory绑定事务管理器和数据源
42     // 这样sqlsessionfactory在创建sqlsession的时候可以通过该事务管理器获取jdbc连接,从而执行sql
43    configuration.setenvironment(new environment(this.environment, this.transactionfactory, this.datasource));
44     // 解析mapper.xml
45    if (!isempty(this.mapperlocations)) {
46      for (resource mapperlocation : this.mapperlocations) {
47        if (mapperlocation == null) {
48          continue;
49        }
50        try {
51          // 解析mapper.xml文件,并注册到configuration对象的mapperregistry
52          xmlmapperbuilder xmlmapperbuilder = new xmlmapperbuilder(mapperlocation.getinputstream(),
53              configuration, mapperlocation.tostring(), configuration.getsqlfragments());
54          xmlmapperbuilder.parse();
55        } catch (exception e) {
56          throw new nestedioexception("failed to parse mapping resource: '" + mapperlocation + "'", e);
57        } finally {
58          errorcontext.instance().reset();
59        }
60 
61        if (logger.isdebugenabled()) {
62          logger.debug("parsed mapper file: '" + mapperlocation + "'");
63        }
64      }
65    } else {
66      if (logger.isdebugenabled()) {
67        logger.debug("property 'mapperlocations' was not specified or no matching resources found");
68      }
69    }
70 
71     // 将configuration对象实例作为参数,
72     // 调用sqlsessionfactorybuilder创建sqlsessionfactory对象实例
73    return this.sqlsessionfactorybuilder.build(configuration);
74 }

我们看第39行,mybatis集成spring后,默认使用的transactionfactory是springmanagedtransactionfactory,那我们就来看看其获取transaction的方法

private sqlsession opensessionfromconnection(executortype exectype, connection connection) {
    try {
      boolean autocommit;
      try {
        autocommit = connection.getautocommit();
      } catch (sqlexception e) {
        // failover to true, as most poor drivers
        // or databases won't support transactions
        autocommit = true;
      }      
      //从configuration中取出environment对象
      final environment environment = configuration.getenvironment();
      //从environment中取出transactionfactory
      final transactionfactory transactionfactory = gettransactionfactoryfromenvironment(environment);
      //创建transaction
      final transaction tx = transactionfactory.newtransaction(connection);
      //创建包含事务操作的执行器
      final executor executor = configuration.newexecutor(tx, exectype);
      //构建包含执行器的sqlsession
      return new defaultsqlsession(configuration, executor, autocommit);
    } catch (exception e) {
      throw exceptionfactory.wrapexception("error opening session.  cause: " + e, e);
    } finally {
      errorcontext.instance().reset();
    }
}

private transactionfactory gettransactionfactoryfromenvironment(environment environment) {
    if (environment == null || environment.gettransactionfactory() == null) {
      return new managedtransactionfactory();
    }
    //这里返回springmanagedtransactionfactory
    return environment.gettransactionfactory();
}

@override
public transaction newtransaction(datasource datasource, transactionisolationlevel level, boolean autocommit) {
    //创建springmanagedtransaction
    return new springmanagedtransaction(datasource);
}

springmanagedtransaction

也就是说mybatis的执行事务的事务管理器就切换成了springmanagedtransaction,下面我们再去看看springmanagedtransactionfactory类的源码:

public class springmanagedtransaction implements transaction {
    private static final log logger = logfactory.getlog(springmanagedtransaction.class);
    private final datasource datasource;
    private connection connection;
    private boolean isconnectiontransactional;
    private boolean autocommit;

    public springmanagedtransaction(datasource datasource) {
        assert.notnull(datasource, "no datasource specified");
        this.datasource = datasource;
    }

    public connection getconnection() throws sqlexception {
        if (this.connection == null) {
            this.openconnection();
        }

        return this.connection;
    }

    private void openconnection() throws sqlexception {
        //通过datasourceutils获取connection,这里和jdbctransaction不一样
        this.connection = datasourceutils.getconnection(this.datasource);
        this.autocommit = this.connection.getautocommit();
        this.isconnectiontransactional = datasourceutils.isconnectiontransactional(this.connection, this.datasource);
        if (logger.isdebugenabled()) {
            logger.debug("jdbc connection [" + this.connection + "] will" + (this.isconnectiontransactional ? " " : " not ") + "be managed by spring");
        }

    }

    public void commit() throws sqlexception {
        if (this.connection != null && !this.isconnectiontransactional && !this.autocommit) {
            if (logger.isdebugenabled()) {
                logger.debug("committing jdbc connection [" + this.connection + "]");
            }
            //通过connection提交,这里和jdbctransaction一样
            this.connection.commit();
        }

    }

    public void rollback() throws sqlexception {
        if (this.connection != null && !this.isconnectiontransactional && !this.autocommit) {
            if (logger.isdebugenabled()) {
                logger.debug("rolling back jdbc connection [" + this.connection + "]");
            }
            //通过connection回滚,这里和jdbctransaction一样
            this.connection.rollback();
        }

    }

    public void close() throws sqlexception {
        datasourceutils.releaseconnection(this.connection, this.datasource);
    }

    public integer gettimeout() throws sqlexception {
        connectionholder holder = (connectionholder)transactionsynchronizationmanager.getresource(this.datasource);
        return holder != null && holder.hastimeout() ? holder.gettimetoliveinseconds() : null;
    }
}

org.springframework.jdbc.datasource.datasourceutils#getconnection

public static connection getconnection(datasource datasource) throws cannotgetjdbcconnectionexception {
    try {
        return dogetconnection(datasource);
    }
    catch (sqlexception ex) {
        throw new cannotgetjdbcconnectionexception("could not get jdbc connection", ex);
    }
}

public static connection dogetconnection(datasource datasource) throws sqlexception {
    assert.notnull(datasource, "no datasource specified");
    //transactionsynchronizationmanager重点!!!有没有很熟悉的感觉??
    //还记得我们前面spring事务源码的分析吗?@transaction会创建connection,并放入threadlocal中
    //这里从threadlocal中获取connectionholder
    connectionholder conholder = (connectionholder)transactionsynchronizationmanager.getresource(datasource);
    if (conholder == null || !conholder.hasconnection() && !conholder.issynchronizedwithtransaction()) {
        logger.debug("fetching jdbc connection from datasource");
        //如果没有使用@transaction,那调用mapper接口方法时,也是通过spring的方法获取connection
        connection con = fetchconnection(datasource);
        if (transactionsynchronizationmanager.issynchronizationactive()) {
            logger.debug("registering transaction synchronization for jdbc connection");
            connectionholder holdertouse = conholder;
            if (conholder == null) {
                holdertouse = new connectionholder(con);
            } else {
                conholder.setconnection(con);
            }

            holdertouse.requested();
            transactionsynchronizationmanager.registersynchronization(new datasourceutils.connectionsynchronization(holdertouse, datasource));
            holdertouse.setsynchronizedwithtransaction(true);
            if (holdertouse != conholder) {
                //将获取到的connectionholder放入threadlocal中,那么当前线程调用下一个接口,下一个接口使用了spring事务,那spring事务也可以直接取到mybatis创建的connection
                //通过threadlocal保证了同一线程中spring事务使用的connection和mapper代理类使用的connection是同一个
                transactionsynchronizationmanager.bindresource(datasource, holdertouse);
            }
        }

        return con;
    } else {
        conholder.requested();
        if (!conholder.hasconnection()) {
            logger.debug("fetching resumed jdbc connection from datasource");
            conholder.setconnection(fetchconnection(datasource));
        }

        //所以如果我们业务代码使用了@transaction注解,在spring中就已经通过datasource创建了一个connection并放入threadlocal中
        //那么当mapper代理对象调用方法时,通过sqlsession的springmanagedtransaction获取连接时,就直接获取到了当前线程中spring事务创建的connection并返回
        return conholder.getconnection();
    }
}

想看怎么获取connholder 

org.springframework.transaction.support.transactionsynchronizationmanager#getresource

//保存数据库连接的threadlocal
private static final threadlocal<map<object, object>> resources = new namedthreadlocal<>("transactional resources");
@nullable
public static object getresource(object key) {
    object actualkey = transactionsynchronizationutils.unwrapresourceifnecessary(key);
    //获取connectionholder
    object value = dogetresource(actualkey);
    ....
    return value;
}

@nullable
private static object dogetresource(object actualkey) {
    /**
     * 从threadlocal <map<object, object>>中取出来当前线程绑定的map
     * map里面存的是<datasource,connectionholder>
     */
    map<object, object> map = resources.get();
    if (map == null) {
        return null;
    }
    //map中取出来对应datasource的connectionholder
    object value = map.get(actualkey);
    // transparently remove resourceholder that was marked as void...
    if (value instanceof resourceholder && ((resourceholder) value).isvoid()) {
        map.remove(actualkey);
        // remove entire threadlocal if empty...
        if (map.isempty()) {
            resources.remove();
        }
        value = null;
    }
    return value;
}

我们看到直接从threadlocal中取出来的conn,而spring自己的事务也是操作的这个threadlocal中的conn来进行事务的开启和回滚,由此我们知道了在同一线程中spring事务中的connection和mybaits中mapper代理对象中操作数据库的connection是同一个,当取出来的conn为空时候,调用org.springframework.jdbc.datasource.datasourceutils#fetchconnection获取,然后把从数据源取出来的连接返回

private static connection fetchconnection(datasource datasource) throws sqlexception {
    //从数据源取出来conn
    connection con = datasource.getconnection();
    if (con == null) {
        throw new illegalstateexception("datasource returned null from getconnection(): " + datasource);
    }
    return con;
}

我们再来回顾一下上篇文章中的sqlsessioninterceptor

 1 private class sqlsessioninterceptor implements invocationhandler {
 2     private sqlsessioninterceptor() {
 3     }
 4 
 5     public object invoke(object proxy, method method, object[] args) throws throwable {
 6         sqlsession sqlsession = sqlsessionutils.getsqlsession(sqlsessiontemplate.this.sqlsessionfactory, sqlsessiontemplate.this.executortype, sqlsessiontemplate.this.exceptiontranslator);
 7 
 8         object unwrapped;
 9         try {
10             object result = method.invoke(sqlsession, args);
11             // 如果当前操作没有在一个spring事务中,则手动commit一下
12             // 如果当前业务没有使用@transation,那么每次执行了mapper接口的方法直接commit
13             // 还记得我们前面讲的mybatis的一级缓存吗,这里一级缓存不能起作用了,因为每执行一个mapper的方法,sqlsession都提交了
14             // sqlsession提交,会清空一级缓存
15             if (!sqlsessionutils.issqlsessiontransactional(sqlsession, sqlsessiontemplate.this.sqlsessionfactory)) {
16                 sqlsession.commit(true);
17             }
18 
19             unwrapped = result;
20         } catch (throwable var11) {
21             unwrapped = exceptionutil.unwrapthrowable(var11);
22             if (sqlsessiontemplate.this.exceptiontranslator != null && unwrapped instanceof persistenceexception) {
23                 sqlsessionutils.closesqlsession(sqlsession, sqlsessiontemplate.this.sqlsessionfactory);
24                 sqlsession = null;
25                 throwable translated = sqlsessiontemplate.this.exceptiontranslator.translateexceptionifpossible((persistenceexception)unwrapped);
26                 if (translated != null) {
27                     unwrapped = translated;
28                 }
29             }
30 
31             throw (throwable)unwrapped;
32         } finally {
33             if (sqlsession != null) {
34                 sqlsessionutils.closesqlsession(sqlsession, sqlsessiontemplate.this.sqlsessionfactory);
35             }
36 
37         }
38         return unwrapped;
39     }
40 }

看第15和16行,如果我们没有使用@transation,mapper方法执行完后,sqlsession将会提交,也就是说通过org.springframework.jdbc.datasource.datasourceutils#fetchconnection获取到的connection将会commit,相当于connection是自动提交的,也就是说如果不使用@transation,mybatis将没有事务可言。

如果使用了@transation呢?那在调用mapper代理类的方法之前就已经通过spring的事务生成了connection并放入threadlocal,并且设置事务不自动提交,当前线程多个mapper代理对象调用数据库操作方法时,将从threadlocal获取spring创建的connection,在所有的mapper方法调用完后,spring事务提交或者回滚,到此mybatis的事务是怎么被spring管理的就显而易见了

还有文章开头的问题,为什么mybtis中要配置datasource,spring的事务中也要配置datasource?

因为spring事务在没调用mapper方法之前就需要开一个connection,并设置事务不自动提交,那么transactionmanager中自然要配置datasource。那如果我们的service没有用到spring事务呢,难道就不需要获取数据库连接了吗?当然不是,此时通过springmanagedtransaction调用org.springframework.jdbc.datasource.datasourceutils#getconnection#fetchconnection方法获取,并将datasource作为参数传进去,实际上获取的connection都是通过datasource来获取的。