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 事务工厂的类继承结构如下:
工厂类有类的实例获取方法,而且我们可以通过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()进行事务的提交。
总结上述调用过程为:
几个注意点
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,会执行回滚操作。