天天看点

Spring面试——事务

Spring面试——事务

      • 前言
      • Spring事务的原理
      • Spring事务回滚
      • Spring事务什么情况下失效
      • Spring的事务隔离和数据库隔离是不是一个概念
      • Sping事务中一个带有事务的方法调用另一个带事务的方法,默认开启几个事务
      • 怎么保证Spring事务内的连接唯一性
      • 结束语

前言

之前面试被问过Spring事务的相关问题,结果因为总结的少,回答的支支吾吾,所以导致被发储备到人家公司的人才仓库了。┭┮﹏┭┮,

所以接下来我就好好总结一下!

Spring事务的原理

Spring事务的本质就是数据库对事物的支持。没有数据库对事物的支持,Spring是无法提供事务功能的。

那我平常用的最多的mysql来说,一般我们都是用JDBC来操作事务的,步骤如下:

  1. 获取连接 Connection con = DriverManager.getConnection();
  2. 开启事务 con.setAutoCommit(true(自动提交)/false(不自动提交));
  3. 执行我们的业务(CRUD) ;
  4. 处理成功,提交事务:con.commit() ;处理失败,回滚事务:con.rollback();
  5. finally关闭连接 conn.close()。

如果没有Spring对事务的支持,那么我们每次执行事务都需要手动处理以上的五个步骤,但是其实对于步骤2和步骤4,我们是不是可以把他从业务代码里面分离出来呢?这时候我们想到了熟悉的AOP,Spring事务就是基于AOP帮我们处理了这两个步骤。

Spring事务回滚

什么时候会发生事务回滚呢?当所代理的方法有指定的异常抛出,事务才会自动进行回滚。

所以我们在加了事务的方法里面,千万不要默默地吞掉异常!如下的做法是错误的。

@Service
@Slfj4
public class UserService{
    @Transactional
    public void addUser(User user) {
        try {
            log.inf("新增一个用户");
            //do something
        } catch {
          //do something
        }
    }
}
           

一般情况我们都是自定义一个业务异常抛出,最好继承RuntimeException类。

为什么要最好继承自RuntimeException?因为在默认的配置下,事务只会对Error与RuntimeException及其子类这些异常做回滚。一半的Exception这些Checked异常不会回滚,如果想要一般的Exception也会滚,可以做以下的配置:

我们顺带说一下java异常的分类吧,这个我也被问过多次!

拿几个比较常见的异常类来说吧:

  • OutOfMemoryError:这个属于Error错误,是系统无法控制的,Error一般指的是系统方面的异常,比如 蓝屏,内存溢出,jvm运行环境出现了问题,,它隶属于unchecked异常,因为无法被预知。。
  • IOException:这个属于check异常,除了RuntimeException及其子类,Exception所有的子类都属于check异常,check异常相当于编译器提前预知到程序中将会出现的异常,一般这种异常需要我们强制手动try,catch或者抛出。
  • NullPointerException:这个属于unchecked异常,因为他继承自RuntimeException,这类异常一般是无可预知的,是程序里面不应该出现的异常,很多都是因为程序bug而抛出的异常。

他们都继承于同一个基类:Throwable,用来定义所有可以作为异常被抛出来的类。

Spring事务什么情况下失效

我使用的过程中好像没遇到过这种情况,但是被问得次数还是蛮多的。

Spring的事务的原理是AOP,对代理的类进行切面增强,所以失效的原因就是这个代理不起作用了,一般常见的情况如下。

  • 发生自调用:说得简单点就是类里面的方法调用它同一个类的方法,如下:
    @Service
    public class OrderServiceImpl implements OrderService {
        @Transactional
        public void update(Order order) {
            updateOrder(order); 
       }
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void updateOrder(Order order) {
            // update order;
        }
    }
               
    那么这种情况我们怎么处理呢,如下,我么可以取容器里面拿这个类的代理类来处理。
    @Service
    	public class OrderServiceImpl implements OrderService {
    	    @Transactional
    	    public void update(Order order) {
    	    	((OrderServiceImpl ) AopContext.currentProxy()).updateOrder(order);
    	   }
    	    @Transactional(propagation = Propagation.REQUIRES_NEW)
    	    public void updateOrder(Order order) {
    	        // update order;
    	    }
    	}
               
  • 方法修饰符不是public:@Transactional 注解只能应用到 public 方法上。如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会以事务的方式运行。
  • 发生了错误的异常:上面已经提到过,Spring事务默认只对RuntimeException异常以及其子类起作用,如果我们想要其他的check异常也能回滚,那么我们需要在@Transactional 注解上加rollbackFor = Exception.class。
  • 异常被程序吞了:异常被程序吞了,导致@Transactional 失效,所以一般我们吞完异常之后可以抛出一个业务异常。
  • 数据库不支持事务:Spring事务是基于数据库事务的,如果数据库都不支持事务,那么Spring事务配置了也白费。
  • 数据源没有配置事务管理器:emmm,这种错误一般只有在项目刚开始的时候可能会发生吧(忘记配置@EnableTransactionManagement)
  • 数据库隔离等级设置有误:隔离等级设置成不支持事务。

Spring的事务隔离和数据库隔离是不是一个概念

数据可以一般有以下四个隔离等级:

  • Read Uncommitted:未提交读,会出现脏读,不可重复读,幻读;
  • Read Committed:提交读,会出现不可重复读,幻读;
  • Repeatable Read:可重复读,会出现幻读;
  • Serializable:可串行化。

MySQL默认的事务隔离级别是Repeatable Read,Oracle事务默认隔离级别是Read Committed。

spring的事务隔离等级配置优先级要高于数据库的事务隔离等级设置,但是如果spring设置的事务隔离等级数据库不支持,那么以数据库的为准。

Sping事务中一个带有事务的方法调用另一个带事务的方法,默认开启几个事务

这个问题,一般是考察我们对事务传播行为的理解。

spring事务默认的传播行为是PROPAGATION_REQUIRED。就是如果当前有事务,加入当前事务;如果没有事务,新建一个事务执行。也就是默认情况下只有一个事务。

事物的传播性质一共有七种:

  • PROPAGATION_REQUIRED:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  • PROPAGATION_MANDATORY:支持当前事务,如果当前没有事务,就抛出异常。
  • PROPAGATION_REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  • PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  • PROPAGATION_NESTED:如果没有,就新建一个事务;如果有,就在当前事务中嵌套其他事务。

怎么保证Spring事务内的连接唯一性

数据库连接在事务开始的时候就存在了ThreadLocal里面了,后面执行过程中,都是从ThreadLocal中取得。所以能保证唯一,以为每一个线程的数据库连接都是隔离的。

我们可以在下面的源码看出来:

Spring面试——事务
Spring面试——事务
Spring面试——事务
Spring面试——事务

结束语

好了,总结完毕,共勉!