经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Spring » 查看文章
【Spring系列】- Spring事务底层原理
来源:cnblogs  作者:怒放吧德德  时间:2022/11/21 9:03:00  对本文有异议

Spring事务底层原理

??生命不息,写作不止
?? 继续踏上学习之路,学之分享笔记
?? 总有一天我也能像各位大佬一样
?? 一个有梦有戏的人 @怒放吧德德
??分享学习心得,欢迎指正,大家一起学习成长!

事务.jpg

前言

昨天学习了bean生命周期底层原理,今天就来接着简单学习spring事务的底层理解。

实验准备

配置文件

首先在配置文件中配置jdbcTemplate和事务管理器,并且需要开启事务的注解@EnableTransactionManagement以及@Configuration注解

  1. @ComponentScan("com.lyd")
  2. @EnableTransactionManagement
  3. @Configuration
  4. public class ApplicationConfig {
  5. @Bean
  6. public JdbcTemplate jdbcTemplate() {
  7. return new JdbcTemplate(dataSource());
  8. }
  9. @Bean
  10. public PlatformTransactionManager transactionManager() {
  11. DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
  12. transactionManager.setDataSource(dataSource());
  13. return transactionManager;
  14. }
  15. @Bean
  16. public DataSource dataSource() {
  17. DriverManagerDataSource dataSource = new DriverManagerDataSource();
  18. dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/eladmin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&useOldAliasMetadataBehavior=true");
  19. dataSource.setUsername("root");
  20. dataSource.setPassword("12356");
  21. return dataSource;
  22. }
  23. }

准备数据表

本次实验使用学生表,就简单几个字段。
image.png

Spring事务的底层原理

       我们在需要加上事务的方法上添加@Transactional注解,然后在此方法中使用jdbcTemplate去执行SQL语句,再来执行此方法,观察可以看到,事务真的回滚了。

  1. @Component
  2. public class UserService {
  3. @Autowired
  4. private JdbcTemplate jdbcTemplate;
  5. @Transactional
  6. public void test(){
  7. jdbcTemplate.execute("insert into student values (1, 'lyd', 18, '20183033210')");
  8. throw new NullPointerException();
  9. }
  10. }

原理

       首先spring会调用代理对象,对于事务,代理对象会通过执行事务的切面逻辑。在这个切面逻辑,Spring会去判断是否含有@Transactional事务注解,如果有才会去开启事务。spring的事务管理器会新建一个数据库连接conn,紧接着会把conn.autocommit 设置为 false ,autocommit(自动提交),每次执行完SQL后就会立马提交,因此这里需要设置为false。spring默认是开启了自动提交,当SQL执行结束之后就会提交,当遇到异常的时候,由于前面的事务都已经提升,因此就没法回滚了,所以需要把自动提交给关闭了。最后在通过第一次创建的对象(Spring个生命周期中通过构造方法创造的对象)去执行test方法。接着会去执行SQL语句,在此SQL执行完之后是不会进行提交的,在执行SQL语句之前,jdbcTemplate会去拿到事务管理器创建的这个数据库连接conn。当执行完test方法后,Spring事务会去判断是否有异常,没有异常就会提交事务(conn.commit()),否者就会事务回滚(conn.rollback());
image.png

Spring事务失效

       接下来实现一个案例,在test方法中调用本类的另一个方法Add,两个方法都是执行了插入的SQL语句。两个方法都加上了@Transactional注解,只是在第二个方法中的注解标上一个策略:propagation = Propagation.NEVER,这个的意思是:总是非事务地执行,如果存在一个活动事务,则抛出异常。按道理来说以下代码执行将会出现异常,并且会回滚事务。

  1. @Component
  2. public class UserService {
  3. @Autowired
  4. private JdbcTemplate jdbcTemplate;
  5. @Transactional
  6. public void test(){
  7. jdbcTemplate.execute("insert into student values (1, 'lyd', 18, '20183033210')");
  8. doAdd();
  9. }
  10. @Transactional(propagation = Propagation.NEVER)
  11. public void doAdd() {
  12. jdbcTemplate.execute("insert into student values (2, 'lyy', 18, '20183033211')");
  13. }
  14. }

可是,最后的结果却不是预期结果。
image.png

失效原理

       可见最后还是将两条数据插入了,显然这个事务是不回滚的,那么这是为什么呢?从上面说事务的底层原理就可以知道,当spring创建了代理对象,在代理对象内部的test方法中的切面逻辑,会去创建数据库连接等等,最后由普通对象(UserService.class通过构造方法去创建的对象)去执行test,也就是相当于是使用了普通对象去执行doAdd方法,普通对象就只是构造方法实例化的一个对象,执行doAdd并不会去检测这个@Transactional注解,因此这个事务就不会被执行到,也就不会回滚。然而spring应该是在执行代理类的test方法时候回去判断@Transactional注解,会有额外的逻辑去判断事务,也就是doAdd应该也要由代理对象去执行。

