热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

开发笔记:一文带你看懂Spring事务!

篇首语:本文由编程笔记#小编为大家整理,主要介绍了一文带你看懂Spring事务!相关的知识,希望对你有一定的参考价值。 来源:Java3y(ID:java3y) 前言 Spring事务管理

篇首语:本文由编程笔记#小编为大家整理,主要介绍了一文带你看懂Spring事务!相关的知识,希望对你有一定的参考价值。





来源:Java3y(ID:java3y)


前言


Spring事务管理我相信大家都用得很多,但可能仅仅局限于一个@Transactional注解或者在XML中配置事务相关的东西。不管怎么说,日常可能足够我们去用了。但作为程序员,无论是为了面试还是说更好把控自己写的代码,还是应该得多多了解一下Spring事务的一些细节。


这里我抛出几个问题,看大家能不能瞬间答得上:



  • 如果嵌套调用含有事务的方法,在Spring事务管理中,这属于哪个知识点?


  • 我们使用的框架可能是Hibernate/JPA或者是Mybatis,都知道的底层是需要一个session/connection对象来帮我们执行操作的。要保证事务的完整性,我们需要多组数据库操作要使用同一个session/connection对象,而我们又知道Spring IOC所管理的对象默认都是单例的,这为啥我们在使用的时候不会引发线程安全问题呢?内部Spring到底干了什么?


  • 人家所说的BPP又是啥东西?


  • Spring事务管理重要接口有哪几个?



一、阅读本文需要的基础知识


阅读这篇文章的同学我默认大家都对Spring事务相关知识有一定的了解了。(ps:如果不了解点解具体的文章去阅读再回到这里来哦)


我们都知道,Spring事务是Spring AOP的最佳实践之一,所以说AOP入门基础知识(简单配置,使用)是需要先知道的。如果想更加全面了解AOP可以看这篇文章:AOP重要知识点(术语介绍、全面使用)。说到AOP就不能不说AOP底层原理:动态代理设计模式。到这里,对AOP已经有一个基础的认识了。于是我们就可以使用XML/注解方式来配置Spring事务管理。


在IOC学习中,可以知道的是Spring中Bean的生命周期(引出BPP对象)并且IOC所管理的对象默认都是单例的:单例设计模式,单例对象如果有"状态"(有成员变量),那么多线程访问这个单例对象,可能就造成线程不安全。那么何为线程安全?,解决线程安全有很多方式,但其中有一种:让每一个线程都拥有自己的一个变量:ThreadLocal


二、两个不靠谱直觉的例子


2.1第一个例子


之前朋友问了我一个例子:


在Service层抛出Exception,在Controller层捕获,那如果在Service中有异常,那会事务回滚吗?


