事务属性:只读
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
对增删改操作设置只读会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
@Transactional(readOnly = true)
public String updateBalance(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? and username=?";
int r1 = jdbcTemplate.update(sql1,1,"张三");
System.out.println("r1="+r1);
String sql2 = "update t_user set balance = balance+50 where user_id = ? and username=?";
int r2 = jdbcTemplate.update(sql2,2,"李四");
System.out.println("r2="+r2);
// int i =1/0;
return "success";
}
@@Transactional(readOnly = true)
在测试类中使用会失效,总之不要在测试类用相关事务了
查询还需要事务吗?干脆不设置事务性能不更高吗?
单条SELECT SQL语句可以不开启事务,多条SELECT SQL语句,并且多条SELECTSQL语句的查询结果有关联,比如用作统计、报表等功能,要求开启事务。但是为了提高效率,建议只读事务。
【原因】
如果你一次执行单条查询语句,则没有必要启用事务支持,数据库默认支持SQL执行期间的读一致性;
如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询SQL必须保证整体的读一致性,否则,在前条SQL查询之后,后条SQL查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持。
事务属性:超时
超时回滚,释放资源。
执行过程中抛出异常:
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Mon Sep 30 23:48:23 CST 2024
使用:
@Transactional(timeout = 2)
public String updateBalanceTimeout(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? and username=?";
int r1 = jdbcTemplate.update(sql1,1,"张三");
System.out.println("r1="+r1);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
String sql2 = "update t_user set balance = balance+50 where user_id = ? and username=?";
int r2 = jdbcTemplate.update(sql2,2,"李四");
System.out.println("r2="+r2);
return "success";
}
注意:如果超时反正在所以数据库操作之后,是并不影响事务的执行的,也就是事务也是可以成功提交,不会回滚
@Transactional(timeout = 2)
public String updateBalanceTimeout(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? and username=?";
int r1 = jdbcTemplate.update(sql1,1,"张三");
System.out.println("r1="+r1);
String sql2 = "update t_user set balance = balance+50 where user_id = ? and username=?";
int r2 = jdbcTemplate.update(sql2,2,"李四");
System.out.println("r2="+r2);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "success";
}
事务属性:回滚策略
声明式事务默认只针对运行时异常回滚,编译时异常不回滚。
可以通过@Transactional
中相关属性设置回滚策略
rollbackFor
属性:需要设置一个Class类型的对象(哪些异常回滚)rollbackForClassName
属性:需要设置一个字符串类型的全类名(哪些异常回滚)noRollbackFor
属性:需要设置一个Class类型的对象(那些异常不回滚)rollbackFoClassNamer
属性:需要设置一个字符串类型的全类名(那些异常不回滚)
// 表示所有的异常都回滚,除了ArithmeticException
// @Transactional(rollbackFor=Exception.class,noRollbackFor = ArithmeticException.class)
// @Transactional(rollbackForClassName="java.lang.Exception",noRollbackForClassName = "java.lang.ArithmeticException")
@Transactional(rollbackFor = Exception.class,noRollbackForClassName = "java.lang.ArithmeticException")
public void updateBalanceRollBack(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? and username=?";
int r1 = jdbcTemplate.update(sql1,1,"张三");
System.out.println("r1="+r1);
int i = 1/0;
String sql2 = "update t_user set balance = balance+50 where user_id = ? and username=?";
int r2 = jdbcTemplate.update(sql2,2,"李四");
System.out.println("r2="+r2);
}
事务隔离级别
各个隔离级别解决并发问题的能力见下表:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED | 有 | 有 | 有 |
READ COMMITTED | 无 | 有 | 有 |
REPEATABLE READ | 无 | 无 | 有 |
SERIALIZABLE | 无 | 无 | 无 |
各种数据库产品对事务隔离级别的支持程度:
隔离级别 | Oracle | MySQL |
---|---|---|
READ UNCOMMITTED | × | √ |
READ COMMITTED | √(默认) | √ |
REPEATABLE READ | × | √(默认) |
SERIALIZABLE | √ | √ |
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
事务属性:传播行为
一个视频教会你spring的事务传播行为_哔哩哔哩_bilibili
什么是事务的传播行为
当我们遇到如下的代码,在一个service中调用了另一个service的方法,此时AService的事务被传播到了BService中,这样就产生了事务的传播:
@service
public class Aservice {
@Autowired
private Bservice bservice:
public void order(){
yyyyyy();
bservice.xxxx():
zzzzz();
}
}
因为两个service都存在事务,那么生成的的sql语句可能如下:
BEGIN;
update yyyy;
-- 两个平务的分界线-------------------
begin;
update xxxx;
commit:
-- --------------------------------
update zzz;
commit:
在这里很明显是存在问题的,当第二个begin执行时,会隐式的将第一个事务直接提交了,从而导致AService的部分事务失效。什么是事务的传播行为?
所以当【B事务】传播到【A事务】中时【B事务】需要做一下微调,微调的结果不外乎以下几种情况:
当AService有事务,AService也有事务
第一种情况:融入A事务(干掉B的事务)形成的sq如下,这种场景是最多的:
BEGIN;
update yyyy;
-- 两个平务的分界线-------------------
update xxxx;
-- --------------------------------
update zzz;
commit:
第二种情况:挂起A事务,让B事务独立于A事务运行。
当两个事务需要各自独立维护自身事务,单个事务则无法独立完成,B事务启动时可以暂时将A事务挂起,就是阻塞住,不给他继续发送sql,让他无法提交,B开启一个新的事务,B执行完成后A继续。

