Mybatis源码学习之事务管理(八)

简述

在实际开发中,数据库事务的控制是一件非常重要的工作,本文将学习Mybatis对事务的管理机制。在Mybatis中基于接口 Transaction 将事务分为两种,一种是JdbcTransaction, 另一种是ManagedTransaction,接下来本文将从源码的角度解读 Transaction 接口的不同实现及其区别。
image
MyBatis将事务抽象成 Transaction 接口,该接口包含了数据库事务应有的操作,包括创建(create)、提交(commit)、回滚(rollback)、关闭(close),如下类图

image

MyBatis的事务管理分为两种形式:

一、使用JDBC的事务管理机制:利用java.sql.Connection对象完成对事务的提交(commit())、回滚(rollback())、关闭(close())等

二、使用MANAGED的事务管理机制:这种机制是让容器如(JBOSS,Weblogic)来实现对事务的管理

image

Mybatis事务的使用

通常我们会通过mybatis.xml配置事务的使用机制,如下 使用JDBC事务管理机制

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
</configuration>

Mybatis事务工厂创建

MyBatis事务的创建是交给TransactionFactory 事务工厂来创建的,如果我们将的type 配置为”JDBC”,那么,在MyBatis初始化解析节点时,会根据type=”JDBC”创建一个JdbcTransactionFactory工厂,源码如下:

/**
     * 解析<transactionManager>节点,创建对应的TransactionFactory
     *
     * @param context xml配置节点信息
     * @return TransactionFactory
     */
    private TransactionFactory transactionManagerElement(XNode context) throws Exception {
        if (context != null) {
            //获取事务管理机制的类型
            String type = context.getStringAttribute("type");
            Properties props = context.getChildrenAsProperties();
            //根据事务管理机制类型创建对应的事务管理工厂
            TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
            factory.setProperties(props);
            return factory;
        }
        throw new BuilderException("Environment declaration requires a TransactionFactory.");
    }

如代码所示,如果type = “JDBC”,则MyBatis会创建一个JdbcTransactionFactory.class 实例;如果type=”MANAGED”,则MyBatis会创建一个MangedTransactionFactory.class实例。

### Mybatis 事务工厂TransactionFactory
事务TransactionFactory定义了创建Transaction的两个方法:

一个是通过指定的Connection对象创建Transaction,

另外是通过数据源DataSource来创建Transaction。

与JDBC 和 MANAGED两种Transaction相对应,TransactionFactory有两个对应的实现的子类:

image

### Mybatis 事务创建
通过上文的学习,很清楚的知道Transaction对象的创建很容易 通过事务工厂TransactionFactory得到。这里我们以JdbcTransaction为例,看一下JdbcTransactionFactory是怎样生成JdbcTransaction的,代码如下:

/**
 * JdbcTransaction事务工厂,创建JdbcTransaction实例
 *
 * @author kaifeng
 * @author Clinton Begin
 * @see JdbcTransaction
 */
public class JdbcTransactionFactory implements TransactionFactory {

    @Override
    public void setProperties(Properties props) {
    }

    /**
     * 根据给定的数据库连接conn创建JdbcTransaction实例
     *
     * @param conn 数据库连接对象
     */
    @Override
    public Transaction newTransaction(Connection conn) {
        return new JdbcTransaction(conn);
    }

    /**
     * 根据给定的数据源conn创建JdbcTransaction实例
     *
     * @param ds         数据源
     * @param level      事务隔离级别
     * @param autoCommit 事务是否自动提交
     */
    @Override
    public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
        return new JdbcTransaction(ds, level, autoCommit);
    }
}

Mybatis JDBC事务

JdbcTransaction是直接使用JDBC的提交和回滚事务管理机制 。它从dataSource中取得连接connection 来管理transaction 的作用域,connection对象的获取被延迟到调用getConnection()方法。
JdbcTransaction相当于对java.sql.Connection事务处理进行了包装(wrapper)。

JdbcTransaction的代码实现如下:

/**
 * JDBC 事务实现
 *
 * @author kaifeng
 * @author Clinton Begin
 * @see JdbcTransactionFactory
 */
public class JdbcTransaction implements Transaction {

    private static final Log log = LogFactory.getLog(JdbcTransaction.class);

    /**
     * 数据库连接对象
     */
    protected Connection connection;

    /**
     * 数据源对象
     */
    protected DataSource dataSource;

    /**
     * 事务隔离级别
     */
    protected TransactionIsolationLevel level;

    /**
     * 事务是否自动提交
     */
    protected boolean autoCommmit;

