面试-Spring 事务

原文地址 mp.weixin.qq.com

前言

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 打脸)

发生了运行时 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 会自动生成代理对象

显然地,我们拿到的是代理 (Proxy) 对象,调用addEmployee2Controller()方法,而addEmployee2Controller()方法的逻辑是target.addEmployee(),调用回原始对象 (target) 的addEmployee()。所以这次的调用压根就没有事务存在,更谈不上说 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();
    }
}


测试结果:

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

因为我们用的是代理对象 (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 的时候,是怎么编写 Session 工具类

Mybatis 工具类部分代码截图

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

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

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

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

全都是 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 的方法,则执行销毁操作

Application 中 Bean 的声明周期

其中也有关于 BPP 图片:

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 技术内幕》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值