当AService无事务,而BService有事务
第一种情况:B事务以事务方式运行
select yyyy;
-- 分界线---------------
begin;
update xxxx;
commit;
-- 分界线---------------
select zzzz;
第二种情况:B事务以无事务方式运行
select yyyy;
-- 分界线---------------
update xxxx;
-- 分界线---------------
select zzzz;
嵌套事务
通过设置保存点,将内部的事务转化为通过保存点和回滚至保存点,实现类似两个事务的操作。
伪代码如下:
begin;
update mystudent set score =100 where id =1:
SAVEPOINT a;
update mystudent set score =100 where id =3:
update mystudent set score =100 where id =4;
-- 以上的代码有问题则会回滚至保存点
ROLLBACK to a:
-- 后边的事务不会受到影响
update mystudent set score=100 where id =2;
commit:
嵌套的过程如下:
- 内部SAVEPOINT a后的代码如果有问题则直接回滚至保存点。
- 整个事务的提交也不受内部【伪事务】的影响。
Spring定义的七种传播行为
REQUIRED
PROPAGATION.REQUIRED
:这是Spring默认的传播行为,表示当前方法(被REQUIRED
修饰的方法)必须运行在事务中。如果外层的调用方法有事务,则按照外层的事务运行,如果外层的方法没有事务,那么外层方法还是按照没有事务的方法运行,但是被调用的方法,会创建一个和原来一模一样的事物
外层方法有事务
AService 方法
// AService 方法
@Transactional(timeout = 5)
public void UpdateBalanceAndName(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? ";
int r1 = jdbcTemplate.update(sql1,1);
System.out.println("r1="+r1);
//---------------分界线-------------------------------
bService.updateName("王五",1);
//---------------分界线-------------------------------
String sql2 = "update t_user set balance = balance+50 where user_id = ? ";
int r2 = jdbcTemplate.update(sql2,2);
System.out.println("r2="+r2);
}
BService 方法
@Transactional(propagation = Propagation.REQUIRED,timeout = 2)
public void updateName(String name,Integer userId) {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----------3秒了-----------");
String sql = "update t_user set username = ? where user_id = ?";
int r = jdbcTemplate.update(sql,name,userId);
System.out.println("r="+r);
}
如果我们只运BService,那么无法修改数据库,会报错
@Test
public void test5(){
bService.updateName("王五",1);
}
//错误日志:TransactionTimedOutException: Transaction timed out: deadline was Tue Oct 01 11:58:30 CST 2024
但是如果我们AService 调用BService,这样就可以成功,因为事务的隔离级别是 REQUIRED
,会使用AService的事务,BService事务会失效,变成这样
@Transactional(timeout = 5)
public void UpdateBalanceAndName(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? ";
int r1 = jdbcTemplate.update(sql1,1);
System.out.println("r1="+r1);
//---------------分界线-------------------------------
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("----------3秒了-----------");
String sql = "update t_user set username = ? where user_id = ?";
int r = jdbcTemplate.update(sql,"王五",1);
System.out.println("r="+r);
//---------------分界线-------------------------------
String sql2 = "update t_user set balance = balance+50 where user_id = ? ";
int r2 = jdbcTemplate.update(sql2,2);
System.out.println("r2="+r2);
}
外层方法没有事务
// 外层的方法:这里是没事务的,所以还是按照没事务的来处理
public void UpdateBalanceAndName(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? ";
int r1 = jdbcTemplate.update(sql1,1);
System.out.println("r1="+r1);
// int i=1/0; // 如果在这个地方发生异常,是不会回滚的
//---------------分界线-------------------------------
bService.updateName("王五",1);
// int i=1/0; // 如果在这个地方发生异常,是不会回滚的
//---------------分界线-------------------------------
String sql2 = "update t_user set balance = balance+50 where user_id = ? ";
int r2 = jdbcTemplate.update(sql2,2);
System.out.println("r2="+r2);
}
// 这里是有事务的,Spring会在这里创建一个和我们配置一模一样的事物
@Transactional(propagation = Propagation.REQUIRED,readOnly = true )
public void updateName(String name,Integer userId) {
System.out.println("----------3秒了-----------");
String sql = "update t_user set username = ? where user_id = ?";
int r = jdbcTemplate.update(sql,name,userId);
System.out.println("r="+r);
}
}
其他的测试
// 外层方法:只回滚FileNotFoundException的异常
@Transactional(rollbackFor = FileNotFoundException.class)
public void UpdateBalanceAndName(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? ";
int r1 = jdbcTemplate.update(sql1,1);
System.out.println("r1="+r1);
//---------------分界线-------------------------------
bService.updateName("王五",1);
//---------------分界线-------------------------------
String sql2 = "update t_user set balance = balance+50 where user_id = ? ";
int r2 = jdbcTemplate.update(sql2,2);
System.out.println("r2="+r2);
}
// 内层方法:模拟一个 ArithmeticException
@Transactional(propagation = Propagation.REQUIRED )
public void updateName(String name,Integer userId) {
System.out.println("----------3秒了-----------");
String sql = "update t_user set username = ? where user_id = ?";
int r = jdbcTemplate.update(sql,name,userId);
int i = 1/0; // 模拟异常
System.out.println("r="+r);
}
如果按照上诉理论,updateName
方法最终会使用UpdateBalanceAndName
的事务,也就是
update t_user set balance = balance-50 where user_id = ?
生效update t_user set username = ? where user_id = ?
生效update t_user set balance = balance+50 where user_id = ?
不生效
相当于没有事务:
String sql1 = "update t_user set balance = balance-50 where user_id = ? ";
int r1 = jdbcTemplate.update(sql1,1);
String sql = "update t_user set username = ? where user_id = ?";
int r = jdbcTemplate.update(sql,name,userId);
int i = 1/0; // 这里发生异常,后面的代码不运行,但是上面的代码对数据库的操作还是有效
String sql2 = "update t_user set balance = balance+50 where user_id = ? ";
int r2 = jdbcTemplate.update(sql2,2);
实际情况是,整体都回滚
原因:Spring默认会对运行时异常进行回滚。那段异常代码,我们没有try-catch
处理,于是内部的方法向上抛异常,被Spring检测到了,就会自动回滚。
如果我们在AService上是这么配置的:不回滚ArithmeticException
@Transactional(noRollbackFor = ArithmeticException.class)
// @Transactional(timeout = 5)
public void UpdateBalanceAndName(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? ";
int r1 = jdbcTemplate.update(sql1,1);
System.out.println("r1="+r1);
// int i=1/0; // 如果在这个地方发生异常,是不会回滚的
//---------------分界线-------------------------------
bService.updateName("王五33",1);
// int i=1/0; // 如果在这个地方发生异常,是不会回滚的
//---------------分界线-------------------------------
String sql2 = "update t_user set balance = balance+50 where user_id = ? ";
int r2 = jdbcTemplate.update(sql2,2);
System.out.println("r2="+r2);
}
会报如下日志:
12:59:07.112 [main] ERROR org.springframework.transaction.interceptor.TransactionInterceptor - Application exception overridden by commit exception
java.lang.ArithmeticException: / by zero
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
REQUIRED_NEW
PROPAGATION.REQUIRED_NEW
:(需要个新的) 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果外面没有,就以自己独立的方式运行。
如果AService有没有事务,那么BService里面 的方法就要自己的事务,外层方法照样无事务运行。这个很好理解
下面我检验AService有事务的情况:
// AService
@Transactional
public void UpdateBalanceAndName(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? ";
int r1 = jdbcTemplate.update(sql1,1);
System.out.println("r1="+r1);
//---------------分界线-------------------------------
bService.updateName("王五xx",2);
//---------------分界线-------------------------------
String sql2 = "update t_user set balance = balance+50 where user_id = ? ";
int r2 = jdbcTemplate.update(sql2,2);
int i = 1/0;
System.out.println("r2="+r2);
}
// BService
@Transactional(propagation = Propagation.REQUIRES_NEW )
public void updateName(String name,Integer userId) {
System.out.println("------updateName--------");
String sql = "update t_user set username = ? where user_id = ?";
int r = jdbcTemplate.update(sql,name,userId);
System.out.println("r="+r);
}
在调用BService的updateName
方法的是时候,AService的UpdateBalanceAndName
的方法的事务会被挂起
BService的方法运行完(修改id=2
的用户的名字),因为是两个独立的事物,所以BService的修改会被提交。
而AService的方法,后面遇到异常,AService相关修改就会被回滚
总结:外层方法的异常不会影响内层方法的事务提交,但是内层方法的异常会影响外层方法的提交
Communications link failure
以下代码会导致Communications link failure
// AService
@Transactional
public void UpdateBalanceAndName(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? ";
int r1 = jdbcTemplate.update(sql1,1);
System.out.println("r1="+r1);
//---------------分界线-------------------------------
bService.updateName("王五xx",1);// 这里是原因
//---------------分界线-------------------------------
String sql2 = "update t_user set balance = balance+50 where user_id = ? ";
int r2 = jdbcTemplate.update(sql2,2);
int i = 1/0;
System.out.println("r2="+r2);
}
// BService
@Transactional(propagation = Propagation.REQUIRES_NEW )
public void updateName(String name,Integer userId) {
System.out.println("------updateName--------");
String sql = "update t_user set username = ? where user_id = ?";
int r = jdbcTemplate.update(sql,name,userId);
System.out.println("r="+r);
}
错误日志:
org.springframework.dao.RecoverableDataAccessException: PreparedStatementCallback; SQL [update t_user set username = ? where user_id = ?]; Communications link failure
Caused by: com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure
原因:由于传播级别是REQUIRES_NEW
,所以内层方法和外层方法是两个独立的事务,倒要修改id=1的数据,造成死锁