解决方案

那么有办法解决吗?办法肯定过有,接下来介绍一种解决方法

方案一

将add方法抽到另一个bean类里面

  1. @Component
  2. public class UserBaseService {
  3. @Autowired
  4. private JdbcTemplate jdbcTemplate;
  5. @Transactional(propagation = Propagation.NEVER)
  6. public void doAdd() {
  7. jdbcTemplate.execute("insert into student values (2, 'lyy', 18, '20183033211')");
  8. }
  9. }

再来通过bean对象来执行这个doAdd方法

  1. @Component
  2. public class UserService {
  3. private JdbcTemplate jdbcTemplate;
  4. private UserBaseService userBaseService;
  5. jdbcTemplate.execute("insert into student values (1, 'lyd', 18, '20183033210')");
  6. userBaseService.doAdd();
  7. }
  8. }

然后把数据表清空,在此执行
image.png

这回就报错了,而且也是我们所期望的错误,在看一下数据表,发现数据没有保存进去。

方案二

       那如果我们还是想要在本类中去执行这个doAdd方法呢?其实也是可以,就是我们通过自己调用自己的方式,在本类中引用本类的bean对象,此时他就是一个代理对象,这样事务的策略也就能够实现了。

  1. @Autowired
  2. private UserService userService;
  3. @Transactional
  4. public void test(){
  5. jdbcTemplate.execute("insert into student values (1, 'lyd', 18, '20183033210')");
  6. throw new NullPointerException();
  7. userService.doAdd();
  8. }

也能得到我们预期的实现效果,数据库中也没有相关数据。
image.png

@Configuration底层原理

在上面我们提到了jdbcTemplate获得数据库连接,那么这个又是如何得到呢?我们来看一下一开始的配置。

  1. @Bean
  2. public JdbcTemplate jdbcTemplate() {
  3. return new JdbcTemplate(dataSource());
  4. }
  5. @Bean
  6. public PlatformTransactionManager transactionManager() {
  7. DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
  8. transactionManager.setDataSource(dataSource());
  9. return transactionManager;
  10. }
  11. @Bean
  12. public DataSource dataSource() {
  13. DriverManagerDataSource dataSource = new DriverManagerDataSource();
  14. dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/eladmin?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useSSL=false&useOldAliasMetadataBehavior=true");
  15. dataSource.setUsername("root");
  16. dataSource.setPassword("12356");
  17. return dataSource;
  18. }

贯穿逻辑

       在创建 jdbcTemplate 的bean对象的时候,会去调用 dataSource ,在创建事务管理PlatformTransactionManager也会去调用一次dataSource方法,在这个方法中会创建新的dataSource,那么,所获得的事务不就不一样了吗,显然这是不行的。那么在spring中,他是如何实现单例的呢?就是通过 @Configuration 注解来实现。
       其实spring会通过ThreadLocal(线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构),在spring中ThreadLocal<Map<DataSource, conn>>是根据 DataSource来存储连接conn,如果没有 @Configuration注解来实现,两次使用的DataSource就是不同的。 jdbcTemplate在获取 DataSource 对象的时候,会去ThreadLocal的map根据jdbcTemplate自己的DataSource去找连接,然而这时候DataSource对象不同,他就找不到,就会自己从新生成连接,当执行完SQL语句之后,就会去提交,这时候接下来再抛异常已经没用了。

@Configuration原理

       然而,@Configuration能够实现单例DataSource对象呢?这就是因为@Configuration也是采用了动态代理会创建ApplicationConfig的代理对象。spring会创建ApplicationConfig的代理对象,这个代理对象会去调用jdbcTemplate方法,而代理对象会执行super.jdbcTemplate(),在这个方法中需要执行dataSource()方法,他会首先去容器中找是否有dataSource的bean对象,如果有直接返回,没有就会创建,创建之后会将单例进行保存。接着transactionManager方法也是由代理对象去执行的,在他需要dataSource对象的时候,也是现去容器查找,这就能够实现他们两个的dataSource是一样的了。这样事务拿到的数据库连接就是相同了,如果是在使用dataSource这个bean对象的时候,使用的beanName是不同,那么最后得到的连接也就不同。

??创作不易,如有错误请指正,感谢观看!记得点赞哦!??

原文链接:https://www.cnblogs.com/lyd-code/p/16909632.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号