在Spring中,数据库事务是通过AOP技术来提供服务的。对于声明式事务,是使用@Transactional进行标注的。在@Transactional允许配置许多事物的属性,如事务的隔离级别与传播行为。
数据库标准提出了4种isolation事务隔离级别,分别为:未提交读、读写提交、可重复读和串行化。源码如下:
package org.springframework.transaction.annotation;
/***
* 隔离级别数字数字配置含义:
* -1:数据库默认隔离级别
* 1:未提交读
* 2:读写提交
* 4:可重复读
* 8:串行化
*/
public enum Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
SpringBoot设置默认隔离级别配置:
spring.datasource.tomcat.default-transaction-isolation=2
未提交读(READ_UNCOMMITTED)是最低的隔离级别,其含义是允许一个事物读取另一个事物没提交的数据。优点在于并发能力高,适合那些对数据一致性没有要求而追求高并发的场景,最大缺点是出现脏读。
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
例子讲解:
T3时刻,因为采用未提交读,所以事务2可以读取事务1未提交的库存数据为1,这里当它扣减库存后则数据为0,然后它提交了事务,库存就变为了0 。,而事务1在T5时刻回滚事务,因为第一类丢失更新已经被克服,所以它不会将库存回滚到2,那么最后的结果就变为了0,这样就出现了错误。
读写提交(READ_COMMITTED),一个事务只能读取另外一个事务已提交的数据,不能读取未提交的数据。该级别克服了脏读,但不可重复读。
@Transactional(isolation = Isolation.READ_COMMITTED)
案例讲解:
可重复读(REPEATABLE_READ),目标是克服读写提交中出现的不可重复读的现象,但会出现幻读。
@Transactional(isolation = Isolation.REPEATABLE_READ)
案例讲解:
串行化(SERIALIZABLE),是数据库最高的隔离级别,它能够完全保证数据的一致性,但性能降低了。
隔离级别和可能发生的现象如下:
对于不同的数据库,支持的隔离级别也不一样:Oracle只能支持读写提交和串行化,而MySQL能够支持4种,对于Oracle默认的隔离级别为读写提交,MySQL则是可重复读。
Spring事务机制中对数据库存在7种传播行为,源码如下:
package org.springframework.transaction.annotation;
public enum Propagation {
REQUIRED(0),
SUPPORTS(1),
MANDATORY(2),
REQUIRES_NEW(3),
NOT_SUPPORTED(4),
NEVER(5),
NESTED(6);
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法。
支持事务,如果当前存在事务,就沿用当前事务,如果不存在,则继续采用无事务的方式运行子方法。
必须使用事务,如果当前没有事务,则会抛出异常,如果存在当前事务,就沿用当前事务。
无论当前事务是否存在,都会创建新事务运行方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立。
不支持事务,当前存在事务时,将挂起事务,运行方法。
不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行。
在当前方法调用子方法时,如果子方法发生异常,只回滚子方法执行过的SQL,而不回滚当前方法的事务。
常用的传播行为主要有三种:REQUIRED 、REQUIRES_NEW、 NESTED。
注解@transactional的底层实现是Spring AOP技术,而Spring AOP技术使用的是动态代理。这就意味着对于静态(static)方法和非public方法,注解@Transactional是失效的。
自调用是指一个类的一个方法去调用自身另外一个方法的过程。在自调用的过程中,是类自身的调用,而不是代理对象去调用, 那么就不会产生 AOP,这样 Spring就不能把你的代码植入到约定的流程中。
为了克服这个问题,一方面可以写两个Service,用一个Service去调用另一个Service,这样就是代理对象的调用。Spring才会将你的代码植入事务流程。另一方面,也可以从Spring IoC容器中获取代理对象来启用AOP。从Spring IoC容器中获取代理对象的代码实例如下:
import com.springboot.web.dao.UserDao;
import com.springboot.web.model.User;
import com.springboot.web.service.UserService;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class UserServiceImpl implements UserService, ApplicationContextAware {
@Autowired
UserDao userDao;
private ApplicationContext applicationContext;
//实现生命周期方法,设置Ioc容器
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public void createUsers(List<User> userList){
UserService userService = applicationContext.getBean(UserService.class);
for(User user : userList){
userService.createUser(user);
}
}
@Override
@Transactional(isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRES_NEW)
public void createUser(User user){
userDao.createUser(user);
}
}