JdbcTransaction:单独使用Mybatis时,默认的事务管理实现类,就和它的名字一样,它就是我们常说的JDBC事务的极简封装,和编程使用mysql-connector-java-5.1.38-bin.jar事务驱动没啥差别。其极简封装,仅是让connection支持连接池而已。ManagedTransaction:含义为托管事务,空壳事务管理器,皮包公司。仅是提醒用户,在其它环境中应用时,把事务托管给其它框架,比如托管给Spring,让Spring去管理事务。
Mybaits中的事务接口Transactionpublic interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
}
org.apache.ibatis.transaction.jdbc.JdbcTransaction.java部分源码。
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close();
}
}
原因是这样的,connection.close()不意味着真的要销毁conn,而是要把conn放回连接池,供下一次使用,既然还要使用,自然就需要重置AutoCommit属性了。通过生成connection代理类,来实现重回连接池的功能。如果connection是普通的Connection实例,那么代码也是没有问题的,双重支持。
事务工厂TransactionFactory顾名思义,一个生产JdbcTransaction实例,一个生产ManagedTransaction实例。两个毫无实际意义的工厂类,除了new之外,没有其他代码。
mybatis-config.xml配置文件内,可配置事务管理类型。
Transaction的用法论是SqlSession,还是Executor,它们的事务方法,最终都指向了Transaction的事务方法,即都是由Transaction来完成事务提交、回滚的。
代码样例:
public static void main(String[] args) {
SqlSession sqlSession = MybatisSqlSessionFactory.openSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setName("yy");
student.setEmail("email@email.com");
student.setDob(new Date());
student.setPhone(new PhoneNumber("123-2568-8947"));
studentMapper.insertStudent(student);
sqlSession.commit();
} catch (Exception e) {
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
注:Executor在执行insertStudent(student)方法时,与事务的提交、回滚、关闭毫无瓜葛(方法内部不会提交、回滚事务),需要像上面的代码一样,手动显示调用commit()、rollback()、close()等方法。
有关事务的几种特殊场景表现(重要) 1. 一个conn生命周期内,可以存在无数多个事务 // 执行了connection.setAutoCommit(false),并返回
SqlSession sqlSession = MybatisSqlSessionFactory.openSession();
try {
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student();
student.setName("yy");
student.setEmail("email@email.com");
student.setDob(new Date());
student.setPhone(new PhoneNumber("123-2568-8947"));
studentMapper.insertStudent(student);
// 提交
sqlSession.commit();
studentMapper.insertStudent(student);
// 多次提交
sqlSession.commit();
} catch (Exception e) {
// 回滚,只能回滚当前未提交的事务
sqlSession.rollback();
} finally {
sqlSession.close();
}
对于JDBC来说,autoCommit=false时,是自动开启事务的,执行commit()后,该事务结束。以上代码正常情况下,开启了2个事务,向数据库插入了2条数据。JDBC中不存在Hibernate中的session的概念,在JDBC中,insert了几次,数据库就会有几条记录,切勿混淆。而rollback(),只能回滚当前未提交的事务。
2. autoCommit=false,没有执行commit(),仅执行close(),会发生什么?try {
studentMapper.insertStudent(student);
} finally {
sqlSession.close();
}
insert后,close之前,如果数据库的事务隔离级别是read uncommitted,那么,我们可以在数据库中查询到该条记录。接着执行sqlSession.close()时,经过SqlSession的判断,决定执行rollback()操作,于是,事务回滚,数据库记录消失。
@Override
public void close() {
try {
executor.close(isCommitOrRollbackRequired(false));
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
事务是否回滚,依靠isCommitOrRollbackRequired(false)方法来判断。
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
在上面的条件判断中,!autoCommit=true(取反当然是true了),force=false,最终是否回滚事务,只有dirty参数了,dirty含义为是否是脏数据。
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
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();
}
}
源码很明确,只要执行update操作,就设置dirty=true。insert、delete最终也是执行update操作。
只有在执行完commit()、rollback()、close()等方法后,才会再次设置dirty=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();
}
}
得出结论:autoCommit=false,但是没有手动commit,在sqlSession.close()时,Mybatis会将事务进行rollback()操作,然后才执行conn.close()关闭连接,当然数据最终也就没能持久化到数据库中了。
总结:Mybatis的JdbcTransaction,和纯粹的Jdbc事务,几乎没有差别,它仅是扩展支持了连接池的connection。另外,需要明确,无论你是否手动处理了事务,只要是对数据库进行任何update操作(update、delete、insert),都一定是在事务中进行的,这是数据库的设计规范之一。
参考博文
https://zhuanlan.zhihu.com/p/133807626