    public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
        dataSource = ds;
        level = desiredLevel;
        autoCommmit = desiredAutoCommit;
    }

    public JdbcTransaction(Connection connection) {
        this.connection = connection;
    }

    @Override
    public Connection getConnection() throws SQLException {
        if (connection == null) {
            openConnection();
        }
        return connection;
    }

    /**
     * 事务提交,通过connection.commit()
     */
    @Override
    public void commit() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            if (log.isDebugEnabled()) {
                log.debug("Committing JDBC Connection [" + connection + "]");
            }
            connection.commit();
        }
    }

    /**
     * 事务回滚,通过connection.rollback();
     */
    @Override
    public void rollback() throws SQLException {
        if (connection != null && !connection.getAutoCommit()) {
            if (log.isDebugEnabled()) {
                log.debug("Rolling back JDBC Connection [" + connection + "]");
            }
            connection.rollback();
        }
    }

    /**
     * 关闭数据库连接
     */
    @Override
    public void close() throws SQLException {
        if (connection != null) {
            resetAutoCommit();
            if (log.isDebugEnabled()) {
                log.debug("Closing JDBC Connection [" + connection + "]");
            }
            connection.close();
        }
    }

    protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
        try {
            if (connection.getAutoCommit() != desiredAutoCommit) {
                if (log.isDebugEnabled()) {
                    log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
                }
                connection.setAutoCommit(desiredAutoCommit);
            }
        } catch (SQLException e) {
            // Only a very poorly implemented driver would fail here,
            // and there's not much we can do about that.
            throw new TransactionException("Error configuring AutoCommit.  "
                    + "Your driver may not support getAutoCommit() or setAutoCommit(). "
                    + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);
        }
    }

    protected void resetAutoCommit() {
        try {
            if (!connection.getAutoCommit()) {
                // MyBatis does not call commit/rollback on a connection if just selects were performed.
                // Some databases start transactions with select statements
                // and they mandate a commit/rollback before closing the connection.
                // A workaround is setting the autocommit to true before closing the connection.
                // Sybase throws an exception here.
                if (log.isDebugEnabled()) {
                    log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
                }
                connection.setAutoCommit(true);
            }
        } catch (SQLException e) {
            if (log.isDebugEnabled()) {
                log.debug("Error resetting autocommit to true "
                        + "before closing the connection.  Cause: " + e);
            }
        }
    }

    protected void openConnection() throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug("Opening JDBC Connection");
        }
        connection = dataSource.getConnection();
        if (level != null) {
            connection.setTransactionIsolation(level.getLevel());
        }
        setDesiredAutoCommit(autoCommmit);
    }

    @Override
    public Integer getTimeout() throws SQLException {
        return null;
    }

}

Mybatis Managed事务

ManagedTransaction是让容器来管理事务Transaction的整个生命周期,使用ManagedTransaction的commit和rollback功能不会对事务有任何的影响,什么都不会做,它将事务管理的权利移交给了容器来实现。

如下Managed的实现代码:

/**
 * 让容器来管理事务的整个生命周期,ManagedTransaction的操作不会对数据库产生影响,默认情况下它的数据库连接是关闭的
 *
 * @author kaifeng
 * @author Clinton Begin
 * @see ManagedTransactionFactory
 */
public class ManagedTransaction implements Transaction {

    private static final Log log = LogFactory.getLog(ManagedTransaction.class);

    /**
     * 数据源
     */
    private DataSource dataSource;

    /**
     * 事务隔离级别
     */
    private TransactionIsolationLevel level;

    /**
     * 数据库连接对象
     */
    private Connection connection;

    /**
     * 是否关闭数据库连接
     */
    private final boolean closeConnection;

    public ManagedTransaction(Connection connection, boolean closeConnection) {
        this.connection = connection;
        this.closeConnection = closeConnection;
    }

    public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {
        this.dataSource = ds;
        this.level = level;
        this.closeConnection = closeConnection;
    }

    @Override
    public Connection getConnection() throws SQLException {
        if (this.connection == null) {
            openConnection();
        }
        return this.connection;
    }

    /**
     * 事务提交,不做任何操作
     */
    @Override
    public void commit() throws SQLException {
        // Does nothing
    }

    /**
     * 事务回滚,不做任何操作
     */
    @Override
    public void rollback() throws SQLException {
        // Does nothing
    }

    /**
     * 事务关闭,不做任何操作
     */
    @Override
    public void close() throws SQLException {
        if (this.closeConnection && this.connection != null) {
            if (log.isDebugEnabled()) {
                log.debug("Closing JDBC Connection [" + this.connection + "]");
            }
            this.connection.close();
        }
    }

    protected void openConnection() throws SQLException {
        if (log.isDebugEnabled()) {
            log.debug("Opening JDBC Connection");
        }
        this.connection = this.dataSource.getConnection();
        if (this.level != null) {
            this.connection.setTransactionIsolation(this.level.getLevel());
        }
    }

    @Override
    public Integer getTimeout() throws SQLException {
        return null;
    }

}

在实践中,MyBatis通常会与Spring集成使用,数据库的事务是交给Spring进行管理的,在后文我们会介绍Transaction接口的另一个实现—SpringManagedTransaction

posted @ 2018-08-11 17:57  IT码客  阅读(236)  评论(0编辑  收藏  举报