// Service方法
@Transactional
public Employee addEmployee() throws Exception {
    Employee employee = new Employee("3y"23);
    employeeRepository.save(employee);
    // 假设这里出了Exception
    int i = 1 / 0;
    return employee;
}
// Controller调用
@RequestMapping("/add")
public Employee addEmployee() {
    Employee employee = null;
    try {
        employee = employeeService.addEmployee();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return employee;
}

第一反应:不会回滚吧。



  • 我当时是这样想的:因为Service层已经抛出了异常,由Controller捕获。那是否回滚应该由Controller的catch代码块中逻辑来决定,如果catch代码块没有回滚,那应该是不会回滚。



但朋友经过测试说,可以回滚阿。(pappapa打脸)



一文带你看懂Spring事务!

发生了运行时Exception,Spring事务管理自动回滚


看了一下文档,原来文档有说明:



By default checked exceptions do not result in the transactional interceptor marking the transaction for rollback and instances of RuntimeException and its  subclasses do



结论:如果是编译时异常不会自动回滚,如果是运行时异常,那会自动回滚!


2.2第二个例子



第二个例子来源于知乎@柳树文章,文末会给出相应的URL



我们都知道,带有@Transactional注解所包围的方法就能被Spring事务管理起来,那如果我在当前类下使用一个没有事务的方法去调用一个有事务的方法,那我们这次调用会怎么样?是否会有事务呢?


用代码来描述一下:


// 没有事务的方法去调用有事务的方法
public Employee addEmployee2Controller() throws Exception {
    return this.addEmployee();
}
@Transactional
public Employee addEmployee() throws Exception {
    employeeRepository.deleteAll();
    Employee employee = new Employee("3y"23);
    // 模拟异常
    int i = 1 / 0;
    return employee;
}

我第一直觉是:这跟Spring事务的传播机制有关吧。


其实这跟Spring事务的传播机制没有关系,下面我讲述一下:



  • Spring事务管理用的是AOP,AOP底层用的是动态代理。所以如果我们在类或者方法上标注注解@Transactional,那么会生成一个代理对象。



接下来我用图来说明一下:



一文带你看懂Spring事务!

Spring会自动生成代理对象


显然地,我们拿到的是代理(Proxy)对象,调用addEmployee2Controller()方法,而addEmployee2Controller()方法的逻辑是target.addEmployee(),调用回原始对象(target)的addEmployee()。所以这次的调用压根就没有事务存在,更谈不上说Spring事务传播机制了。


原有的数据:



一文带你看懂Spring事务!

原有的数据


测试结果:压根就没有事务的存在



一文带你看懂Spring事务!

没有事务的存在


2.2.1再延伸一下

从上面的测试我们可以发现:如果是在本类中没有事务的方法来调用标注注解@Transactional方法,最后的结论是没有事务的。那如果我将这个标注注解的方法移到别的Service对象上,有没有事务?


@Service
public class TestService {
    @Autowired
    private EmployeeRepository employeeRepository;
    @Transactional
    public Employee addEmployee() throws Exception {
        employeeRepository.deleteAll();
        Employee employee = new Employee("3y"23);
        // 模拟异常
        int i = 1 / 0;
        return employee;
    }
}
@Service
public class EmployeeService {
    @Autowired
    private TestService testService;
    // 没有事务的方法去调用别的类有事务的方法
    public Employee addEmployee2Controller() throws Exception {
        return testService.addEmployee();
    }
}

测试结果:



一文带你看懂Spring事务!

抛出了运行时异常,但我们的数据还是存在的!


因为我们用的是代理对象(Proxy)去调用addEmployee()方法,那就当然有事务了。



看完这两个例子,有没有觉得3y的直觉是真的水!



三、Spring事务传播机制



如果嵌套调用含有事务的方法,在Spring事务管理中,这属于哪个知识点?



在当前含有事务方法内部调用其他的方法(无论该方法是否含有事务),这就属于Spring事务传播机制的知识点范畴了。


Spring事务基于Spring AOP,Spring AOP底层用的动态代理,动态代理有两种方式:



  • 基于接口代理(JDK代理)



    • 基于接口代理,凡是类的方法非public修饰,或者用了static关键字修饰,那这些方法都不能被Spring AOP增强



  • 基于CGLib代理(子类代理)



    • 基于子类代理,凡是类的方法使用了private、static、final修饰,那这些方法都不能被Spring AOP增强





至于为啥以上的情况不能增强,用你们的脑瓜子想一下就知道了。



值得说明的是:那些不能被Spring AOP增强的方法并不是不能在事务环境下工作了。只要它们被外层的事务方法调用了,由于Spring事务管理的传播级别,内部方法也可以工作在外部方法所启动的事务上下文中。



至于Spring事务传播机制的几个级别,我在这里就不贴出来了。这里只是再次解释“啥情况才是属于Spring事务传播机制的范畴”。



四、多线程问题



我们使用的框架可能是Hibernate/JPA或者是Mybatis,都知道的底层是需要一个session/connection对象来帮我们执行操作的。要保证事务的完整性,我们需要多组数据库操作要使用同一个session/connection对象,而我们又知道Spring IOC所管理的对象默认都是单例的,这为啥我们在使用的时候不会引发线程安全问题呢?内部Spring到底干了什么?



回想一下当年我们学Mybaits的时候,是怎么编写?



一文带你看懂Spring事务!

Mybatis工具类部分代码截图


没错,用的就是ThreadLocal,同样地,Spring也是用的ThreadLocal。


以下内容来源《精通 Spring4.x》



我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态的“状态性对象”采用ThreadLocal封装,让它们也成为线程安全的“状态性对象”,因此,有状态的Bean就能够以singleton的方式在多线程中工作。



我们可以试着点一下进去TransactionSynchronizationManager中看一下:



一文带你看懂Spring事务!

全都是ThreadLocal


五、啥是BPP?


BBP的全称叫做:BeanPostProcessor,一般我们俗称对象后处理器



  • 简单来说,通过BeanPostProcessor可以对我们的对象进行“加工处理”。



Spring管理Bean(或者说Bean的生命周期)也是一个常考的知识点,我在秋招也重新整理了一下步骤,因为比较重要,所以还是在这里贴一下吧:



  1. ResouceLoader加载配置信息


  2. BeanDefintionReader解析配置信息,生成一个一个的BeanDefintion


  3. BeanDefintion由BeanDefintionRegistry管理起来


  4. BeanFactoryPostProcessor对配置信息进行加工(也就是处理配置的信息,一般通过PropertyPlaceholderConfigurer来实现)


  5. 实例化Bean


  6. 如果该Bean配置/实现了InstantiationAwareBean,则调用对应的方法


  7. 使用BeanWarpper来完成对象之间的属性配置(依赖)


  8. 如果该Bean配置/实现了Aware接口,则调用对应的方法


  9. 如果该Bean配置了BeanPostProcessor的before方法,则调用


  10. 如果该Bean配置了init-method或者实现InstantiationBean,则调用对应的方法


  11. 如果该Bean配置了BeanPostProcessor的after方法,则调用


  12. 将对象放入到HashMap中


  13. 最后如果配置了destroy或者DisposableBean的方法,则执行销毁操作




一文带你看懂Spring事务!

Application中Bean的声明周期


其中也有关于BPP图片:



一文带你看懂Spring事务!

BBP所在的位置


5.1为什么特意讲BPP?


Spring AOP编程底层通过的是动态代理技术,在调用的时候肯定用的是代理对象。那么Spring是怎么做的呢?



我只需要写一个BPP,在postProcessBeforeInitialization或者postProcessAfterInitialization方法中,对对象进行判断,看他需不需要织入切面逻辑,如果需要,那我就根据这个对象,生成一个代理对象,然后返回这个代理对象,那么最终注入容器的,自然就是代理对象了。



Spring提供了BeanPostProcessor,就是让我们可以对有需要的对象进行“加工处理”啊!


六、认识Spring事务几个重要的接口


Spring事务可以分为两种:



  • 编程式事务(通过代码的方式来实现事务)


  • 声明式事务(通过配置的方式来实现事务)



编程式事务在Spring实现相对简单一些,而声明式事务因为封装了大量的东西(一般我们使用简单,里头都非常复杂),所以声明式事务实现要难得多。


在编程式事务中有以下几个重要的了接口:



  • TransactionDefinition:定义了Spring兼容的事务属性(比如事务隔离级别、事务传播、事务超时、是否只读状态)


  • TransactionStatus:代表了事务的具体运行状态(获取事务运行状态的信息,也可以通过该接口间接回滚事务等操作)


  • PlatformTransactionManager:事务管理器接口(定义了一组行为,具体实现交由不同的持久化框架来完成---类比JDBC)





PlatformTransactionManager解析


在声明式事务中,除了TransactionStatus和PlatformTransactionManager接口,还有几个重要的接口:



  • TransactionProxyFactoryBean:生成代理对象


  • TransactionInterceptor:实现对象的拦截


  • TransactionAttrubute:事务配置的数据



最后


本文主要讲了Spring事务管理一些比较重要的知识点,当然在学习的过程中还看到其他的知识点,如果想要继续学习的同学不妨通过下面给出的参考资料继续阅读。


参考资料:



  • 那些年,我们一起追的Spring



    • https://zhuanlan.zhihu.com/p/41961670



  • 《精通Spring 4.x 企业应用开发实战》


  • 《Spring技术内幕》







觉得我的文章写得不错,不妨点一下
好看

分享
给朋友!












Java团长




专注于Java干货分享




扫描上方二维码获取更多Java干货










推荐阅读
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • Java实战之电影在线观看系统的实现
    本文介绍了Java实战之电影在线观看系统的实现过程。首先对项目进行了简述,然后展示了系统的效果图。接着介绍了系统的核心代码,包括后台用户管理控制器、电影管理控制器和前台电影控制器。最后对项目的环境配置和使用的技术进行了说明,包括JSP、Spring、SpringMVC、MyBatis、html、css、JavaScript、JQuery、Ajax、layui和maven等。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 在springmvc框架中,前台ajax调用方法,对图片批量下载,如何弹出提示保存位置选框?Controller方法 ... [详细]
  • Java SE从入门到放弃(三)的逻辑运算符详解
    本文详细介绍了Java SE中的逻辑运算符,包括逻辑运算符的操作和运算结果,以及与运算符的不同之处。通过代码演示,展示了逻辑运算符的使用方法和注意事项。文章以Java SE从入门到放弃(三)为背景,对逻辑运算符进行了深入的解析。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了在Mac上搭建php环境后无法使用localhost连接mysql的问题,并通过将localhost替换为127.0.0.1或本机IP解决了该问题。文章解释了localhost和127.0.0.1的区别,指出了使用socket方式连接导致连接失败的原因。此外,还提供了相关链接供读者深入了解。 ... [详细]
  • GreenDAO快速入门
    前言之前在自己做项目的时候,用到了GreenDAO数据库,其实对于数据库辅助工具库从OrmLite,到litePal再到GreenDAO,总是在不停的切换,但是没有真正去了解他们的 ... [详细]
author-avatar
米粒多可爱几_642
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有