《我是面试官》之Spring事务

我叫老凡,是一名又老又平凡的程序员,今天作为面试官,我的目标是:不管坐在我对面的是谁,我都要把他问懵!

老凡 你好啊,欢迎你来参加我们的面试,我们直接开始吧,了解Spring事务吗?

面试者:面试官你好,我知道Spring事务,Spring的事务是指一组操作被视为一个单独的逻辑单元,并在事务管理下执行。如果其中任何一步操作失败,整个事务将回滚,所有已完成的操作都将被撤消,以确保数据的一致性和完整性。

老凡 不错,常用的事务管理方式有哪些?

面试者:有编程式事务和声明式事务。

老凡 分别解释下这两种事务方式

面试者:编程式事务通过编写代码来管理gi事务的边界和操作,需要手动地创建事务、开始、提交或回滚事务,以及在需要的时候进行异常处理等。声明式事务是通过配置文件或在代码中使用 @Transactional 注解实现的,Spring会自动管理事务的创建、提交和回滚。

老凡 声明式事务与编程式事务相比,有哪些优点?

面试者:一方面它更加方便和简洁,不需要手动管理事务,只需专注于业务逻辑的实现即可,同时降低了代码的耦合性。另一方面,它也很灵活,声明式事务可以在不修改代码的情况下,更改事务的隔离级别、传播行为、超时时间等属性。此外呢,声明式事务可以被多个方法或类共享。这样可以避免在每个方法中都编写相同的事务代码,提高了代码的可重用性。

老凡 那声明式事务有什么局限性吗?

面试者:我认为声明式事务的缺点主要有3点吧:

1. Spring 声明式事务只能控制方法级别的事务,而不能控制跨多个方法或者多个请求的事务,这是由于 Spring AOP 的限制所致。

2. Spring 声明式事务只能控制单个数据源上的事务,如果需要控制多个数据源上的事务,则需要使用分布式事务。

3. Spring 声明式事务只会在受检异常(checked exception)抛出时回滚事务,而对于未受检异常(unchecked exception)则不会回滚事务。

老凡 Spring事务的隔离级别有哪些 ?

面试者:隔离级别有5种:

  1. 读未提交(READ_UNCOMMITTED):一个事务可以读取另一个事务未提交的数据,可能导致脏读、不可重复读和幻读等问题。

  2. 读已提交(READ_COMMITTED):一个事务只能读取另一个事务已提交的数据,可以避免脏读问题,但可能会导致不可重复读和幻读等问题。

  3. 可重复读(REPEATABLE_READ):一个事务在执行期间多次读取同一数据时,保证每次读取的数据都是一样的,可以避免脏读和不可重复读问题,但可能会导致幻读问题。

  4. 可序列化(SERIALIZABLE):这是最高的隔离级别,就是在一个事务执行期间,其他事务无法对其操作的数据进行修改、插入和删除,可以避免脏读、不可重复读和幻读问题。

  5. 默认(DEFAULT):这个就是用底层数据库设置的隔离级别。

老凡 顺便问一下,什么是幻读?

面试者:幻读指的是在同一时间内,同一查询语句多次执行时,由于其他事务对数据进行了插入、更新或删除操作,导致前后查询的结果不一致的情况。

老凡 那幻读和脏读的区别是什么?

面试者:脏读是指一个事务读取了另一个事务未提交的数据,而幻读则是指一个事务读取了另一个事务已提交的数据,但由于并发操作的影响,结果发生了变化。

老凡 那幻读和不可重复读的区别又是什么?

面试者:它们都涉及到多个事务同时访问同一数据的情况,但两者的区别在于数据的修改方式不同。

不可重复读是指一个事务在读取某些数据后,另一个事务修改了这些数据并进行了提交,导致第一个事务再次读取同一数据时,发现与之前读取的数据不一致。例如,一个事务读取了一条记录的值,而另一个事务修改了该记录的值,并提交了事务,此时第一个事务再次读取同一记录,发现值已经改变,就发生了不可重复读。

而幻读则是指一个事务在读取某些数据时,另一个事务插入了符合条件的新数据并进行了提交,导致第一个事务再次读取同一数据时,发现多出了一些未曾出现过的新记录,就发生了幻读。

老凡 很好,Spring 事务的传播机制有哪些?

面试者:Spring定义了7种不同的事务传播机制,分别是:

  1. REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

  2. SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则不使用事务。

  3. MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

  4. REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则挂起该事务。

  5. NOT_SUPPORTED:如果当前存在事务,则将事务挂起,不使用事务。

  6. NEVER:如果当前存在事务,则抛出异常。

  7. NESTED:如果当前存在事务,则在该事务内创建一个保存点,以便回滚到该保存点;如果当前没有事务,则创建一个新的事务。

老凡 你最常用的是哪些?

面试者:最常用的是REQUIRED 和 REQUIRES_NEW。

老凡 这里有一段代码,请说出test_Required() 和 test_Required_New() 的执行结果,并说明原因

@Service
public class UserServiceImpl implements UserService {

   @Autowired
   private UserMapper mapper;

   @Override
   @Transactional(propagation = Propagation.REQUIRED)
   public void add(User user) {
       mapper.insert(user);
  }

