一、Spring事务是什么?
1)、一个数据库事务是一个被视为一个工作单元的逻辑上的一组操作,这些操作要么全部执行,要么全部不执行。
需要注意的是,并不是所有的数据库(引擎)都支持事务,比如说MySQL的MyISAM存储引擎
2)、事务的四大特性(ACID)
-
原子性(Atomicity): 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。
-
一致性(Consistency): 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。
-
隔离性(Isolation): 数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括未提交读(Read Uncommitted)、提交读(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
-
持久性(Durability): 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
二、Spring事务的写法(编程式事务、声明式事务)
1)、编程式事务是什么?
通过 PlatformTransactionManager 或者 TransactionTemplate 可以实现编程式事务。
/**
* 编程式事务
*/
@Autowired
PlatformTransactionManager transactionManager;
public void transactionManagerTest(){
// 定义一个默认的事务属性
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
// 事务隔离级别
definition.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);
// 事务过时时间
definition.setTimeout(3000);
// 事务只读
definition.setReadOnly(true);
// 获取 TransactionStatus
TransactionStatus transaction = transactionManager.getTransaction(definition);
try {
// 业务逻辑代码
// 报错代码(校验是否会回滚)
int i = 1 / 0;
// 事务提交
transactionManager.commit(transaction);
} catch (Exception e){
// 事务回滚
transactionManager.rollback(transaction);
}
}
/**
* 编程式事务
如果不需要获取事务执行的结果,则直接使用 TransactionCallbackWithoutResult 类即可,如果要获取事务执行结果,则使用 TransactionCallback 即可
*/
@Autowired
TransactionTemplate transaction;
public void transactionManagerTest(){
transaction.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// 业务逻辑代码
// 报错代码(校验是否会回滚)
int i = 1 / 0;
// 此事务会自动提交
} catch (Exception e){
// 事务回滚
transactionStatus.setRollbackOnly();
}
}
});
}
编程式事务由于代码入侵太严重了,因为在实际开发中使用的很少,我们在项目中更多的是使用声明式事务。
2)、声明式事务
当@Transactional
注解加在类上面的时候,表示该类的所有方法都有事务,该注解加在方法上面的时候,表示该方法有事务。
@Transactional(noRollbackFor = ArithmeticException.class)
public void transactionManagerTest(){
// 业务逻辑代码
// 报错代码(校验是否会回滚)
int i = 1 / 0;
}
三、事务的三个接口
这三个核心类是 Spring 处理事务的核心类。
PlatformTransactionManager
TransactionDefinition
TransactionStatus
1)、PlatformTransactionManager(主要的三个方法)
1.getTransaction()
getTransaction() 是根据传入的 TransactionDefinition 获取一个事务对象,TransactionDefinition 中定义了一些事务的基本规则,例如传播性、隔离级别等。
2.commit()
commit() 方法用来提交事务。
3.rollback()
rollback() 方法用来回滚事务。
2)、TransactionDefinition(五个方法)
-
getIsolationLevel(),获取事务的隔离级别
-
getName(),获取事务的名称
-
getPropagationBehavior(),获取事务的传播性
-
getTimeout(),获取事务的超时时间
-
isReadOnly(),获取事务是否是只读事务
3)、TransactionStatus(可以理解为事务本身)
-
isNewTransaction() 方法获取当前事务是否是一个新事务。
-
hasSavepoint() 方法判断是否存在 savePoint()。
-
setRollbackOnly() 方法设置事务必须回滚。
-
isRollbackOnly() 方法获取事务只能回滚。
-
flush() 方法将底层会话中的修改刷新到数据库,一般用于 Hibernate/JPA 的会话,对如 JDBC 类型的事务无任何影响。
-
isCompleted() 方法用来获取是一个事务是否结束。
四、事务隔离性
1)、事务的隔离级别一共分为四种,分别如下:
-
序列化(SERIALIZABLE)
-
可重复读(REPEATABLE READ)
-
提交读(READ COMMITTED)
-
未提交读(READ UNCOMMITTED)
1.1)、READ UNCOMMITTED:最低的隔离级别,会导致数据出现、脏读
1.2)、READ COMMITTED:避免脏读、允许不可重复读和幻读
1.3)、REPEATABLE READ:避免脏读、不可重复读、允许幻读
1.4)、SERIALIZABLE:最高的隔离级别、串行读、事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率低。
- 脏读:一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。
- 不可重复读:一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。
- 幻读:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。
五、事务的七大传播性
传播性 | 描述 |
---|---|
REQUIRED | 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务 |
SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行 |
MANDATORY | 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常 |
REQUIRES_NEW | 创建一个新的事务,如果当前存在事务,则把当前事务挂起 |
NOT_SUPPORTED | 以非事务方式运行,如果当前存在事务,则把当前事务挂起 |
NEVER | 以非事务方式运行,如果当前存在事务,则抛出异常 |
NESTED | 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 TransactionDefinition.PROPAGATION_REQUIRED |
六、事务失效
1)、方法必须使用、public修饰 否则会失效。
/**
* 这个情况下,事务是会进行回滚的、事务生效
*/
@Transactional
public void test(){
// 业务代码
// 错误代码
int i = 1 / 0;
}
/**
* 这个情况下,编码是会报错的,事务是失效状态
*
* private、protected 使用修饰,事务都是失效的、或者 什么都不加 也是失效的
*/
@Transactional
private void test1(){
// 业务代码
// 错误代码
int i = 1 / 0;
}
为什么一定要使用、public进行修饰,我们可以看下源码如下:
2)、使用、find修饰方法、事务失效
/**
* 这个情况下,事务会失效的、
* spring事务底层、是通过 Aop,动态代理或cglib,生成代理类的,在代理类中实现事务的功能
* 如果我们用 find 修改的方法,在代理类中是不会重写方法,无法实现事务功能
* 如果用 static 修饰、也是无法通过动态代理、无法实现事务功能的
*/
@Transactional
public final void test(){
// 业务代码
// 错误代码
int i = 1 / 0;
}
3)、方法内部调用、事务失效
@Service
public class ClickService {
/**
* 将本身的service类注入进来
*/
@Autowired
ClickService clickService;
public void test(){
// 业务代码
// 错误代码
// int i = 1 / 0;
// 同方法调用事务、事务失效 ,这样调用实际是,this.add(),相当于new了一个对象,调用对象里面的方法
add();
// 解决方法
clickService.add();
}
@Transactional
public void add(){
// 业务代码
}
}
4)、未被spring管理、事务失效
//@Service
public class ClickService {
/**
* 这种情况下、该service为被spring管理、因为我们,@Service注释了
*/
@Transactional
public void add(){
// 业务代码
}
}
5)、多线程调用、事务失效
/**
* spring事务源码,是属于数据库的事务,他是通过 ThreadLocal去保存的,
* 如果用多线程调用数据,会产生多份 ThreadLocal、从而产生多个数据库链接,所以事务失效
*/
@Transactional
public void add(){
// 业务代码
new Thread(() -> {
// 业务代码
}).start();
}
6)、在spirngBoot没有出来之前,是要通过 xml文件去配置,aop用切点把事务切进去,开启事务
如果项目使用的是,SpringBoot框架是会默认配置事务的。
7)、事务的错误传播特性、事务失效
@Service
public class ClickService {
/**
* Propagation.NEVER 是不支持事务的,如果有事务,会报错的,不会进行回滚
*/
@Transactional(propagation = Propagation.NEVER)
public void add(){
// 业务代码
}
}
8)使用 try 事务失效
@Service
public class ClickService {
/**
* 如果我们使用 在事务中,使用 try 发生异常时,自己把异常吞了,事务是不会进行回滚的、默认为没有异常
*
* 如果我们抛了异常,throw new Exception(e) 属于非运行时异常、它是不会回滚的
* spring事务默认情况下,RuntimeException(); 或 Error(); 这两个异常是,会进行回滚的
*/
@Transactional()
public void add(){
try {
// 业务代码
} catch (Exception e){
throw new RuntimeException();
}
}
}
9)、自定义回滚异常,事务失效
@Service
public class ClickService {
/**
* 自定义事务异常,如果抛出的异常,和自定义异常不一致,这个事务也是不会进行回滚的。
*
* 所以、建议一般情况下,将参数设置为:Exception或Throwable
*/
@Transactional(rollbackFor = BusinessException.class)
public void add(){
// 业务代码
}
}