欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 房产 > 家装 > Spring 事务

Spring 事务

2025/3/12 21:48:57 来源:https://blog.csdn.net/weixin_74744611/article/details/146171568  浏览:    关键词:Spring 事务

1.什么是事务

事务就是把所有的操作一起视为一个整体,一起向数据库提交或者撤销操作请求.这组操作要么同时成功,要么同时失败

事务存在的作用:

比如转账:A给B转100块 此时A的账户余额:-100

                B收到100块,此时B的账户余额:+100

如果没有事务,A此时转账是成功的,但是B可能是由于网络等因素的影响,他的接收是失败的,此时就会出现A少了100块,B并没有收到.这问题就很严重了.

1.1事务的操作

1.开始事务 start transaction/ begin (⼀组操作前开启事务)

2.提交事务:commit (这组操作全部成功, 提交事务)

3.回滚事务: rollback (这组操作中间任何⼀个操作出现异常, 回滚事务)

2.Spring 事务实现

实现事务有两种方式:

1.编程式事务(通过代码来实现)

2.声明式事务(通过注解来实现)

a.学习事务之前的准备:数据库两张表(user_info,log_info)

具体代码就不展示了,就是一些平常的SQL语句.

b.创建项目并且引入依赖,在application.yml配置文件中连接数据库

c.创建实体类,应用分层.

2.1编程式事务

做完准备工作之后,我们先用编程式来操作事务

package org.ioc.com.spring_trans.controller;mport com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;@RequestMapping("/user")
@RestController
public class UserController {// JDBC 事务管理器@Autowiredprivate DataSourceTransactionManager dataSourceTransactionManager;// 定义事务属性@Autowiredprivate TransactionDefinition transactionDefinition;@Autowiredprivate UserService userService;@RequestMapping("/registry")public String registry(String name, String password) {// 开启事务TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);//⽤⼾注册userService.registryUser(name, password);//提交事务dataSourceTransactionManager.commit(transactionStatus);//回滚事务//dataSourceTransactionManager.rollback(transactionStatus);return "注册成功";}
}
//提交事务
dataSourceTransactionManager.commit(transactionStatus);
//回滚事务
//dataSourceTransactionManager.rollback(transactionStatus);return"注册成功";

我们观察发现编程式实现的代码量虽然不是很多,但是对于我们开发来说,也是浪费一些时间.所以Spring再次出手,利用注解省去了编程式的实现,我们只需要实现注解即可.

2.2 Spring 声明事务

使用@Transactional注解,只需要在想要声明事务的方法上加上这个注解即可.无需手动开启,提交,回滚.不要998,不要888.如果中途发生异常,@Transactional会自动回滚.

url:http://127.0.0.1:8080/user/register?userName=admin&password=admin

1.我们先看下执行程序运行正常的代码:

/*程序正常执行,自动提交事务*/@Transactional@RequestMapping("/register")public Integer register(String userName,String password){Integer result=userInfoService.insert(userName,password);return  result;}

 代码执行完成之后我们观察下运行结果

此时出现commiting就证明事务提交成功了.此时观察数据库也成功插入一条数据了


 

 2.程序存在异常代码自动回滚

url:http://127.0.0.1:8080/user/r1?userName=admin&password=admin

  /*程序存在异常,自动回滚*/@Transactional@RequestMapping("/r1")public Integer r1(String userName,String password){Integer result=userInfoService.insert(userName,password);int a=10/0;return  result;}

我们看到数据虽然已经提交给数据库,但是事务并没有提交,造成了回滚. 

 数据库并没有显示数据


3.使用try-catch捕获异常后,事务还是可以正常提交.因为我们在方法中已经对异常进行了处理,@Transactional就感知不到异常了.(类似明星绯闻,公关即使处理,外界知道的就少了) 

  /*用try-catch捕获异常后可以自动提交事务try-catch捕获后,@Transactional感知不到有异常*/@Transactional@RequestMapping("/r3")public Integer r3 (String userName,String password){Integer result=userInfoService.insert(userName,password);try {int a=10/0;}catch (Exception e){e.printStackTrace();}return  result;}

