spring事务为什么不生效,回滚失效,事务try catch

2020-12-30 17:53:54 浏览数 (1)

Spring事务的原理

Spring事务的本质其实就是数据库Innodb对事务的支持,没有innodb是事务支持,spring是无法提供事务支持的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

对于纯jdbc操作数据库,想要用到事务,需要按照以下的步骤进行:

  1. 获取连接Connection connection = DriverManager.getConnection(url, username, root);
  2. 开启事务connection .setAutoCommit(true/false);
  3. 执行CRUD
  4. 提交事务/回滚事务 connection .commit() / connection .rollback();
  5. 关闭连接 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动态代理实现(原理是使用反射机制)

具体有如下四步骤:

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 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代码是画蛇添足。

结论:

  1. 无论内外报 非RuntimeException 错误,都不会回滚。
  2. 如果加上rollbackFor = Exception.class,无论内外怎么报错,都会回滚。

参考: https://my.oschina.net/xiaolyuh/blog/3109049 https://www.cnblogs.com/pjjlt/p/10926398.html

0 人点赞