SUPPORTS
PROPAGATION.SUPPORTS
:表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行。如果外层没有事务,该方法就以非事务运行。SUPPORTS表示支持,就是B事务会支持A事务。
@Transactional(propagation = Propagation.SUPPORTS )
public void updateName(String name,Integer userId) {
System.out.println("------updateName--------");
String sql = "update t_user set username = ? where user_id = ?";
int r = jdbcTemplate.update(sql,name,userId);
int i = 1/0;// 模拟异常
System.out.println("r="+r);
}
另外,如果我们单独使用上面的代码,出现了异常也是不会回滚的。相当于这段代码没有使用
MANDATORY
PROPAGATION.MANDATORY
:(强制性的)表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。
// BService
@Transactional(propagation = Propagation.MANDATORY )
public void updateName(String name,Integer userId) {
System.out.println("------updateName--------");
String sql = "update t_user set username = ? where user_id = ?";
int r = jdbcTemplate.update(sql,name,userId);
System.out.println("r="+r);
}
如果我单独使用或者在上层方法无事务都会报如下错误:
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
NOT_SUPPORTED
PROPAGATION.NOT_SUPPORTED
:(不支持事务)表示该方法不应该运行在事务中。如果存在当前事务在该方法运行期间,当前事务将被挂起。
// AService
@Transactional
public void UpdateBalanceAndName(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? ";
int r1 = jdbcTemplate.update(sql1,1);
System.out.println("r1="+r1);
//---------------分界线-------------------------------
bService.updateName("王五xx",2);
//---------------分界线-------------------------------
String sql2 = "update t_user set balance = balance+50 where user_id = ? ";
int r2 = jdbcTemplate.update(sql2,2);
System.out.println("r2="+r2);
}
// BService
@Transactional(propagation = Propagation.NOT_SUPPORTED )
public void updateName(String name,Integer userId) {
System.out.println("------updateName--------");
String sql = "update t_user set username = ? where user_id = ?";
int r = jdbcTemplate.update(sql,name,userId);
int i = 1/0; // 模拟异常
System.out.println("r="+r);
}
BService发生异常是不回滚的,AService接收到BService会回滚
NOT_SUPPORTED 和 REQUIRED_NEW 的异同
同:如果上层方法有事务,上层方法都是被挂起
异:
- NOT_SUPPORTED 是在无事务下运行,那么相关的方法发生异常不回滚
- REQUIRED_NEW 是在有事务下运行,那么相关方法发生异常会回滚
NEVER
PROPAGATION.NEVER
:(不会运行在有事务的环境)表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常
异常为:
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation 'never'
NEVER 和 MANDATORY 的异同
同:都是会直接抛异常
异:NEVER 要求不能有事务,MANDATORY 要求要有事务
- 单独使用:NEVER 不抛异常,MANDATORY 抛异常
- 上层无事务:NEVER 不抛异常,MANDATORY 抛异常
- 上层有事务:NEVER 抛异常,MANDATORY 不抛异常
NESTED
PROPAGATION.NESTED
:(嵌套的)表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务。
@Service
public class BService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional(propagation = Propagation.NESTED )
public void updateName(String name,Integer userId) {
try {
System.out.println("------updateName--------");
String sql = "update t_user set username = ? where user_id = ?";
int r = jdbcTemplate.update(sql,name,userId);
System.out.println("r="+r);
int i = 1/0;
} catch (DataAccessException e) {
e.printStackTrace();
}
}
}
@Service
public class AService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private BService bService;
@Transactional
public void UpdateBalanceAndName(){
String sql1 = "update t_user set balance = balance-50 where user_id = ? ";
int r1 = jdbcTemplate.update(sql1,1);
System.out.println("r1="+r1);
//---------------分界线-------------------------------
try {
bService.updateName("李四",1);
} catch (Exception e) {
e.printStackTrace();
}
//---------------分界线-------------------------------
String sql2 = "update t_user set balance = balance+50 where user_id = ? ";
int r2 = jdbcTemplate.update(sql2,2);
System.out.println("r2="+r2);
}
}

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1909773034@qq.com