首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >spring事务为什么不生效,回滚失效,事务try catch

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

作者头像
HaC
发布2020-12-30 17:53:54
3K0
发布2020-12-30 17:53:54
举报

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();

代码如下

 	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为例,可以在代码配置事务管理器:

 @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

事务的嵌套例子

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参数让其回滚. 配置如下:

@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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-04-09 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring事务的原理
  • Spring的事务机制
    • AOP的代理:
      • 1.JDK动态代理实现(原理是使用反射机制)
      • 2.CGLIB代理
  • 数据库隔离级别
  • Spring 事务的7个传播属性
  • 事务的嵌套例子
    • PROPAGATION_REQUIRED(spring 默认)
      • PROPAGATION_REQUIRES_NEW
      • spring 什么情况下进行事务回滚?
      相关产品与服务
      云数据库 MySQL
      腾讯云数据库 MySQL(TencentDB for MySQL)为用户提供安全可靠,性能卓越、易于维护的企业级云数据库服务。其具备6大企业级特性,包括企业级定制内核、企业级高可用、企业级高可靠、企业级安全、企业级扩展以及企业级智能运维。通过使用腾讯云数据库 MySQL,可实现分钟级别的数据库部署、弹性扩展以及全自动化的运维管理,不仅经济实惠,而且稳定可靠,易于运维。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档