程序虽然有异常但是还是成功提交了

数据库也成功插入数据

 


 4.重新抛出异常后,事务还是会回滚

/*重新抛出异常或者手动抛出异常依旧会回滚*/@Transactional@RequestMapping("/r4")public Integer r4 (String userName,String password){Integer result=userInfoService.insert(userName,password);try {int a=10/0;}catch (Exception e){//抛出异常throw e;}return  result;}

没有出现commiting,提交事务未成功 

此时数据库也没有结果 


5.异常try-catch住后,依然可以通过手动回滚来回滚事务

使用

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

@Transactional@RequestMapping("/r5")public Integer r5 (String userName,String password){Integer result=userInfoService.insert(userName,password);try {int a=10/0;}catch (Exception e){//手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();}return  result;}

实现了回滚 

 数据库也是没有记录的


6.对于Exception异常不会处理,依然会提交事务

  /*事务对于异常的处理//Exception不会回滚*/@Transactional@SneakyThrows//这个注解也是通过try-catch捕获的异常@RequestMapping("/r6")public Integer r6 (String userName,String password){Integer result=userInfoService.insert(userName,password);if (true){throw new Exception();}return  result;}

观察结果,事务提交成功. 


7.既然Exception 异常可以提交事务我们试下RuntimeException异常

/*事务对于异常的处理//异常中RuntimeException和Error会自动回滚,其他的不会*/@Transactional@RequestMapping("/r7")public Integer r7 (String userName,String password){Integer result=userInfoService.insert(userName,password);if (true){throw new RuntimeException();}return  result;}

这里 RuntimeException异常并没有提交事务,而是回滚了.因为规定RuntimeException和Error会自动回滚,但是其他的异常不会


8.如果想要其他异常也回滚,我们需要设置

@Transactional(rollbackFor = Exception.class)

 /*注解中指定所有异常都要回滚*/@Transactional(rollbackFor = Exception.class)@RequestMapping("/r8")@SneakyThrowspublic Integer r8 (String userName,String password){Integer result=userInfoService.insert(userName,password);if (true){throw new Exception();}return  result;}

 指定回滚异常类型即可.

结果显示其他类型也实现了回滚.

3.事务隔离级别

3.1MySQL事务隔离

读未提交(脏读):这种隔离级别的事务可以看到其他事务中未提交事务的数据.

                比如A事务只是刚开始事务,还未提交事务,B事务也可以阅读A事务未提交的数据.

 读提交(不可重复读):该隔离级别的事务能读取到已经提交事务的数据

                事务1开始,更新A和B的余额,但还没提交。这时事务2第一次读取B的余额,应该得到原来的100元,而不是事务1未提交的200元。然后事务1提交后,事务2再次读取,得到200元。这样两次读取结果不同,说明不可重复读,但避免了脏读。 


可重复读 :可重复读(REPEATABLE READ): 事务不会读到其他事务对已有数据的修改, 即使其他事务已提交. 也 就可以确保同⼀事务多次查询的结果⼀致, 但是其他事务新插⼊的数据, 是可以感知到的. 这也就引发了幻读问题. 可重复读, 是 MySQL 的默认事务隔离级别

    ⽐如此级别的事务正在执⾏时, 另⼀个事务成功的插⼊了某条数据, 但因为它每次查询的结果都是 ⼀样的, 所以会导致查询不到这条数据, ⾃⼰重复插⼊时⼜失败(因为唯⼀约束的原因). 明明在事务 中查询不到这条信息,但⾃⼰就是插⼊不进去, 这个现象叫幻读

串行化 :序列化, 事务最⾼隔离级别. 它会强制事务排序, 使之不会发⽣冲突, 从⽽解

决了脏读, 不可重复读和幻读问题, 但因为执⾏效率低, 所以真正使⽤的场景并不多

3.2 Spring的事务隔离级别

Spring 中事务隔离级别有5 种:
1. Isolation.DEFAULT : 以连接的数据库的事务隔离级别为主.
2. Isolation.READ_UNCOMMITTED : 读未提交, 对应SQL标准中 READ UNCOMMITTED
3. Isolation.READ_COMMITTED : 读已提交,对应SQL标准中 READ COMMITTED
4. Isolation.REPEATABLE_READ : 可重复读, 对应SQL标准中 REPEATABLE READ
5. Isolation.SERIALIZABLE : 串⾏化, 对应SQL标准中 SERIALIZABLE
其实和MySQL的隔离事务是一样的,只是多了一个默认的.也就是第一条,接入的数据库是什么隔离级别Spring默认就是什么级别.
Spring 中事务隔离级别可以通过 @Transactional 中的 isolation 属性进⾏设置

3.3 事务传播机制

3.3.1什么是事务传播机制

⽐如有两个⽅法A, B都被 @Transactional 修饰, A⽅法调⽤B⽅法
A⽅法运⾏时, 会开启⼀个事务. 当A调⽤B时, B⽅法本⾝也有事务, 此时B⽅法运⾏时, 是加⼊A的事务, 还是创建⼀个新的事务呢?  这样就涉及到了事务传播机制.
事务隔离级别解决的是多个事务同时调⽤⼀个数据库的问题
⽽事务传播机制解决的是⼀个事务在多个节点(⽅法)中传递的问题
        

3.3.2事务的传播机制

@Transactional 注解⽀持事务传播机制的设置, 通过 propagation 属性来指定传播⾏为.
Spring 事务传播机制有以下7种:

        1.Propagation.REQUIRED: 默认的事务传播级别.如果当前存在事务,则加入该事务,如果当前没有则会创建一个新的事务.
        比如一对新婚情侣,如果结婚前买了婚房,直接住进新房.但是如果没有新房子就要买个新房

        代码解释:

        

TestController调用UserInfoService和LogInfoService的两个方法,三个方法都是使用同一个事务.程序都正确执行的情况下事务都会提交.但是如果其中有一个抛出了异常,那么其余也会回滚

就是我们所谓的一荣俱荣,一损俱损.同年同月同日生,同年同月同日死.

TestController代码:

public class TestController {@Autowiredprivate UserInfoService userService;@Autowiredprivate LogInfoService logInfoService;@Transactional@RequestMapping("/register")public Boolean register(String userName, String password){/*** 用户表的插入和日志表的插入, 理应在Service完成* 为了方便, 咱们放在Controller里面完成*/Integer result = userService.insert(userName, password);System.out.println("插入用户表, result: "+ result);//插入日志表Integer logResult = logInfoService.insert(userName, "用户注册");System.out.println("插入日志表, result: "+ logResult);return true;}
}

 LogInfoService方法(假如LogInfoService存在异常)

@Service
public class LogInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Transactional(propagation = Propagation.REQUIRED)public Integer insert(String userName, String op) {Integer result = logInfoMapper.insertLog(userName, op);int a = 10/0;return result;}
}

 UserInfoService方法:(无异常)

@Service
public class UserInfoService {@Autowiredprivate UserInfoMapper userInfoMapper;@Transactional(propagation = Propagation.REQUIRED)public Integer insert(String userName, String password) {Integer result=userInfoMapper.queryInsert(userName,password);return result;}
}

上述代码中 由于哥三个都是使用的同一个事务,LogInfoService存在异常,那么UserInfoService也不会成功提交.

就像是兄弟二人共吃一块蛋糕,此时老二把蛋糕掉地上了,那么就谁也吃不上了.


      
        2.Propagation.SUPPORTS: 如果当前存在事务,则加入该事务,如果没有事务,则以非事务的方式继续执行
        还是类似刚结婚的新婚燕尔,如果有婚房,直接住进婚房,但是如果没有婚房.租房子住也可以

        3.Propagation.MANDTORY: 强制性.如果当前存在事务,就加入事务,如果当前没有事务,则会抛出异常
        就像是结婚后如果有婚房还好,但是如果没婚房,也可以拜拜就拜拜,下一个更乖.
        

        4.Propagation.REQUIRES_NEW:
创建⼀个新的事务. 如果当前存在事务, 则把当前事务挂起. 也就是说不管外部⽅法是否开启事务, Propagation.REQUIRES_NEW 修饰的内部⽅法都会新开启⾃⼰的事务, 且开启的事务相互独⽴, 互不⼲扰.

         就像是两人结婚后本来结婚前你有的房子我不要,我还要自己再买一套大平层来住,不稀罕小屋子

代码解释:

 TestController代码:

public class TestController {@Autowiredprivate UserInfoService userService;@Autowiredprivate LogInfoService logInfoService;@Transactional@RequestMapping("/register")public Boolean register(String userName, String password){/*** 用户表的插入和日志表的插入, 理应在Service完成* 为了方便, 咱们放在Controller里面完成*/Integer result = userService.insert(userName, password);System.out.println("插入用户表, result: "+ result);//插入日志表Integer logResult = logInfoService.insert(userName, "用户注册");System.out.println("插入日志表, result: "+ logResult);return true;}
}

 UserInfoService代码:

@Service
public class UserInfoService {@Autowiredprivate UserInfoMapper userInfoMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public Integer insert(String userName, String password) {Integer result=userInfoMapper.queryInsert(userName,password);return result;}
}

LogInfoService代码:

@Service
public class LogInfoService {@Autowiredprivate LogInfoMapper logInfoMapper;@Transactional(propagation = Propagation.REQUIRES_NEW)public Integer insert(String userName, String op) {Integer result = logInfoMapper.insertLog(userName, op);int a = 10/0;return result;}
}

上述代码中即使 LogInfoService发生异常回滚事务,对于UserInfoService没有丝毫影响,因为他们两个是不同的事务.


5 .Propagation.NOT_SUPPORTED : 以⾮事务⽅式运⾏, 如果当前存在事务, 则把当前事务挂起(不
⽤).
         结婚后即使你有房子我也不住,咱们继续租房子住.


 6.Propagation.NEVER : 以⾮事务⽅式运⾏, 如果当前存在事务, 则抛出异常.

婚后一直租房住,但是如果你有房子,我们立即离婚.

TestController代码

public class TestController {@Autowiredprivate UserInfoService userService;@Autowiredprivate LogInfoService logInfoService;@Transactional@RequestMapping("/register")public Boolean register(String userName, String password){/*** 用户表的插入和日志表的插入, 理应在Service完成* 为了方便, 咱们放在Controller里面完成*/Integer result = userService.insert(userName, password);System.out.println("插入用户表, result: "+ result);//插入日志表Integer logResult = logInfoService.insert(userName, "用户注册");System.out.println("插入日志表, result: "+ logResult);return true;}
}

 UserInfoService代码:

@Service
public class UserInfoService {@Autowiredprivate UserInfoMapper userInfoMapper;@Transactional(propagation = Propagation.NEVER)public Integer insert(String userName, String password) {Integer result=userInfoMapper.queryInsert(userName,password);return result;}
}

 TestController调用UserInfoService中的方法.此时TestController存在事务,那么UserInfoService就会抛出异常.


7.Propagation.NESTED : 如果当前存在事务, 则创建⼀个事务作为当前事务的嵌套事务来运⾏. 如果当前没有事务, 则该取值等价于 PROPAGATION_REQUIRED .

如果没房,我们就一起攒钱买个新房,如果有新房,我们就以房子为根基去创业.

解释:

此时如果还是 LogInfoService存在异常,和.Propagation.REQUIRED方式不同.由于他们三个使用的是不同的事务.所以只需要存在异常的LogInfoService自己回滚即可,而对UserInfoService不会有影响.

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词