文章目录
@Transactional
注解开启事务,其中注解的各种属性详解
1、事务传播行为
事务传播行为是为了解决业务层方法之间互相调用的事务问题
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
举个例子:我们在 A 类的aMethod()
方法中调用了 B 类的 bMethod()
方法。这个时候就涉及到业务层方法之间互相调用的事务问题。如果我们的 bMethod()
如果发生异常需要回滚,如何配置事务传播行为才能让 aMethod()
也跟着回滚呢?这个时候就需要事务传播行为的知识了,如果你不知道的话一定要好好看一下
如下 StudentService中的 changeName 方法有运行时异常
java
@Service
public class TopService {
@Autowired
private StudentService studentService;
//测试 事务的传播行为
@Transactional
public void topService(){
studentService.changeAge();
studentService.changeName();
}
}
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
//事务的传播行为
@Transactional
public void changeAge(){
studentDao.updateAgeById(998, 1);
}
//事务的传播行为,
@Transactional
public void changeName(){
studentDao.updateNameById("dasdas", 1);
int i = 1/0;
}
}
在TransactionDefinition
定义中包括了如下几个表示传播行为的常量:
java
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
......
}
不过,为了方便使用,Spring 相应地定义了一个枚举类:Propagation
java
package org.springframework.transaction.annotation;
import org.springframework.transaction.TransactionDefinition;
public enum Propagation {
REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
NEVER(TransactionDefinition.PROPAGATION_NEVER),
NESTED(TransactionDefinition.PROPAGATION_NESTED);
private final int value;
Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
注意事务传播行为在不同类之间调用生效
在同一个类中,对于@Transactional注解的方法调用,事务传播行为不会生效。
这是因为Spring框架中使用代理模式实现了事务机制,在同一个类中的方法调用并不经过代理,而是通过对象的方法调用,因此@Transactional注解的设置不会被代理捕获,也就不会产生任何事务传播行为的效果。
Propagation.REQUIRED(默认传播行为)
如果父方法有事务,就加入父方法事务,如果没有就新建自己独立的事务!
传播行为在子方法的@Transactional中通过propagation 进行设置。
下面代码中是父方法有事务的情况,propagation 设置为Propagation.REQUIRED,在topService()中调用了studentService.changeAge()和studentService.changeName(),因为事务传播行为为REQUIRED,所以changeAge()和changeName()方法在同一个事务中。
此时changeName()发生运行时异常,两个方法同时回滚, 年龄和名字均不会被修改。
java
@Service
public class TopService {
@Autowired
private StudentService studentService;
//测试 事务的传播行为
@Transactional
public void topService(){
studentService.changeAge();
studentService.changeName();
}
}
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
//事务的传播行为
@Transactional(propagation = Propagation.REQUIRED)
public void changeAge(){
studentDao.updateAgeById(998, 1);
}
//事务的传播行为,
@Transactional(propagation = Propagation.REQUIRED)
public void changeName(){
studentDao.updateNameById("dasdas", 1);
int i = 1/0;
}
}
Propagation.REQUIRES_NEW
不管父方法是否有事务,我都新建事务,都是独立的,子方法都是独立的事务
下面代码中,propagation 设置为Propagation.REQUIRES_NEW,在topService()中调用了studentService.changeAge()和studentService.changeName(),因为事务传播行为为REQUIRES_NEW,所以changeAge()和changeName() 子方法是两个独立的事务
此时changeName()发生运行时异常,changeName()发生回滚,不会影响changeAge()方法,年龄将被修改,名字不会修改。
java
@Service
public class TopService {
@Autowired
private StudentService studentService;
//测试 事务的传播行为
@Transactional
public void topService(){
studentService.changeAge();
studentService.changeName();
}
}
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
//事务的传播行为
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void changeAge(){
studentDao.updateAgeById(998, 1);
}
//事务的传播行为,
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void changeName(){
studentDao.updateNameById("dasdas", 1);
int i = 1/0;
}
}
Propagation.NESTED
如果当前存在事务,就在嵌套事务内执行;
- 在外围方法未开启事务的情况下
Propagation.NESTED
和Propagation.REQUIRED
作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰 - 在外围方法开启事务的情况下
Propagation.NESTED
修饰的内部方法属于外部事务的子事务 ,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务
举个例子:如果 bMethod()
回滚的话,aMethod()
不会回滚。如果 aMethod()
回滚的话,bMethod()
会回滚。
java
@Service
Class A {
@Autowired
B b;
@Transactional(propagation = Propagation.REQUIRED)
public void aMethod {
//do something
b.bMethod();
}
}
@Service
Class B {
@Transactional(propagation = Propagation.NESTED)
public void bMethod {
//do something
}
}
2、事务的隔离级别
隔离级别在@Transactional中通过 isolation = Isolation.XXXX 进行设置。
数据库事务的隔离级别是指在多个事务并发执行时,数据库系统为了保证数据一致性所遵循的规定。常见的隔离级别包括:
- 读未提交(READ_UNCOMMITTED):事务可以读取未被提交的数据 ,容易产生脏读、不可重复读和幻读等问题。实现简单但不太安全,一般不用。
- 读已提交(READ_COMMITTED):事务只能读取已经提交的数据,可以避免脏读问题,但可能引发不可重复读和幻读。
- 可重复读(REPEATABLE_READ):在一个事务中,相同的查询将返回相同的结果集,不管其他事务对数据做了什么修改。可以避免脏读和不可重复读,但仍有幻读的问题。
- 串行化(SERIALIZABLE):最高的隔离级别,完全禁止了并发,只允许一个事务执行完毕之后才能执行另一个事务。可以避免以上所有问题,但效率较低,不适用于高并发场景。
关于四种事务隔离级别,和什么是脏读、不可重复读和幻读 ,具体可查看MYSQL事务隔离级别知识点。
TransactionDefinition
接口中定义了五个表示隔离级别的常量:
java
public interface TransactionDefinition {
......
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
......
}
和事务传播行为那块一样,为了方便使用,Spring 也相应地定义了一个枚举类:Isolation
java
public enum Isolation {
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
private final int value;
Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
隔离级别设置
隔离级别在@Transactional中通过 isolation = Isolation.XXXX 进行设置。
java
//isolation = 设置事务的隔离级别,mysql默认是repeatable read!
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void changeInfo() throws FileNotFoundException {
studentDao.updateAgeById(100,1);
studentDao.updateNameById("test1",1);
}
3、设置事务异常回滚
事务异常回滚在@Transactional中通过 rollbackFor = xxxException.class 进行设置。
3.1、默认情况
默认只针对运行时异常回滚,编译时异常不回滚。情景模拟代码如下:
下面程序会终止,且不会回滚,且 updateAgeById(100,1) 的方法 会修改数据库成功
java
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
/**
* timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!
* rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!
* noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
*/
@Transactional(readOnly = false,timeout = 3)
public void changeInfo() throws FileNotFoundException {
studentDao.updateAgeById(100,1);
//主动抛出一个检查异常,测试! 发现不会回滚,因为不在rollbackFor的默认范围内!
new FileInputStream("xxxx");
studentDao.updateNameById("test1",1);
}
}
3.2、设置回滚异常
rollbackFor属性:指定哪些异常类才会回滚,默认是 RuntimeException and Error 异常方可回滚!
下面程序会终止,但是会回滚,因为 FileNotFoundException 属于 Exception异常,updateAgeById方法修改数据库不成功。
java
/**
* timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!
* rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!
* noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
*/
@Transactional(readOnly = false,timeout = 3,rollbackFor = Exception.class)
public void changeInfo() throws FileNotFoundException {
studentDao.updateAgeById(100,1);
//主动抛出一个检查异常,测试! 发现会回滚
new FileInputStream("xxxx");
studentDao.updateNameById("test1",1);
}
3.3、设置不回滚的异常
在默认设置和已有设置的基础上,再指定一个异常类型,碰到它不回滚。
noRollbackFor属性:指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
java
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
/**
* timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!
* rollbackFor = 指定哪些异常才会回滚,默认是 RuntimeException and Error 异常方可回滚!
* noRollbackFor = 指定哪些异常不会回滚, 默认没有指定,如果指定,应该在rollbackFor的范围内!
*/
@Transactional(readOnly = false,timeout = 3,rollbackFor = Exception.class,noRollbackFor = FileNotFoundException.class)
public void changeInfo() throws FileNotFoundException {
studentDao.updateAgeById(100,1);
//主动抛出一个检查异常,测试! 发现不会回滚,因为设置了noRollbackFor
new FileInputStream("xxxx");
studentDao.updateNameById("test1",1);
}
}
4、超时时间
超时时间在@Transactional中通过 timeout = 3 进行设置。
事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)。
此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执行。
概括来说就是一句话:超时回滚,释放资源。
java
@Service
public class StudentService {
@Autowired
private StudentDao studentDao;
/**
* timeout设置事务超时时间,单位秒! 默认: -1 永不超时,不限制事务时间!
*/
@Transactional(readOnly = false,timeout = 3)
public void changeInfo(){
studentDao.updateAgeById(100,1);
//休眠4秒,等待方法超时!
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
studentDao.updateNameById("test1",1);
}
}
5、只读
对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。
设置方式
java
// readOnly = true把当前事务设置为只读 默认是false!
@Transactional(readOnly = true)
针对DML动作设置只读模式
会抛出下面异常:
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed