Spring事务的原理
Spring事务的本质其实就是数据库Innodb对事务的支持,没有innodb是事务支持,spring是无法提供事务支持的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
对于纯jdbc操作数据库,想要用到事务,需要按照以下的步骤进行:
- 获取连接
Connection connection = DriverManager.getConnection(url, username, root);
- 开启事务
connection .setAutoCommit(true/false);
- 执行CRUD
- 提交事务/回滚事务
connection .commit() / connection .rollback();
- 关闭连接
connection .close();
代码如下
代码语言:javascript复制 try {
Connection connection = DriverManager.getConnection(url, username, root);
connection .setAutoCommit(false); //开启事务,禁止自动提交
preparedStatement = conn.prepareStatement("update t_category t set t.name='测试' where t.id =?");
preparedStatement.setInt(1, 10);
preparedStatement.executeUpdate() ;
connection .commit(); //提交事务
}catch(Exception e ){
connection .rollback();
}
使用Spring的事务管理功能后,我们可以不再写步骤 2 和 4 的代码,而是由Spirng 自动完成。
Spring的事务机制
Spring的事务机制提供了一个PlatformTransactionManager接口,不同的数据访问技术的事务使用不同的接口实现,如表所示:
数据访问技术 | 实现 |
---|---|
JDBC | DataSourceTransactionManager |
JPA | JapTransactionManager |
Hibernate | HibernateTransactionManager |
JDO | HibernateTransactionManager |
分布式事务 | JtaTransactionManager |
以上参考出处:https://my.oschina.net/xiaolyuh/blog/3109049
以JDBC为例,可以在代码配置事务管理器:
代码语言:javascript复制 @Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(druidDataSource());
return transactionManager;
}
使用@Transactional注解在方法上表明该方法需要事务支持。这是一个基于AOP的实现操作。
AOP的代理:
1.JDK动态代理实现(原理是使用反射机制)
具体有如下四步骤:
- 通过实现 InvocationHandler 接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类; ’通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型; 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
2.CGLIB代理
1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP 2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP 3、如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换
数据库隔离级别
隔离级别 | 隔离级别的值 | 导致的问题 |
---|---|---|
Read-Uncommitted | 0 | 导致脏读 |
Read-Committed | 1 | 避免脏读,允许不可重复读和幻读 |
Repeatable-Read | 2 | 避免脏读,不可重复读,允许幻读 |
Serializable | 3 | 串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重 |
Spring 事务的7个传播属性
常量名称 | 常量解释 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是 Spring 默认的事务的传播。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 支持当前事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。 |
注:Spring的默认事务传播特性是PROPAGATION_REQUIRED,MySQL默认的隔离级别是Repeatable-Read
事务的嵌套例子
代码语言:javascript复制package com.yudianxx.springBootDemo.transation;
import com.yudianxx.springBootDemo.mapper.image.ImageCategoryMapper;
import com.yudianxx.springBootDemo.model.image.Category;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Service
@Slf4j
public class TransactionTestServiceImpl implements TransactionTestService {
@Autowired
TransactionTestServiceImpl transactionTestService;
@Autowired
ImageCategoryMapper imageCategoryMapper;
/**
* 事务测试
*
* @return
*/
@Override
@Transactional(propagation = Propagation.REQUIRED)
public void testTransactional() {
Category category = Category.builder().name("事务测试").build();
/*// 情况 1.
try {
a(category); //内部类调用,不走AOP,事务不起作用,加入a()报错了,插入仍然有效,相当于普通调用
b(category);
} catch (Exception e) {
e.printStackTrace();
}*/
/*
// 情况2.
transactionTestService.a(category); //解决情况一的问题
transactionTestService.b(category);
// throw new RuntimeException(); //没有try catch ,父、子同一事务,父报错,全回滚
*/
/*// 情况3.
try{
transactionTestService.a(category);
transactionTestService.b(category);
throw new RuntimeException(); //父、子同一事务,子方法没有抛异常,父虽然抛了异常但是被catch到,等于没抛出过,所以都不会回滚
}catch (Exception e){
}*/
/*// 情况4.
try {
transactionTestService.a(category);
transactionTestService.b(category);
throw new RuntimeException();
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //强制回滚,假如子是REQUIRES_NEW,则子不回滚
}*/
/*// 情况 5.
try {
transactionTestService.a(category);
transactionTestService.b(category);
throw new RuntimeException();
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); //强制回滚,假如子是REQUIRES_NEW,则子不回滚
}*/
// 情况6.
try {
transactionTestService.a(category);
transactionTestService.b(category);
// transactionTestService.c(category); //不回滚
// transactionTestService.d(category); //回滚
// transactionTestService.e(category); //不回滚 e是另外的事务
// transactionTestService.f(category); //a、b不回滚,f回滚
transactionTestService.g(category); //a、b、g都不回滚
} catch (Exception e) {
}
}
// @Transactional(propagation = Propagation.REQUIRES_NEW)
// 会单独起一个事务,成功了则插入,不受其他事务影响
@Transactional(propagation = Propagation.REQUIRED)
public void a(Category category) {
log.info("进入A方法");
category.setName("事务测试a");
imageCategoryMapper.insert(category);
}
@Transactional(propagation = Propagation.REQUIRED)
public void b(Category category) {
log.info("进入B方法");
category.setName("事务测试b");
imageCategoryMapper.insert(category);
}
@Transactional(propagation = Propagation.REQUIRED)
public void c(Category category) {
log.info("进入c方法");
try {
int j = 1 / 0;
} catch (Exception e) {
// throw e; //如果是把错误抛出来了,上层捕获了就会回滚事务的,如果没有throw,这个方法自己处理了异常就不会抛,相当于没抛异常正常执行
//简单的说,throw e 了相当于没有加try catch
}
}
@Transactional(propagation = Propagation.REQUIRED)
public void d(Category category) {
log.info("进入d方法");
int j = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void e(Category category) {
log.info("进入e方法");
int j = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void f(Category category) {
log.info("进入f方法");
category.setName("事务测试f");
imageCategoryMapper.insert(category);
int j = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void g(Category category) {
log.info("进入g方法");
category.setName("事务测试g");
imageCategoryMapper.insert(category);
try {
int j = 1 / 0;
} catch (Exception e) {
}
}
}
PROPAGATION_REQUIRED(spring 默认)
情况6中, transactionTestService.a(category) 的时候spring已经起了事务,这时调用 transactionTestService.b(category), transactionTestService.b(category)看到自己已经运行在 transactionTestService.a(category) 的事务内部,就不再起新的事务,加入到a的事务。
PROPAGATION_REQUIRES_NEW
情况6中, transactionTestService.e(category) 是新的事务,a和b的事务会挂起,e会新起一个事务。a、b、e回不回滚主要看是否抛出异常。
spring 什么情况下进行事务回滚?
Spring、EJB的声明式事务默认情况下都是在抛出unchecked exception后才会触发事务的回滚 unchecked异常,即运行时异常runntimeException 回滚事务;
checked异常,即Exception可try{}捕获的不会回滚.当然也可配置spring参数让其回滚. 配置如下:
代码语言:javascript复制@Transactional( rollbackFor = Exception.class)
spring的事务边界是在调用业务方法之前开始的,业务方法执行完毕之后来执行commit or rollback(Spring默认取决于是否抛出runtime异常). 如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。 一般不需要在业务方法中catch异常,如果非要catch,在做完你想做的工作后(比如关闭文件等)一定要抛出runtime exception,否则spring会将你的操作commit,这样就会产生脏数据.所以你的catch代码是画蛇添足。
结论:
- 无论内外报 非RuntimeException 错误,都不会回滚。
- 如果加上rollbackFor = Exception.class,无论内外怎么报错,都会回滚。
参考: https://my.oschina.net/xiaolyuh/blog/3109049 https://www.cnblogs.com/pjjlt/p/10926398.html