   @Override
   @Transactional(propagation = Propagation.REQUIRED)
   public void add_Exception(User user) {
       mapper.insert(user);
       throw new RuntimeException();
  }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void add_New(User user) {

        mapper.insert(user);

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void add_New_Exception(User user) {

        mapper.insert(user);
        throw new RuntimeException();

    } 
}

@Service
public class TestUserServiceImpl implements TestUserService {

    @Autowired
    private UserService userService;

   
    @Override
    @Transactional
    public void test_Required() {

        User zhangSan = new User("张三");
        User liSi = new User("李四");
        userService.add(zhangSan);
        try {
            userService.add_Exception(liSi);
        } catch (Exception e) {
            log.error("Error!!");
        }

    }

    @Override
    @Transactional
    public void test_Required_New() {

        User zhangSan =  new User("张三");
        User liSi = new User("李四");
        User wangWu = new User("王五");
        userService.add(zhangSan);
        userService.add_New(liSi);
        try {
            userService.add_New_Exception(wangWu);
        } catch (Exception e) {
            log.error("Error!!");
        }

    } 
} 

面试者:嗯,我看看。。。执行结果是这样的:

1. test_Required方法:张三和李四都没有入库成功,因为外层方法test_Required()开启事务,所有内部方法均加入外层方法的事务中,即所有方法都处于同一个事务中,此时“李四”的插入方法发生异常,那么这个方法所在的事务就会被 Spring 设置为 rollback 状态。当外部方法执行完要进行 commit 操作时却发现当前事务已经处于 rollback 状态了,回滚所有操作。 

2. test_Required_New方法:张三、李四插入成功,王五没有插入成功。这个同样是外层方法开启事务,插入“张三”的方法加入外层方法的事务中,而插入“李四”和插入“王五”的方法则是开启一个独立的新事物。插入“李四”的方法没有异常,可以正常提交,李四插入成功。然后插入“王五”的方法因为发生异常,自己回滚了。所以王五入库不成功。但是插入"王五"方法抛出的异常在外层方法中被try-catch处理了,所以张三可以插入成功。

老凡:回答的很好,Spring 事务的实现原理是什么?

面试者:Spring事务的实现原理基于AOP(面向切面编程)和代理模式。

老凡:能具体说下吗?

面试者:可以的,首先我们通过@Transactional注解来指定事务的属性和传播机制,然后Spring会在需要开启事务的方法或类上,使用AOP的方式,创建一个代理对象,并将代理对象的方法拦截下来。接着在代理对象方法执行前,创建一个事务,并将该事务绑定到当前线程上下文中。当代理对象方法执行完毕后,根据方法执行结果选择提交或回滚事务。最后,在方法执行完毕后,将事务从当前线程上下文中移除。

老凡:如何处理 Spring 事务中的异常?

面试者:有以下几种方式:

  1. 可以手动捕获异常并回滚事务:通过try-catch块捕获异常,并在catch块中调用TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()方法将事务设置为回滚状态。这样,当方法执行完成后,事务就会回滚。

  2. 抛出异常并由Spring事务管理器回滚事务:在方法中抛出RuntimeException或其子类异常,由Spring事务管理器自动回滚事务。但注意的是,抛出的异常需要是非检查异常(Unchecked Exception)。

  3. @Transactional注解中使用rollbackFornoRollbackFor属性来控制异常回滚:rollbackFor属性可以指定需要回滚的异常类型,而noRollbackFor属性可以指定不需要回滚的异常类型。

  4. @Transactional注解中使用rollbackForClassNamenoRollbackForClassName属性来指定异常类名:rollbackForClassName属性可以指定需要回滚的异常类名,而noRollbackForClassName属性可以指定不需要回滚的异常类名。

老凡:说下Spring事务失效的常见原因

面试者:我所了解的原因大概有以下几种:

1. 非public使用@Transactional注解,那么该注解就会不起作用;

2. 异常被try-catch捕获:如果在事务方法中捕获了异常并在try-catch块中处理,但没有将异常继续向上抛出,事务就不会被回滚;

3. 事务方法中抛出的是受检查异常(checked exception):例如,如果在事务方法中抛出了一个IOException,那么事务就会失效;

4. 类不受 Spring管理:类上如果没有加@Service或@Component等注解,事务也会失效,因为 Spring事务是由 AOP机制实现的,本质就是动态代理,事务需要的 Advisor等资源是在 Spring创建代理类时创建的,如果类不受 Spring容器管理,那么事务需要的 Advisor资源就无法创建,事务自然就失效了。

5. 非事务方法内部调用事务方法:例如下面的代码,addUser方法的事务就不会被回滚,原因是事务是通过Spring AOP代理来实现的,而 addUser()内部事务方法其实是this对象调用的,而不是通过AOP代理来调用的,因此事务失效。


  @Override
  public void add(User user){
    // 调用内部方法
    this.addUser(user);
  }

  @Transactional
  public void addUser(User user){
    userRepository.save(user);
    throw new RuntimeException();
  }

6. 数据库不支持事务:Spring 事务是业务层的事务,其底层还是依赖于数据库本身的事务支持。如果需要使用事务,一定要确保使用的数据库支持事务。

7. 代码中抛出的异常与rollbackFor属性配置的类型不相关,也会导致事务失效,比如代码中抛出的异常是 Exception,而rollbackFor指定的类型是Error。

老凡:不错不错,还有最后一个问题,你能熬夜吗?

面试者:???熬夜可以,但熬夜工作不行。

老凡:啊。。。。。。

我叫老凡,没想到啊没想到,今天是我懵了…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值