Spring对事务的支持
首先,程序是否支持事务,取决于数据库,比如Mysql的Innodb引擎,那么就支持事务;如果是Myisam引擎,那么就根本不支持事务。
Spring支持两种方式的事务管理,一种是编程式,另一种是注解式。编程式对代码入侵太大,一般使用情况较少;基本上都是通过注解方式来进行事务管理。
事务传播行为
事务传播行为是为了解决业务层方法之间互相调用的事务问题。
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
Propagation.REQUIRED
使用的最多的一个事务传播行为,我们平时经常使用的@Transactional注解默认使用就是这个事务传播行为。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。也就是说:
- 如果外部方法没有开启事务的话,Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
- 如果外部方法开启事务并且被Propagation.REQUIRED的话,所有Propagation.REQUIRED修饰的内部方法和外部方法均属于同一事务 ,只要一个方法回滚,整个事务均回滚。
举个例子:
@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
B b;
@Transactional(propagation=Propagation.REQUIRED)
public void requiredA(){
b.requiredB(); //1
jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
//除于0 ,制造一个程序异常
int s = 3/0; //2
}
}
@Component
@EnableTransactionManagement
public class B {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation=Propagation.REQUIRED)
public void requiredB(){
jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
}
}
在方法A里面,制造一个异常//2处
,此时方法A会回滚,那么方法B也就会回滚。
或者将异常放在方法B中,就算方法A对异常进行了try catch处理,方法B回滚了,那么方法A也一样会回滚。
@Component
@EnableTransactionManagement
public class A {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
B b;
@Transactional(propagation=Propagation.REQUIRED)
public void requiredA(){
jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
try{
//将异常放在B中,就算异常捕获了,A还是会回滚
b.requiredB();
}catch (Throwable e){
System.out.println(e.getMessage());
}
}
}
@Component
@EnableTransactionManagement
public class B {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation=Propagation.REQUIRED)
public void requiredB(){
jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
//除于0 ,制造一个程序异常
int s = 3/0; //2
}
}
Propagation.REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起。也就是说不管外部方法是否开启事务,Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。
比如上面,我们将方法B的注解改为:@Transactional(propagation=Propagation.REQUIRES_NEW)
@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
B b;
@Transactional(propagation=Propagation.REQUIRED)
public void requiredNewA(){
b.requiredNewB(); //1
jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
//除于0 ,制造一个程序异常
int s = 3/0; //2
}
}
@Component
@EnableTransactionManagement
public class B {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void requiredNewB(){
jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
}
}
此时,方法A抛出了异常,方法A会回滚,但是方法B不会回滚,因为方法B是独立的事务。 同理,如果方法B回滚,同样也不会对方法A造成影响,比如:
@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
B b;
@Transactional(propagation=Propagation.REQUIRED)
public void nestedA(){
b.nestedB();
jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
//除于0 ,制造一个程序异常
int s = 3/0;
}
}
@Component
@EnableTransactionManagement
public class B {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation=Propagation.NESTED)
public void nestedB(){
jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
}
}
此时,方法A回滚,那么方法B也就会回滚。
如果将异常放在方法B里面:
@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
B b;
@Transactional(propagation=Propagation.REQUIRED)
public void nestedA(){
b.nestedB();
jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
}
}
@Component
@EnableTransactionManagement
public class B {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation=Propagation.NESTED)
public void nestedB(){
jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
//除于0 ,制造一个程序异常
int s = 3/0;
}
}
Propagation.NESTED
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。也就是说:
它大部分情况都等价于Propagation.REQUIRS_NEW,但是: 如果主事务回滚了,那么NESTED也会回滚。比如:
@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
B b;
@Transactional(propagation=Propagation.REQUIRED)
public void nestedA(){
b.nestedB(); //1
jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
//除于0 ,制造一个程序异常
int s = 3/0; //2
}
}
@Component
@EnableTransactionManagement
public class B {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation=Propagation.NESTED)
public void nestedB(){
jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
}
}
此时方法A产生了异常,出现了回滚,那么方法B也会回滚。
Propagation.MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性) 这个一般用得的比较少。 比如方法A有事务:
@Component
@EnableTransactionManagement //相当于配置文件的spring-tx配置,springboot自动包括该注解
public class A {
@Autowired
JdbcTemplate jdbcTemplate;
@Autowired
B b;
@Transactional(propagation=Propagation.REQUIRED) //1
public void mandatoryA(){
b.mandatoryB();
jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
}
}
@Component
@EnableTransactionManagement
public class B {
@Autowired
JdbcTemplate jdbcTemplate;
@Transactional(propagation=Propagation.MANDATORY)
public void mandatoryB(){
jdbcTemplate.update("insert into user(id,name) value (3,'wangwu')");
}
}
此时程序正常,未见异常。
如果将//1
处,方法A的@Transactional
注解去掉,再次运行执行方法A,就会出现如下异常:
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation 'mandatory'
Propagation.NOT_SUPPORTED
表示不支持事务,方法不会以事务的方式进行。如果有事务存在,将它挂起,以无事务状态运行 。 无事务,就是指底层的Connection对象的autoCommit、isolation等属性与数据库有关,与dataSource设置的属性有关,不会被Spring改变。
Transactional注解
Transactional注解中常用的配置参数
属性名 | 说明 |
---|---|
propagation | 事务的传播行为,默认值为 REQUIRED,上面已说过 |
isolation | 事务的隔离级别,默认值采用 DEFAULT , 下面会说到 |
timeout | 事务的超时时间,单位秒,默认值为-1(不会超时)。 |
readOnly | 指定事务是否为只读事务,默认值为 false。 |
rollbackFor | 用于指定能够触发事务回滚的异常类型,并且可以指定多个异常类型。 |
事务回滚规则
默认情况下,事务在遇到运行时异常(RuntimeException)及Error错误就会回滚。 在遇到检查型(checked)异常不会回滚,比如这样:
@Transactional
public void rollbackForA() throws IOException {
jdbcTemplate.update("insert into user(id,name) value (2,'abc')");
throw new IOException("123");
}
那么该事务不会回滚。
这时可以指定事务回滚规则,在@Transcational
注解中添加rollbackFor
属性:
@Transactional(rollbackFor = IOException.class)
public void rollbackForA() throws IOException {
jdbcTemplate.update("insert into user(id,name) value (2,'abc')");
throw new IOException("123");
}
此时该事务就会回滚。
事务超时时间
timeou,单位秒,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务。
比如,指定事务时间为2秒,下面这个sql执行需要3秒,那么事务就会回滚。
@Transactional(timeout = 2)
public void timeoutA(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
jdbcTemplate.update("insert into user(id,name) value (2,'123')");
}
此时事务超时,回滚并会抛出一个异常:
org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Wed Jul 15 10:32:14 CST 2020
事务只读属性
readOnly,对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。
如果指定了readOnly=true
,但还是update
语句,那么就会抛出异常并回滚。比如:
@Transactional(readOnly=true)
public void readOnlyA(){
jdbcTemplate.update("insert into user(id,name) value (2,'123')");
}
此时就会抛出异常:
org.springframework.dao.TransientDataAccessResourceException: StatementCallback; SQL [insert into user(id,name) value (2,'123')]; Connection is read-only. Queries leading to data modification are not allowed.; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed.
- 只读为什么也要有事务?
如果不用事务,即不加@Tranactional
,那么每条sql会单独开启一个事务。考虑这么个情况,执行n个表的查询语句,其中一些表此时正被其他事务修改,那么你读到的数据就会出现不一致的情况。
如果给方法加上了Transactional注解的话,这个方法执行的所有sql会被放在一个事务中。如果声明了只读事务的话,数据库就会去优化它的执行,并不会带来其他的什么收益。
事务隔离级别
Spring有一个枚举类Isolation
,专门定义事务隔离级别。
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;
}
}
- DEFAULT:使用后端数据库默认的隔离级别,MySQL 默认采用的 REPEATABLE_READ 隔离级别 Oracle 默认采用的 READ_COMMITTED 隔离级别;
- READ_UNCOMMITTED:最低的隔离级别,使用这个隔离级别很少,因为它允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读;
- READ_COMMITTED : 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生;
- REPEATABLE_READ : 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生;
- SERIALIZABLE : 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别;
Spring 事务失效的几种情况
@Transactional只有在public方法才起作用
@Transactional
注解作用的方法,private
方法直接就编译出错,protected
方法注解不会生效。比如:
@Transactional
protected void pA(){
jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
int s =3/0;
}
该方法的事务不会生效
自调用
在一个非事务方法里调用一个事务方法,此时事务方法的事务就会失效。比如:
@Component
@EnableTransactionManagement
public class TxInvalidMaster {
@Autowired
JdbcTemplate jdbcTemplate;
public void a(){
jdbcTemplate.update("insert into user(id,name) value (1,'zhangsan')");
b();
}
@Transactional
public void b(){
jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
int s =3/0;
}
}
执行方法a,会发现方法b的事务并没有生效。
属性rollbackFor指定异常不正确
比如,下面方法抛出的是IoException,但是rollbackFor却是SQLException,那么此时事务也不会回滚
@Transactional(rollbackFor = SQLException.class)
public void rollbackForA() throws IOException {
jdbcTemplate.update("insert into user(id,name) value (2,'lisi')");
throw new IOException("123");
}
1024 up up up 节日快乐!
cool