MyBatis 的秘密(一)Transaction


public List<Country> selectById()throws IOException {
    try (Reader reader = Resources.getResourceAsReader("mybatis-config.xml")) {
        //创建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        //获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //执行Sql
        return sqlSession.selectList("selectAll");
    }
}

上面是使用MyBatis执行一段SQL完整的Java API

可以看到整个过程中创建了4个对象:

  • Reader : 用来读取MyBatis的配置文件流
  • SqlSessionFactory: 用来根据各种配置创建SqlSession,一般应该作为static成员变量存在,只初始化一次
  • SqlSession : 真正的干活的类

对于SqlSessionFactoryBuilder.builder(),这里只用简单说说,此类是用来读取MyBatisXML配置文件,然后根据配置文件来初始化Configuration对象,目前只用知道这个,当需要了解细节的时候再细看。


因此,对于MyBatis的秘密,全都藏在了SqlSession中。

首先看看SqlSession的创建方法:SqlSessionFactory#openSession()

由官方文档中可以知道,openSession()包括以下几种重载:

//默认,level 使用数据库默认,autoCommit 为false ,execType 为defaultExecType 默认为SIMPLE
//通过配置的DataSource 获取Connection 对象
SqlSession openSession()
//指定是否自动提交
SqlSession openSession(boolean autoCommit)
//通过connection 初始化
SqlSession openSession(Connection connection)
//指定事务级别    
SqlSession openSession(TransactionIsolationLevel level)
//指定执行器类型和事务级别    
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
//通过connection初始化,并指定事务级别    
SqlSession openSession(ExecutorType execType, Connection connection)

也就是说,对于SqlSession,我们可以直接配置的属性有:

  • ExecutorType : 执行器类型
  • AutoCommit : 是否开启事务 ( false 为开,true 为不开)
  • TransactionIsolationLevel : 事务级别

接下来看看创建SqlSession的代码:

SqlSessionFactory#openSessionFromDataSource()

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        //获取配置文件的environment对象
        final Environment environment = configuration.getEnvironment();
        //从environment对象中获取事务管理器创建对象
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
        //创建事务管理器
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        //创建执行器
        final Executor executor = configuration.newExecutor(tx, execType);
        //将执行器作为参数创建SqlSession
        return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
        closeTransaction(tx); // may have fetched a connection so lets call close()
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

总结起来分为3步:

  • 获取environment对象
  • 根据environment对象创建事务管理器工厂以及通过工厂创建Transaction
  • Transaction对象作为参数创建执行器
  • 将执行器作为SqlSession构造参数创建SqlSession

首先我们看environment的配置:

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/new_test"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </dataSource>
    </environment>
</environments>

这里配置了两个元素: transactionManager 以及 dataSource

Transaction

这个类算是MyBaits中比较重要的一个类,重要在哪里呢?它的实现特别简单,但是在MyBatis中起着一个中间层的作用,首先看这个接口包含的方法:

public interface Transaction {

  Connection getConnection() throws SQLException;

  void commit() throws SQLException;

  void rollback() throws SQLException;

  //....
}

可以看出来这些功能其实就是对Connection的包装。

MyBatis中,Transaction有两个实现类:JdbcTransactionManagedTransaction

对于JdbcTransaction来说,其相当于connection的代理类,需要提交的时候便调用Connection的提交,需要回滚的时候便调用Connection的回滚方法

而对于ManagedTransaction 来说,缺忽略了commit()rollback()方法,其实现类中什么都没做。

MyBatis的官方文档中,写明了:

  • JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
  • MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)

看到这里就大概能明白MyBatis设计Transaction中间层的意义了,某些时候,当MyBatis和其他类似EJB的服务整合的时候,此时不应该在MyBatis中涉及到提交或者回滚,而应该交由EJB容器管理,而这个中间层则正好可以做到一个动态选择的作用,这样可以在不修改任何代码的情况下,实现由程序本身控制事务还是交给容器管理的选择。


同理,可以想到Spring整合MyBatis,在Spring中,定义了事务的传播机制,而这些机制的实现,依然需要依赖Transaction做中间层代理,这样使用Transaction的代码都不用动,而实现Spring的事务传播则交给Transaction来做。

Spring-MyBatis中,实现此接口的类为:SpringManagedTransaction

从这里便可以看出来面向对象的抽象的好处,如果简单的设计,可能会使得代码中到处都是if-else的执行判断


熟悉了Transaction后,应该明白它充当的作用便是Connection的作用,下一章来看Executor

发表评论