Spring面试——事务
-
-
- 前言
- Spring事务的原理
- Spring事务回滚
- Spring事务什么情况下失效
- Spring的事务隔离和数据库隔离是不是一个概念
- Sping事务中一个带有事务的方法调用另一个带事务的方法,默认开启几个事务
- 怎么保证Spring事务内的连接唯一性
- 结束语
-
前言
之前面试被问过Spring事务的相关问题,结果因为总结的少,回答的支支吾吾,所以导致被发储备到人家公司的人才仓库了。┭┮﹏┭┮,
所以接下来我就好好总结一下!
Spring事务的原理
Spring事务的本质就是数据库对事物的支持。没有数据库对事物的支持,Spring是无法提供事务功能的。
那我平常用的最多的mysql来说,一般我们都是用JDBC来操作事务的,步骤如下:
- 获取连接 Connection con = DriverManager.getConnection();
- 开启事务 con.setAutoCommit(true(自动提交)/false(不自动提交));
- 执行我们的业务(CRUD) ;
- 处理成功,提交事务:con.commit() ;处理失败,回滚事务:con.rollback();
- 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中取得。所以能保证唯一,以为每一个线程的数据库连接都是隔离的。
我们可以在下面的源码看出来:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNCM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPn1EMVRlTzUlaNBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL1kTM5AzM0ETMzETMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
结束语
好了,总结完毕,共勉!