美文网首页
Mybatis源码分析2--事务

Mybatis源码分析2--事务

作者: zhuke | 来源:发表于2017-08-24 16:59 被阅读0次

Mybatis的事务接口如下:

public interface Transaction {
  Connection getConnection() throws SQLException;
  void commit() throws SQLException;
  void rollback() throws SQLException;
  void close() throws SQLException;
  Integer getTimeout() throws SQLException;
}

事务类的继承体系如图:

事务的继承体系

JdbcTransaction是对JDBK commit & rollback简单封装。

ManagedTransaction托管事务,空壳事务管理器。在其它环境中应用时,把事务托管给其它框架,比如托管给Spring。

下面分析Mybatis的JdbcTransaction事务提交和管理过程相关源码。

在我们执行SqlSession.openSession()开启一个session的过程中,就已经开启了一个事务,并设置了事务的autocommit属性为false

public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();

      //通过TransactionFactory 事务工厂类生成一个新的事务实例
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      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();
    }
  }

这里通过TransactionFactory 事务工厂类生成一个新的事务实例,TransactionFactory 事务工厂的类继承结构如下:

TransactionFactory 类继承结构

工厂类有类的实例获取方法,而且我们可以通过mybatis-config.xml中配置获取事务的工厂类:

<transactionManager type="JDBC"/>

而后我们对SqlSession进行各类更新操作,因为之前开启session时,设置了autocommit属性为false,所以事务会在调用Transaction.commit() | rollback()时才会提交或回滚。

最后我们手动调用SqlSession.commit():

@Override
  public void commit() {
    commit(false);
  }

  @Override
  public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

这里会委托到具体的执行器Executor进行commit操作:

@Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    clearLocalCache();
    flushStatements();
    if (required) {
      transaction.commit();
    }
  }

委托到Transaction的commit操作:

public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }

最终调用Connection.commit()进行事务的提交。

总结上述调用过程为:

Mybatis事务调用过程

几个注意点

1. 一个SqlSession的生命周期内,可以有无数个事务提交。

如以下代码:

User user2 = new User();
user2.setFirstName("firstname");
user2.setLastName("lastname");

session.insert("mybatis.dao.UserMapper.insert", user2);
session.commit();//第一次事务提交

session.insert("mybatis.dao.UserMapper.insert", user2);
session.commit();//第二次事务提交

从以上的源码分析可知,在SqlSession创建之初,即设置了Transaction的autocommit属性为false,那么每次执行update操作时:

public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      //预处理sql语句,这里获取数据库连接
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }


private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }


public Connection getConnection() throws SQLException {
    //如果是首次获取connection连接,则新建一个数据库连接
    if (connection == null) {
      openConnection();
    }
    return connection;
  }

protected Connection getConnection(Log statementLog) throws SQLException {
    //从transaction实例中获取数据库连接
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    //这里设置了autocommit参数为默认的false,事务不会自动提交,必须手动提交
    setDesiredAutoCommit(autoCommmit);
  }


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);
    }
  }

所以,在同一个SqlSession的生命周期中,会复用一个connection连接,当调用SqlSession.commit()执行事务提交后,会提交生效上一次commit提交之后的所有数据库更改操作,当再次调用commit操作时,仍然会提交生效上一次commit提交之后的所有数据库更改操作。

2.如果没有执行commit操作,会是什么情况?

insert后,在事务的超时时间内,如果事务隔离级别是read uncommitted,我们可以查到该条记录。当超过超时时间,仍没有收到事务的commit,那么事务将会被回滚。

3.关于SqlSession.close()

正确的使用Mybatis的编程模型是:

String resource = "mybatis-config.xml";
InputStream inputStream = new ClassPathResource(resource).getInputStream();
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
try {
    session.insert(……);
    session.commit();
} catch (Exception e) {
    session.rollback();
} finally {
    session.close();
}

那么close操作到底执行了什么操作呢?如果我们不执行commit,直接执行close操作呢?

public void close() {
    try {
      //计算是否需要强制回滚
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }

private boolean isCommitOrRollbackRequired(boolean force) {
    return (!autoCommit && dirty) || force;
  }

isCommitOrRollbackRequired计算方法中,force=false, autoCommit=false,那么就看dirty的取值情况了。

通过分析可知,dirty其余情况都是==false,只有在update方法内部会设置为true

public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

在commit, rollback后,会设置为dirty=false。
所以针对上面的情况,事务中,执行了update操作,dirty=true,不执行commit,那么在close时,(!autoCommit && dirty) || force == (true && true ) || false = true,会执行回滚操作。

相关文章

网友评论

      本文标题:Mybatis源码分析2--事务

      本文链接:https://www.haomeiwen.com/subject/eikedxtx.html