一、事务介绍
1、数据库事务:是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。这些操作要么全部执行成功提交(commit),要么全部中止失败(abort,rollback)。就是在数据库执行多条SQL语句,要么都执行成功,要么都执行失败。
2、数据库事务必须同时满足4个特性:原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)和持久性(Durabiliy),简称为ACID四大特性。
- 原子性:表示组成一个事务的多个数据库操作是一个不可分割的原子单元,只有所有的操作执行成功,整个事务才提交。
事务中的任何一个数据库操作失败,已经执行的任何操作都必须撤销,让数据库返回到初始状态。 - 一致性:事务操作成功后,数据接所处的状态和它的业务规则是一致的,数据不会被破坏。
- 隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对对方产生干扰。准确地说,并非要求做到完全无干扰。数据库规定了多种事务隔离级别,不同的隔离级别对应不同的干扰程度,隔离级别越高,数据一致性越好,但并发性越弱。
- 持久性:一旦事务提交成功之后,事务中所有的数据操作都必须被持久化到数据库中。即使在提交事务之后,数据库马上崩溃,在数据库重启时,也必须保证能够通过某种机制恢复数据。
在这些事务特性中,数据"一致性"是最终目标,其他特性都是为达到这个目标而采取的措施、要求或手段。
3、数据库管理系统一般采用重执行日志来保证原子性、一致性和持久性。
数据库管理系统采用数据库锁机制保证事务的隔离性。当多个事务试图对相同的数据进行操作时,只有持有锁的事务才能操作数据,直到前一个事务完成后,后面的事务才有机会对数据进行操作。
二、事务隔离性(四大隔离级别)
1.当多个线程都开启事务操作数据库中的数据时,数据库系统要能进行隔离操作,以保证各个线程获取数据的准确性。如果不考虑事务的隔离性,会有以下几种问题:
- 脏读(dirty read)
- 不可重复读(unrepeatable read)
- 幻象读(phantom read)
2.四大隔离级别
- Read Uncommitted 读取未提交
- Read Committed 读取已提交
- Repeated Read 可重复读
- Serializable 可序列化
三、Spring事务的7种传播特性
在业务开发中会有服务接口方法嵌套调用的情况就会出现事务嵌套。Spring通过事务传播行为控制当前的事务如何传播到被嵌套调用的目标服务接口方法中,就是多个事务方法相互调用时,事务如何在这些方法间传播。
Spring规定了7种类型的事务传播行为,规定了事务方法和事务方法发生嵌套调用时事务如何进行传播。
- PROPAGATION_REQUIRED 1.支持当前事务,如果当前执行线程没有事务,则新建事务2.如果当前执行线程存在事务,则加入当前事务,合并成一个事务 3.这个是spring的默认事务传播行为
- REQUIRES_NEW 1.新建事务,如果当前存在事务,则把当前事务挂起2.这个方法会独立提交事务,不受调用者的事务影响,父级异常,它也是正常提交。 当前事务和新开启的事务没有关联。
- PROPAGATION_NOT_SUPPORTED 以非事务方式执行操作(不支持事务)。如果当前存在事务,就把当前事务挂起。
- PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_NEVER 以非事务方式执行,如果当前存在事务,就抛出异常。
- PROPAGATION_NESTED 如果当前存在事务,就在嵌套事务内执行。如果当前没有事务,就执行与PROPAGATION_REQUIRED类型的操作。当前事务和内嵌的事务有关联,如果外部事务回滚,内嵌事务也会回滚;如果内嵌事务回滚,外部的事务不回滚。
- PROPAGATION_MANDATORY 使用当前事务,如果当前没有事务,就抛出异常。
四、Spring事务的几种实现方式
1.编程式事务。
2.声明式事务(通过AOP增强类的功能)。
- 使用XML配置声明式事务
- 使用注解配置声明式事务
3.@EnableTransactionManagement注解
该注解用于启用Spring的注解驱动的事务管理功能
SpringBoot自动了配置了事务,开启了事务支持。在debug级别日志中,可以看到如下日志信息:
Creating shared instance of singleton bean ‘org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration$EnableTransactionManagementConfiguration’ 。
EnableTransactionManagement注解包含以下属性:
- proxyTargetClass 基于实现类来代理业务类(CGLIB)为(true) ,基于Java接口的代理(false) 默认值为 false
- mode 指示应如何应用事务通知。 默认值 AdviceMode.PROXY
- order aop事务拦截顺序,如果业务类除了事务切面外,还需要织如其他的切面,则通过该属性可以控制事务切面在目标连接点的织入顺序。
可以在启动类添加该注解覆盖掉系统的默认配置。
4.@Transactional注解
@Transactional 注解可以被应用于接口方法和类定义和类的public方法上。属性如下:
- transactionManager 用于指定事务管理器
- propagation 事务传播类型,默认值为Propagation.REQUIRED.
- isolation 事务隔离级别, 默认值 Isolation.DEFAULT.
- timeout 超时时间 int类型 单位为秒
- readOnly 事务读写性,布尔型。
- rollbackFor 用于指定回滚的异常类型,类型为Class<? extends Throwable>[],默认值为{},多个异常用逗号隔开。
- rollbackForClassName 用于指定回滚的异常类名,类型为String[],默认值为{}
- noRollbackFor 用于指定不回滚的异常类型,类型为Class<? extends Throwable>[] 默认值为{}
- noRollbackForClassName 用于指定不回滚的异常类名,类型为String[],默认值为{}
在默认情况下,只有当程序抛出运行时异常和unChecked异常时,Spring事务才会自动回滚事务。也就是只有当程序抛出一个RuntimeException或其子类实例,以及Error对象时,Spring才会自动回滚事务。如果事务方法中抛出Checked异常,则事务不会自动回滚。如果想让事务遇到特定的Checked异常时自动回滚,则需要设置rollbackFor属性。
五、Spring事务失效的几种原因
1.方法不加@Transactional注解
如果方法不加@Transactional注解就是没加事务,这样就变成了各条SQL单独的事务,底层JDBC的数据库连接是自动提交的,就算抛出了运行时异常也不会回滚。
@Override
public void updateEmp(Emp emp) throws Exception{
empMapper.updateEmp(emp);
throw new RuntimeException("运行时异常!");
}
调用这个updateEmp方法抛出了异常,但是数据库还是会执行更新语句。
2.自身调用问题(同一个类中)
(1).不带事务的方法调用带事务的方法,则事务不生效
/**
* 员工业务接口实现
*
* @author David Lin
* @version: 1.0
* @date 2019-11-10 22:32
*/
@Service("empService")
public class EmpServiceImpl implements EmpService {
@Override
@Transactional
public void updateEmp(Emp emp) throws Exception{
empMapper.updateEmp(emp);
throw new RuntimeException("运行时异常!");
}
@Override
public void update(Emp emp) throws Exception {
this.updateEmp(emp);
}
调用update方法,发现数据库还是更新了,updateEmp方法的事务没有效果。
解决方法:将自己的代理对象注入,没有事务的方法调用注入对象的方法.
/**
* 员工业务接口实现
*
* @author David Lin
* @version: 1.0
* @date 2019-11-10 22:32
*/
@Service("empService")
public class EmpServiceImpl implements EmpService {
/**
* 注入自己的代理对象
*/
@Resource
private EmpService empService;
@Override
@Transactional
public void updateEmp(Emp emp) throws Exception{
empMapper.updateEmp(emp);
throw new RuntimeException("运行时异常!");
}
@Override
public void update(Emp emp) throws Exception {
//调用的是代理对象增强后的方法
empService.updateEmp(emp);
}
这样updateEmp方法的事务就会有效果,事务就回滚了。还有一种方法也可以让updateEmp方法的事务生效 如下:
@Override
public void update(Emp emp) throws Exception {
((EmpService) (AopContext.currentProxy())).updateEmp(emp);
}
或者将updateEmp方法写在其他类中然后注入到当前类中调用。
(2)在一个事务中开启另外一个事务
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateEmp(Emp emp) {
empMapper.updateEmp(emp);
}
@Override
@Transactional
public void update(Emp emp) throws Exception {
this.updateEmp(emp);
throw new RuntimeException("运行时异常!");
}
updateEmp方法注解属性加了REQUIRES_NEW,代表新开启一个事务。但是这里新开启的事务没有效果,因为updateEmp的事务和update方法的事务是同一个(spring事务传播特性)。解决方法和上面的一样,注入自己的代理对象然后调用代理对象的方法,这样updateEmp方法新开启的事务就会有效果(执行更新语句并且提交事务)。
3.异常没有抛出
@Override
@Transactional
public void updateEmp(Emp emp) {
try {
empMapper.updateEmp(emp);
int num = 20 / 0;
} catch (Exception e) {
e.printStackTrace();
}
}
由于异常没有抛出,被‘吃’了导致事务没有回滚。
解决方法如下:
@Override
@Transactional
public void updateEmp(Emp emp) {
try {
empMapper.updateEmp(emp);
int num = 20 / 0;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("抛出了运行时异常");
}
}
4.抛出的异常类型错误
@Override
@Transactional
public void updateEmp(Emp emp) throws Exception {
try {
empMapper.updateEmp(emp);
int num = 20 / 0;
} catch (Exception e) {
e.printStackTrace();
throw new Exception("抛出了异常");
}
}
由于在默认情况下,只有当程序抛出一个RuntimeException或其子类实例,以及Error对象时,Spring才会自动回滚事务。所以这里的事务不会回滚。解决方法就是在@Transactional注解上添加rollbackFor属性 如下:
@Override
@Transactional(rollbackFor = Exception.class)
public void updateEmp(Emp emp) throws Exception {
try {
empMapper.updateEmp(emp);
int num = 20 / 0;
} catch (Exception e) {
e.printStackTrace();
throw new Exception("抛出了异常");
}
}
参考文章: