种一棵树最好的时间是十年前,其次是现在;
1.提到数据库事务,就会浮现出事务的四大特性(ACID)、四大隔离级别、七大传播特性。
事务四大特性
- 原子性(Atomicity)
- 一致性(Consistency)
- 隔离性(Isolation)
- 持久性(Durability)
四大隔离级别
- Read uncommitted
- Read committed
- Repeatable read
- Selializable
四大还好说,问题是七大传播特性是哪儿来的?是Spring在当前线程内,处理多个数据库操作方法事务时所做的一种事务应用策略。事务本身并不存在什么传播特性,不要混淆事务本身和Spring的事务应用策略。
2.说到事务,可能又会想起create、begin、commit、rollback、close、suspend。可实际上,只有commit、rollback是实际存在的,剩下的create、begin、close、suspend都是虚幻的,是业务层或数据库底层应用语意,而非JDBC事务的真实命令。
create(事务创建):不存在。
begin(事务开始):姑且认为存在于DB的命令行中,比如Mysql的start transaction 命令,以及其他数据库中的begin transaction命令。JDBC中不存在。
close(事务关闭):不存在。应用程序接口中的close()方法,是为了把connection放回数据库连接池中,供下一次使用,与事务毫无关系。
suspend(事务挂起):不存在。Spring中事务挂起的含义是,需要新事务时,将现有的connection1保存起来(它还有尚未提交的事务),然后创建connection2,connection2提交、回滚、关闭完毕后,再把connection1取出来,完成提交、回滚、关闭等动作,保存connection1的动作称之为事务挂起。在JDBC中,是根本不存在事务挂起的说法的,也不存在这样的接口方法。
因此,事务的三个真实存在的方法,不要被各种事务状态名词所迷惑,它们分别是:conn.setAutoCommit()、conn.commit()、conn.rollback()。
conn.close()含义为关闭一个数据库连接,这已经不再是事务方法了。
1. Mybaits中的事务接口Transaction
// 3.4.6
public interface Transaction {
// Retrieve inner database connection
Connection getConnection() throws SQLException;
// Commit inner database connection.
void commit() throws SQLException;
// Rollback inner database connection.
void rollback() throws SQLException;
// Close inner database connection.
void close() throws SQLException;
// Get transaction timeout if set
Integer getTimeout() throws SQLException;
}
当你再次看到close()方法时,千万别再认为是关闭一个事务了,而是关闭一个conn连接,或者是把conn连接放回连接池内。
MyBatis 事务的两个实现
文章图片
JdbcTransaction:单独使用Mybatis时,默认的事务管理实现类,就和它的名字一样,它就是我们常说的JDBC事务的极简封装,和编程使用mysql-connector-java.jar事务驱动一样。其极简封装,仅是让connection支持连接池而已。
ManagedTransaction:含义为托管事务,空壳事务管理器,皮包公司。仅是提醒用户,在其它环境中应用时,把事务托管给其它框架,比如托管给Spring,让Spring去管理事务。
org.apache.ibatis.transaction.jdbc.JdbcTransaction.java close源码
@Override
public void close() throws SQLException {
if (connection != null) {
// 重新设置事务提交
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close();
}
}
前面说的close 是关闭连接可以在这里看见调用的是connection.close()。但是在之前竟然调用了resetAutoCommit(),这里指的是把connection的自动提交的值设置为初始状态。但是为什么呢?
首先我们要明确,我们使用JDBC做数据库操作时,每次开启连接和关闭连接是占用的大部分资源的,这个一般使用连接池,在连接池中建立连接实例,用的时候从连接池中拿取,用完了之后就放回去。这里的resetAutoCommit()就是是连接实例恢复初始状态。
2. 事务工厂TransactionFactory org.apache.ibatis.transaction.TransactionFactory.java 接口定义
public interface TransactionFactory {
// Sets transaction factory custom properties.
void setProperties(Properties props);
// Creates a {@link Transaction} out of an existing connection.
// @since 3.1.0
Transaction newTransaction(Connection conn);
// Creates a {@link Transaction} out of a datasource.
// @since 3.1.0
Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
}
MyBatis 事务工厂的两个实现
文章图片
一个生产JdbcTransaction实例,一个生产ManagedTransaction实例。两个工厂类,只有新建对应的实例;
使用:
mybatis-config.xml配置文件内,可配置事务管理类型。
dataSource 属性type就可以设置连接类型:UNPOOLED,POOLED,JNDI;
3. Transaction的用法 无论是SqlSession,还是Executor,它们的事务方法,最终都指向了Transaction的事务方法,即都是由Transaction来完成事务提交、回滚的。
文章图片
/**
* @author zhaojm
* @date 2020/4/12 10:53
*/
public class TransactionTest {
public static void main(String[] args) {
String resource = "org/mybatis/internal/example/Configuration.xml";
Reader reader;
try {
// mybatis 初始化
reader = Resources.getResourceAsReader(resource);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
SqlSession sqlSession = factory.openSession();
try {
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User addUser = new User("transaction", 14);
userMapper.addUser(addUser);
sqlSession.commit();
} catch (Exception e) {
sqlSession.rollback();
} finally {
sqlSession.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
如果把 sqlSession.commit(); 注释掉运行结果如下图:
updates: 1 表示执行的一句update语句,但是我们并没有异常就不会执行rollback()方法,但是确实有回滚了,这是因为默认的autoCommit是关闭的。
文章图片
然后再来看rollback()的调用用链(如下图),可以发现调用rollback的地方在close()中,在关闭之前会判断是否需要提交回滚(isCommitOrRollbackRequired(false))发现我们没用设置自动提交,dirty表示是否有脏数据(未提交update会设置dirty为true,insert,delete 实际调用的也是update方法)返回需要强制回滚,最终调用Transaction 的回滚。
文章图片
private boolean isCommitOrRollbackRequired(boolean force) {
return (!autoCommit && dirty) || force;
}
4. 有关事务的几种特殊场景表现(重要) 1. 一个conn生命周期内,可以存在无数多个事务。
再之前的代码中第一次提交之后再加入一条insert语句,然后再commit,如果不执行第二次的commit()方法,那这时候的回滚是两条数据一起回滚还是一条数据回滚?
文章图片
运行之后发现只有一次回滚了,第一次提交的数据已经写入库中了,由此可知rollback只回滚当前为提交的事务。
对于JDBC来说,autoCommit=false时,是自动开启事务的,执行commit()后,该事务结束。JDBC中不存在Hibernate中的session的概念,在JDBC中,insert了几次,数据库就会有几条记录,切勿混淆。
Mybatis的JdbcTransaction,和纯粹的Jdbc事务,几乎没有差别,它仅是扩展支持了连接池的connection。另外,需要明确,无论你是否手动处理了事务,只要是对数据库进行任何update操作(update、delete、insert),都一定是在事务中进行的,这是数据库的设计规范之一。
参考:https://my.oschina.net/zudajun/blog/666764
《Mybatis 解读》
推荐阅读
- xml|MyBatis03:连接池及事务控制、xml动态SQL语句、多表操作
- mybatis|MyBatis事务
- 关于mybatis的 insert into select 命令未结束问题
- #|Mybatis学习 && 配置解析
- mybatis|mybatis plus使用雪花算法_MyBatis-Plus笔记
- Java_web|Java-web案例(mybatis、maven、jsp、tomcat、servlet...)
- spring|springboot+mybais+mabatisplus(swagger)实现增删改查接口
- mybtais-plus|MyBatisPlus中的代码生成器
- MyBatis|MyBatisPlus的代码生成器