开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 13 天,点击查看活动详情
本文我们着重讲解Mybatis的事务管理
1.事务特性
事务就是几件事情一起要么一起完成,要么全都不做!并且不同的事务直接相互隔离,互不干扰。
1.1 事务ACID特性
- 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 举例: A给B转账 100
转账前:A-100 B-0,总额100(一致性状态)
转账中:A-0 B-0,总额100(不一致状态)
转账后:A-0 B-100 总额100(达到一致性状态)
-
一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。
-
隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
-
持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
1.2 事务的隔离
MySQL事务隔离级别
| 事务隔离级别 | 脏读 | 不可重复读 | 幻读 | 备注 |
|---|---|---|---|---|
| 读未提交(read-uncommitted) | 是 | 是 | 是 | 其他事务会读取当前事务尚未更改的提交(相当于读取的是这个事务暂时缓存的内容,并不是数据库中的内容) |
| 不可重复读(read-committed) | 否 | 是 | 是 | 他事务会读取当前事务已经提交的数据(也就是直接读取数据库中已经发生更改的内容) |
| 可重复读(repeatable-read) | 否 | 否 | 是 | 【MySql默认】事务提交前后都能读,其他事务会读取当前事务已经提交的数据并且其他事务执行过程中不允许再进行数据修改(注意这里仅仅是不允许修改数据) |
| 串行化(serializable) | 否 | 否 | 否 | serializable时会锁表,是最安全的,也是日常开发基本不会用的,它完全服从ACID原则,一个事务必须等待其他事务结束之后才能开始执行,相当于挨个执行,效率很低 |
2.Mybatis的事务管理
mybatis 事务有两种使用方式:
- 使用JDBC的事务管理机制:即使用 java.Sql.Connection对象 完成对事务的提交,回滚和关闭操作。
- 使用MANAGED的事务管理机制:mybatis本身不会去实现事务管理的相关操作,而是交给外部容器来管理事务。与spring整合使用后,一般使用spring来管理事务。
Mybatis的Transaction 接口如下:
- comomit 提交事务
- rollback 回滚事务
Transaction 有三个实现
package org.apache.ibatis.transaction;
import java.sql.Connection;
import java.sql.SQLException;
public interface Transaction {
Connection getConnection() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
void close() throws SQLException;
Integer getTimeout() throws SQLException;
}
2.1 JdbcTransaction 事务实现
看一下核心方法 commit/rollback
JdbcTransaction 的commit 底层通过 connection.commit()进行事务的提交
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
connection.commit();
}
}
JdbcTransaction rollback 底层通过 connection.rollback()进行事务的提交
public void rollback() throws SQLException {
if (this.connection != null && !this.connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + this.connection + "]");
}
this.connection.rollback();
}
}
JdbcTransaction setDesiredAutoCommit 底层通过设置要求自动提交
protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
try {
if (this.connection.getAutoCommit() != desiredAutoCommit) {
if (log.isDebugEnabled()) {
log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + this.connection + "]");
}
this.connection.setAutoCommit(desiredAutoCommit);
}
} catch (SQLException var3) {
throw new TransactionException("Error configuring AutoCommit. Your driver may not support getAutoCommit() or setAutoCommit(). Requested setting: " + desiredAutoCommit + ". Cause: " + var3, var3);
}
}
2.1 ManagedTransaction事务实现
看一下核心方法 commit/rollback,发现ManagerTransaction的commit 和 rollback方法根本什么都没有做
说明采用ManagerTransaction的事务的commit/rollback 全都交给了容器取管理,自身没有实现逻辑
@Override
public void commit() throws SQLException {
// Does nothing
}
@Override
public void rollback() throws SQLException {
// Does nothing
}
3.事务测试
3.1 新建testconfig/sqlConfig.xml
resources文件夹下 新建testconfig, 下新建sqlConfig.xml
sqlConfig.xml配置文件
<?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="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="sqlmapper/UserInfoMapper.xml"/>
</mappers>
</configuration>
3.2 新建MyJdbcTransactionTest 类
这是Main函数, 每次执行 都会查询 id =1 的数据,然后删除, 我们看看 是否真正删除
package com.jzj.tdmybatis.util;
import cn.hutool.json.JSONUtil;
import com.jzj.tdmybatis.domain.po.UserInfoPO;
import com.jzj.tdmybatis.repository.mapper.UserInfoMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.InputStream;
@Slf4j
public class MyJdbcTransactionTest {
public static void main(String[] args) {
SqlSessionFactory sqlSessionFactory;
SqlSession sqlSession = null;
//使用类加载器加载mybatis的配置文件(它也加载关联的映射文件)
try {
//mybatis配置文件
String resource = "testconfig\sqlConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//创建能执行映射文件中sql的sqlSession
sqlSession = sqlSessionFactory.openSession();
log.info("==================" + sqlSession.getConnection().getAutoCommit());
//
// //获取mapper方式
UserInfoMapper userMapper = sqlSession.getMapper(UserInfoMapper.class);
UserInfoPO user1 = userMapper.getUserById(1L);
log.info("==================" + JSONUtil.toJsonStr(user1));
//这里删除是不会提交到数据库的
// 因为 autoCommit JDBCTransaction 默认设置的 false 是不会自动提交的
//这段代码执行100遍, 下次进来查询的时候 ID=1的记录始终存在
//也可以看下DB 看看是否被删除
userMapper.deleteUserById(1L);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
}
执行该方法, 可以得到结果 正确查出了User信息
而且 根据日志 看出来
- JdbcTransaction 的事务是默认关闭的
- query 查询语句生效了,查出来一条 user信息
- delete user也生效了 有一条update语句执行 Updates: 1
00:47:59.351 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1292838001.
00:47:59.352 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d0f2471]
00:47:59.352 [main] INFO com.jzj.tdmybatis.util.MyJdbcTransactionTest - ==================false
00:47:59.364 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.getUserById - ==> Preparing: select * from user_info where id = ?
00:47:59.384 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.getUserById - ==> Parameters: 1(Long)
00:47:59.404 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.getUserById - <== Total: 1
00:47:59.426 [main] INFO com.jzj.tdmybatis.util.MyJdbcTransactionTest - =================={"address":"北京","modtime":0,"goods":"{\"deptId\": 1, \"deptName\": \"部门1\", \"deptLeaderId\": 4}","addtime":0,"id":1,"age":1}
00:47:59.426 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.deleteUserById - ==> Preparing: delete from user_info where id = ?
00:47:59.426 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.deleteUserById - ==> Parameters: 1(Long)
00:47:59.428 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.deleteUserById - <== Updates: 1
00:47:59.428 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Rolling back JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d0f2471]
00:47:59.444 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d0f2471]
00:47:59.445 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d0f2471]
00:47:59.445 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1292838001 to pool.
3.3 查看DB执行结果
事实 真的是这样么 ???
数据依旧存在
我们再次执行程序,还是一样的结果, user id=1的 记录始终没有删除, 为什么 ?????
因为执行完语句,Mybatis的事务 没有手动提交, 我们需要 手动提交事务, 才会对数据库做更新操作
sqlSession.commit();
//!!!! 很重要 提交事务, 有这一行,删除代码才会生效
sqlSession.commit();
3.4 手动提交事务
我们修改下代码逻辑 在 delete完, 执行sqlSession.commit(); 手动提交事务
运行下 程序,让程序执行完毕后 (这一次真的删除了), 为了验证,我们 再一次运行
可以看到 查不到user记录了,Total: 0 , =====null 说明数据库已经被修改了
00:53:13.970 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1292838001.
00:53:13.971 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d0f2471]
00:53:13.971 [main] INFO com.jzj.tdmybatis.util.MyJdbcTransactionTest - ==================false
00:53:13.983 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.getUserById - ==> Preparing: select * from user_info where id = ?
00:53:14.005 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.getUserById - ==> Parameters: 1(Long)
00:53:14.022 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.getUserById - <== Total: 0
00:53:14.025 [main] INFO com.jzj.tdmybatis.util.MyJdbcTransactionTest - ==================null
00:53:14.025 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.deleteUserById - ==> Preparing: delete from user_info where id = ?
00:53:14.025 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.deleteUserById - ==> Parameters: 1(Long)
00:53:14.026 [main] DEBUG com.jzj.tdmybatis.repository.mapper.UserInfoMapper.deleteUserById - <== Updates: 0
00:53:14.026 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d0f2471]
00:53:14.026 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d0f2471]
00:53:14.027 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@4d0f2471]
00:53:14.027 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1292838001 to pool.
我们看一下数据库DB的数据, 是否还有这条user id=1 的记录 ?
可以看到记录已经被删除了, 说明事务生效,db数据修改 生效