大家好,又见面了,我是你们的朋友全栈君。
原创博文,欢迎转载,转载时请务必附上博文链接,感谢您的尊重。
前言
通过本篇,你将了解到【Spring事务】与【数据库事务】的关系,以及优先级问题,我将为你一一论证。
阅读本篇,你可能会需要的博文:
- @Transactional 注解参数详解,以及注解的使用特性说明(典藏版)
- @Transactional 注解的失效场景,这个问题见过太多的人栽跟头,一篇刨根问底,让面试官都闭嘴
- 搞定数据库事务、事务的隔离级别,以及脏读、不可重复读、幻读,我是认真的
正文
数据库是可以控制事务的传播和隔离级别的,Spring在之上又进一步进行了封装,可以在不同的项目、不同的操作中再次对事务的传播行为和隔离级别进行策略控制。
所以说,spring事务本质上使用数据库事务,而数据库事务本质上使用数据库锁,所以spring事务本质上使用数据库锁,开启spring事务意味着使用数据库锁。
上面的“总结”来自网络,结合自身理解与实践,的确可以最为一句精华,本文内容主要围绕这句话展开,以2个问题的形式阐明观点。
一、两者的关系
详情在我的其他博文都有具体介绍(需要的朋友见本文【前言】提示),这里就不赘述了,我们直接对比结果。
1. 数据库的隔离级别:
MySQL 默认为 :EPEATABLE_READ;Oracle,sql server 默认为:READ_COMMITTED;READ_UNCOMMITTED 由于隔离级别较低,通常不会被使用。
隔离级别 | 隔离级别的值 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
Read uncommitted(未提交读) | 0 | √ | √ | √ |
Read committed(已提交读) | 1 | × | √ | √ |
Repeatable read(可重复读) | 2 | × | × | √ |
Serializable(可串行化) | 3 | × | × | × |
2. Spring事务的隔离级别:
Spring事务由 @Transactional 注解实现,隔离级别由它的参数 isolation 控制,Isolation 的 Eum 类中定义了“五个”表示隔离级别的值,如下。
Isolation的值与隔离级别 | 隔离级别的值 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
Isolation.DEFAULT | 0 | – | – | – |
Isolation.READ_UNCOMMITTED | 1 | √ | √ | √ |
Isolation.READ_COMMITTED | 2 | × | √ | √ |
Isolation.REPEATABLE_READ | 4 | × | × | √ |
Isolation.SERIALIZABLE | 8 | × | × | × |
Isolation.DEFAULT 是 PlatfromTransactionManager 默认的隔离级别,它的含义是:使用数据库默认的事务隔离级别。
除此之外,另外四个与 JDBC 的隔离级别是相对应的,就好像 Java 里的重写一样,所以说:Spring事务隔离级别是在数据库隔离级别之上又进一步进行了封装。
二、 不一致会怎样
既然是封装,那么Spring项目应该就是以Spring事务为准的,除非使用 @Transactional(isolation = Isolation.DEFAULT)时,才会使用数据库设置的隔离级别。
为了验证这个猜想,我们还是找到源码解读一下,从JDBC开始说起吧。
1. JDBC 加载流程
每一个 Spring 事务管理,都涉及到了与数据库的交互,也必然涉及到了JDBC连接。
JDBC 加载的流程还记得吧,肯定都被问过,有四步:注册驱动,建立连接,发起请求,输出结果。写一段伪代码:
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try{
// 1.注册 JDBC 驱动
Class.forName("com.mysql.jdbc.Driver");
// 2.创建链接
System.out.println("连接数据库...");
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/my_db","root","root");
// 3.发起请求
stmt = conn.createStatement();
String sql = "SELECT id, name, url FROM websites";
rs = stmt.executeQuery(sql);
// 4.输出结果
System.out.print("查询结果:" + rs);
// 关闭资源(演示代码,不要纠结没有写在finally中)
rs.close();
stmt.close();
conn.close();
} catch (SQLException se)
se.printStackTrace();
}catch(Exception e){
e.printStackTrace();
}
在创建连接阶段,JDBC 从数据库获取一个连接 Connection 对象,该对象不仅有连接数据库的方法,还有设置当前连接的事物隔离级别的方法。
2. Connection 源码解释
Connection 实体类中包含了 void setTransactionIsolation(int level) throws SQLException;
设置设置当前连接的事物隔离级别的方法。
Spring 约定了使用 @Transactional 注解的形式实现事务特性,而隔离级别的开启,也是注解的形式实现,如:开启事务的串行级别 —— @Transactional(isolation = Isolation.SERIALIZABLE)。
源码截不全,我复制一下:
/**
* 这是 Connection 连接的部分源码
*/
public interface Connection extends Wrapper, AutoCloseable {
...
/**
* 尝试将此连接对象的事务隔离级别更改为给定的级别
* 接口连接中定义的常量是可能的事务隔离级别
*/
void setTransactionIsolation(int level) throws SQLException;
...
}
该方法的注释说明:尝试将此连接对象的事务隔离级别更改为给定的级别,如果在事务期间调用此方法,则结果由实现定义。
没错,强调的就是本次连接 Connection,所以,如果spring与数据库事务隔离级别不一致时,以spring为准。
3. 验证
阐述一下方法:
- 首先,验证测试数据库的隔离级别 Select @@tx_isolation;
- 写一个包含update,save的与测试数据库交互的方法;
- 分别验证加上@Transactional(isolation = Isolation.SERIALIZABLE)注解前后,测试数据库的隔离级别是否变化!!
以我的测试数据库为例,结果没有发生变化,都是 READ_COMMITTED。
这只是简单的验证下Spring事务隔离级别的修改,是否会直接影响数据库的隔离级别,结论是没有。
不太严谨,但是证明了我的猜想,后期我会在Sping项目中,通过测试代码进一步验证“修改隔离级别以成功运用到Spring事务中”。
三、总结
- 数据库是可以控制事务的传播和隔离级别的,Spring在之上又进一步进行了封装,可以在不同的项目、不同的操作中再次对事务的传播行为和隔离级别进行策略控制;
- 项目中,以 Spring 事务为准,因为他重写了数据库的隔离级别,但没有直接修改数据库的隔离级别;
- 项目中,如果 Spring 事务隔离级别设置为(isolation = Isolation.DEFAULT)默认数据库优先时,以数据库的隔离级别为准。
小编怀着忐忑的心情,上传了本篇博文,如果有错误,还请大佬指正!!后期也会补全自己的验证手段!!
That’s all,thank you!
我是「IT无知君」,您的点赞、评论和关注,是我创作的动力源泉。
学无止境,气有浩然,让我们一起加油,天涯未远,江湖有缘再见!!
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/141451.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...