目录

Spring 事务管理概述

演示环境准备

@Transactional 最简单用法

@Transactional 注解属性介绍

propagation 事务传播行为

rollbackFor 与 noRollbackFor

@Transactional 事务实现机制

长事务发生情况与避免

@Transactional 事务失效场景

Java 异常捕获注意事项

Spring boot 2.x 开始默认使用 cglib 动态代理

@EnableAspectJAutoProxy(exposeProxy = true)


Spring 事务管理概述

1、Spring 事务管理分为编程式和声明式的两种方式。编程式事务通过编码方式实现事务,声明式事务基于 AOP(动态代理)将具体业务逻辑与事务处理解耦。

2、声明式事务管理使业务代码逻辑不受污染,在实际使用中较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于 @Transactional 注解的方式。

3、因为现在注解编程已经是主流,所以实际中使用 @Transactional 注解的方式更多,也最方便。

1)默认配置下 Spring 只会回滚运行时异常(非受检查异常),即继承自 RuntimeException 的异常或者 Error。官网

2)@Transactional 注解只能应用到 public 修饰的方法。(官网

4、本文测试的环境是:Java JDK8 + Spring Boot 2.1.4,内部 Spring 版本为 5.1.6,Spring Data JPA 操作 Mysql 数据库。

5、事先提醒:JPA 默认使用 MyISAM 作为 Mysql 的存储引擎,此引擎不支持事务、不支持外键。InnoDB 存储引擎支持事务,可以在配置文件中进行指定。

演示环境准备

1、pom.xml 文件内容如下:

        <!--jpa内部依赖了spring-boot-starter-jdbc,jdbc内部默认依赖Hikari数据源-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mysql数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

pom.xml · 汪少棠/jpaTransactional - Gitee.com

2、application.yml 配置文件如下:

#配置数据源
spring:
  datasource:
    username: root
    password: root
    #mysql驱动8.0.5时,指定时区 serverTimezone. 同时 driver-class-name 也变成如下的新地址,不再是以前的 com.mysql.hdbc.Driver
    url: jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8&serverTimezone=UTC
    driver-class-name: com.mysql.cj.jdbc.Driver

#JPA配置
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
    #指定 JPA 底层的 hibernate方言,使用 InnoDB 作为存储引擎。否则默认 JPA 使用的是 MyISAM 存储引擎,此引擎不支持事务、不支持外键的
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

src/main/resources/application.yml · 汪少棠/jpaTransactional - Gitee.com

3、实体类如下:

import javax.persistence.*;
import java.util.Date;
/**
 * Created by Administrator on 2019/2/27 0027.
 * 电视机实体。应用启动时自动,配置文件中配置 ddl-auto: update:如果数据库不存在,则自动新建,否则不再新建。
 */
@Entity
public class TV {
    //strategy 指定主键生成的方式,AUTO 可以指定 H2 数据库主键自动增长,IDENTITY 可以指定 mysql 主键自动增长
    @Id//标识为主键
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer tvId;//电视id,主键

    /**
     * 下面没标识的属性都会以默认值和数据库表的字段进行映射对应
     * 如果修改默认值,又不属性的,可以参考:https://blog.csdn.net/wangmx1993328/article/details/82048775
     * 中的 "domain Area" 部分
     */
    @Column(length = 16)//长度为16个字符
    private String tvName;//电视名称
    private Float tvPrice;//电视价格
    private Date dateOfProduction;//生产日期

    //......
}

src/main/java/com/wmx/entity/TV.java · 汪少棠/jpaTransactional - Gitee.com

4、持久化层如下:

import com.wmx.entity.TV;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
/**
 * Java 接口可以多继承
 * JpaRepository 中有常用的 CRUD 、分页、排序等方法
 * JpaSpecificationExecutor 可以实现任意的复杂查询
 */
public interface TVRepository extends JpaRepository<TV, Integer>, JpaSpecificationExecutor<TV> {
}

src/main/java/com/wmx/repository/TVRepository.java · 汪少棠/jpaTransactional - Gitee.com

5、service 接口如下:

import com.wmx.entity.TV;
import java.util.List;
public interface TVService {
    //查询所有
    List<TV> findAll();

    //保存或更新
    void save(TV tv);

    //根据主键 tvId 删除
    void deleteById(int id);
}

src/main/java/com/wmx/service/TVService.java · 汪少棠/jpaTransactional - Gitee.com

@Transactional 最简单用法

1、只需要将 @Transactional 注解添加到 service 层实现类的方法上,这个方法就会进行事务管理,这也是最简单的方法。

import com.wmx.entity.TV;
import com.wmx.repository.TVRepository;
import com.wmx.service.TVService;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
@Service
public class TVServiceImpl implements TVService {
    @Resource
    private TVRepository tvRepository;

    @Override
    public List<TV> findAll() {
        //查询所有数据,并以主键 tvId 倒序排序
        return tvRepository.findAll(Sort.by(Sort.Direction.DESC, "tvId"));
    }

    //org.springframework.transaction.annotation.Transactional:事务管理
    //添加到哪个方法上,哪个方法就会进行事务管理
    @Transactional
    @Override
    public void save(TV tv) {
        tvRepository.save(tv);
        //故意制造一个数组下标越界异常,典型的运行时异常
        //如果没加 Transactional 则抛了异常,数据也会添加进数据库,加了Transactional,则会回滚。
        System.out.println("123".split(",")[1]);
    }

    @Override
    public void deleteById(int id) {
        tvRepository.deleteById(id);
    }
}

src/main/java/com/wmx/service/impl/TVServiceImpl.java · 汪少棠/jpaTransactional - Gitee.com

@Transactional 注解属性介绍

1、@Transactional 注解管理事务就是如此的简单,下面来看看注解的详细信息,如下所示为 @Transactional 的源码:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    //当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。
    @AliasFor("transactionManager")
    String value() default "";

    //当配置了多个事务管理器时,可以使用该属性指定选择哪个事务管理器。
    //如 @Transactional(value = "jpaTransactionManager")
    @AliasFor("value")
    String transactionManager() default "";

    //事务的传播行为。默认如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务
    Propagation propagation() default Propagation.REQUIRED;

    //事务的隔离级。默认使用底层数据库自己的隔离级别
    Isolation isolation() default Isolation.DEFAULT;

    //事务超时时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。-1表示永不超时
    int timeout() default -1;

    //事务是否为只读事务,为了忽略那些不需要事务的方法,如读取数据,可以设置 read-only 为 true。
    boolean readOnly() default false;

    //用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。
    Class<? extends Throwable>[] rollbackFor() default {};

    //用于指定能够触发事务回滚的异常名称,可以指定多个
    String[] rollbackForClassName() default {};

    //抛出指定的异常类型,不回滚事务,可以指定多个
    Class<? extends Throwable>[] noRollbackFor() default {};

    //抛出指定的异常名称,不回滚事务,可以指定多个。
    String[] noRollbackForClassName() default {};
}

propagation(传播)事务传播行为,默认值为 Propagation.REQUIRED,它是一个枚举,可选值如下:

REQUIRED(默认值):如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务。required(必须的)。
SUPPORTS:如果当前存在事务,则加入该事务;如果当前不存在事务,则以非事务方式继续运行。supports(支持的)
MANDATORY:如果当前存在事务,则加入该事务;如果当前不存在事务,则抛出异常。mandatory(强制的)
REQUIRES_NEW:重新创建一个新的事务,如果当前存在事务,暂停当前的事务。requires_new(依赖新的)
NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,暂停当前的事务。not_supported(不支持的)
NEVER:以非事务的方式运行,如果当前存在事务,则抛出异常。never(从不)
NESTED:和 REQUIRED 效果一样。nested(嵌套)

isolation(隔离)事务隔离级别,默认值为 Isolation.DEFAULT,它是一个枚举,可选择如下:

DEFAULT(默认值):使用底层数据库默认的隔离级别。
READ_UNCOMMITTED:未提交读。无法防止。
READ_COMMITTED:可提交读。可以防止脏读。
REPEATABLE_READ:可重复读。可以防止脏读、可重复读。mysql 默认隔离级别。
SERIALIZABLE:串行事务。最高事务隔离级别,可以防止脏读、可重复读、幻读。

propagation 事务传播行为

情形1:同类中 save 方法内部调用 deleteById 方法,且两者上面都没有加 @Transactional 事务管理,当控制器层调用 save 方法后,显然会先删除 id 为 14 的数据,然后插入新数据。

 @Override
    public void save(TV tv) {
        deleteById(14);
        tv.setTvName(tv.getTvName());
        tvRepository.save(tv);
    }

    @Override
    public void deleteById(int id) {
        tvRepository.deleteById(id);
    }

情形2:save 方法内部调用 deleteById(两者都未加@Transactional) 后,故意制造一个运行时异常,结果就是 deleteById 会正常删除 id 为 13 的数据,之后 save 因为抛异常了,所以不会再 save 插入新数据了。

    @Override
    public void save(TV tv) {
        deleteById(13);
        //故意制造一个数组下标越界异常,典型的运行时异常
        System.out.println("123".split(",")[1]);
        tv.setTvName(tv.getTvName() + "_xxx");
        tvRepository.save(tv);
    }

    @Override
    public void deleteById(int id) {
        tvRepository.deleteById(id);
    }

情形3:在上面的基础上,在 save 方法上加上 @Transactionl 事务管理,因为事务具有传播特性,所以 save 与 deleteById 位于同一个事务,deleteById 执行后,save 方法抛了异常,会导致数据回滚,所以不会删除也不会新增。

    //org.springframework.transaction.annotation.Transactional:事务管理
    //@Transactional 添加到哪个方法上,哪个方法就会进行事务管理.默认传播特性为REQUIRED
    @Transactional(propagation = Propagation.REQUIRED)
    @Override
    public void save(TV tv) {
        deleteById(12);
        //故意制造一个数组下标越界异常,典型的运行时异常
        System.out.println("123".split(",")[1]);
        tv.setTvName(tv.getTvName() + "_xxx");
        tvRepository.save(tv);
    }

    @Override
    public void deleteById(int id) {
        tvRepository.deleteById(id);
    }

情形4同类中调用者方法上面未加 @Transactionl ,被调用方法上面加 @Transaction 此时事务不会开启,不会生效。

失效原因:事务管理是基于动态代理对象的代理逻辑实现的,在类内部调用类内部的事务方法时,这个调用事务方法的过程并不是通过代理对象来调用的,而是直接通过 this 对象来调用方法,绕过的代理对象。

	// 同类中调用者没有加 @Transactionl,而被调用者加了 @Transactionl,事务不会开启,不会生效
    @Override
    public List<TV> findAll() {
        //查询所有数据,并以主键 tvId 倒序排序
        List<TV> tvList = tvRepository.findAll(Sort.by(Sort.Direction.DESC, "tvId"));
        save(tvList.get(0));
        return tvList;
    }

    // 同类中调用者没有加 @Transactionl,而被调用者加了 @Transactionl,事务不会开启,不会生效
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void save(TV tv) {
        // 数据删除
        tvRepository.deleteById(tv.getTvId());

        // 故意制造一个数组下标越界异常,典型的运行时异常
        System.out.println("123".split(",")[1]);
        tv.setTvName(tv.getTvName() + "_xxx");
        tvRepository.save(tv);
    }

情形5:同类中内部方法相互调用,默认代理模式下,如果调用者自己已经有事务,则被调用的永远和它处于同一事务。如下所示 save 方法有事务管理,deleteById 方法的 REQUIRES_NEW、NOT_SUPPORTED、NEVER 等等都不会有效,REQUIRES_NEW 并不会为 deleteById 新开一个事务。

 //org.springframework.transaction.annotation.Transactional:事务管理
    //@Transactional 添加到哪个方法上,哪个方法就会进行事务管理.默认传播特性为REQUIRED
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(TV tv) {
        deleteById(11);
        //故意制造一个数组下标越界异常,典型的运行时异常
        System.out.println("123".split(",")[1]);
        tv.setTvName(tv.getTvName() + "_xxx");
        tvRepository.save(tv);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
//    @Transactional(propagation = Propagation.NOT_SUPPORTED)
//    @Transactional(propagation = Propagation.NEVER)
    public void deleteById(int id) {
        tvRepository.deleteById(id);
    }

情形6

1、先准备一个额外的接口与其实现类。

public interface TVServiceExt {
    //根据主键 tvId 删除
    void deleteByIdExt(int id);
}

src/main/java/com/wmx/service/TVServiceExt.java · 汪少棠/jpaTransactional - Gitee.com

import com.wmx.repository.TVRepository;
import com.wmx.service.TVServiceExt;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class TVServiceExtImpl implements TVServiceExt {
    @Resource
    private TVRepository tvRepository;

    @Override
    public void deleteByIdExt(int id) {
        tvRepository.deleteById(id);
    }
}

src/main/java/com/wmx/service/impl/TVServiceExtImpl.java · 汪少棠/jpaTransactional - Gitee.com

2、下面从 TvServiceImpl 的 save 方法内部调用 TvServiceExtImpl 的 deleteByIdExt 方法,如下所示,当调用者与被调用双方都没有加 @Transactional 时,完全无事务管理,删除操作会正确执行,save 不执行。

@Service
public class TVServiceImpl implements TVService {
    @Resource
    private TVRepository tvRepository;

    @Resource
    private TVServiceExt tvServiceExt;
    
    //其余方法省略不写了

    //org.springframework.transaction.annotation.Transactional:事务管理
    //@Transactional 添加到哪个方法上,哪个方法就会进行事务管理.默认传播特性为REQUIRED
    @Override
    public void save(TV tv) {
        tvServiceExt.deleteByIdExt(11);//调用其它类中的方法

        //故意制造一个数组下标越界异常,典型的运行时异常
        System.out.println("123".split(",")[1]);
        tv.setTvName(tv.getTvName() + "_xxx");
        tvRepository.save(tv);
    }
}

情形7:save 方法上加上 @Transactional 事务管理,deleteByIdExt 方法上没加,结果和预期的一样,因为事务的传播特性,deleteByIdExt 和 save 处于同一个事务,一荣俱荣,一损俱损,所以删除与插入都不会成功。

    //org.springframework.transaction.annotation.Transactional:事务管理
    //@Transactional 添加到哪个方法上,哪个方法就会进行事务管理.默认传播特性为REQUIRED
    @Override
    @Transactional
    public void save(TV tv) {
        tvServiceExt.deleteByIdExt(10);//调用其它类中的方法

        //故意制造一个数组下标越界异常,典型的运行时异常
        System.out.println("123".split(",")[1]);
        tv.setTvName(tv.getTvName() + "_xxx");
        tvRepository.save(tv);
    }

情形8:TvServiceImpl 的 save 方法内部调用 TvServiceExtImpl 的 deleteByIdExt 方法,save 上加了 @Transactional ,deleteById 上也加了,但是 propagation 等于 requires_new,即自己会新开事务。结果就是 deleteByIdExt 与 save 方法位于两个不同的事务,删除操作会成功,save 会失败。

    //REQUIRES_NEW:表示新开一个事务,如果当前已经有事务,则会暂停
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void deleteByIdExt(int id) {
        tvRepository.deleteById(id);
    }

说到这里应该就已经很清楚了,如果  deleteByIdExt 上面传播特性为 @Transactional(propagation = Propagation.REQUIRED),显然就又会和 save 方法处于同一个事务了。

rollbackFor 与 noRollbackFor

1、默认配置下 Spring 只会回滚运行时异常(未检查异常)(继承自 RuntimeException 的异常)或者 Error,编译时异常默认不会回滚。

如 @Transactional 标注的方法中抛出异常 throw new RuntimeException("xxx"); 则事务会回滚

如 @Transactional 标注的方法中抛出异常 throw new Exception("xxx"); 则事务会不回滚

2、如果想方法中发生某些编译时异常时也回滚,则可以通过 rollbackFor 属性指定,如 :@Transactional(rollbackFor=Exception.class)、@Transactional(rollbackFor=SQLException.class) 等

3、如果想方法中发生某些运行时异常不回滚,则可以通过 noRollbackFor 属性指定,如:@Transactional(notRollbackFor=RunTimeException.class),@Transactional(notRollbackFor=ClassCastException.class)

4、如果 @Transactional 标注的方法中使用 try{}catch{}捕获处理了异常,则事务不再回滚,如果想让事务回滚,则必须继续往外抛:try{xxx}catch(Exception e){ throw new RuntimeException("xxx",e) }

@Transactional 事务实现机制

1、@Transactional 声明目标方法后,Spring Framework 默认使用 AOP 代理,代码运行时生成一个代理对象,根据 @Transactional 的属性配置,代理对象决定该声明 @Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截。

2、在 TransactionInterceptor 拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器 AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务。

3、Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,以 CglibAopProxy 为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。对于 JdkDynamicAopProxy,需要调用其 invoke 方法。

4、默认的代理模式下,只有目标方法由外部调用,才能被 Spring 事务拦截器拦截。在同一个类中的两个方法直接调用,是不会被 Spring 的事务拦截器拦截。

长事务发生情况与避免

1、@Transactional 注解使用 AOP 实现,本质就是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务。

2、Spring 遇到 @Transactional 注解时,会自动从数据库连接池中获取 connection,并开启事务然后绑定到 ThreadLocal 上,对于 @Transactional 注解包裹的整个方法都是使用同一个 connection 连接 。

3、当 @Transactional 注解方法中出现了耗时的操作,比如第三方接口调用,业务逻辑复杂,大批量数据处理等,就会导致占用这个 connection 的时间会很长,数据库连接一直被占用不释放,一旦类似操作过多,或者并非太大,就可能导致数据库连接池耗尽。

4、这种运行时间比较长,长时间未提交的事务,也可以称之为大事务 。

5、长事务引发的常见危害:

  A、数据库连接池被占满,应用无法获取连接资源;
  B、容易引发数据库死锁;
  C、数据库回滚时间长;
  D、在主从架构中会导致主从延时变大。

6、避免长事务:

  A、使用编程式事务手动控制事务范围。
  B、对方法进行拆分,将不需要事务管理的逻辑与事务操作分开:

@Transactional 事务失效场景

1、几个常见的事务不生效的场景

序号失效场景描述
1@Transactional 应用在非 public 修饰的方法上只能标记在 public 修饰的方法上
2@Transactional 注解传播属性 propagation 设置错误

类与类相互调用时,被调用者可以新开事务,不走事务。

3@Transactional 注解回滚异常属性 rollbackFor 设置错误默认配置下 Spring 只会回滚运行时异常(非受检查异常),即继承自 RuntimeException 的异常或者 Error。推荐统一设置为:@Transactional(rollbackFor = Exception.class)
4同类中内部方法相互调用,默认代理模式下,如果调用者自己已经有事务,则被调用的永远和它处于同一事务。被调用者的设置的 REQUIRES_NEW、NOT_SUPPORTED、NEVER 等等都不会有效,
5同类中内部方法相互调用调用者方法自己没有事务,自己也未加 @Transactionl,此时被调用方法上面即使加了 @Transaction,事务也不会生效。因为此时它不是使用的代理方式调用的,而是 this.xx调用的。
6异常被 catch 捕获,没有继续往外抛,导致 @Transactional 失效使用 try{}catch{}捕获处理了异常,事务的AOP无法捕获异常,导致即使出现了异常,事务也不会回滚。如果想让事务回滚,则必须继续往外抛。
7数据库存储引擎不支持事务导致失效Spring Data JPA 默认使用 MyISAM 作为 Mysql 的存储引擎,此引擎不支持事务、不支持外键。InnoDB 存储引擎支持事务,可以在配置文件中进行指定。
8抛出的异常不正确@Transactional  默认只回滚 RuntimeException(运行时异常)和 Error(错误)。
9多线程影响1、子线程中调用其它类中的方法,而目标方法中有事务注解时,则目标方法受事务控制,但与主线程事务不是同一个。
2、子线程中直接调用本类中的方法,即使目标方法有事务注解,目标方法也不受事务控制。
3、主线程、子线程因为获取到的数据库连接不一样,从而是两个不同的事务。
10事务方法被final、static修饰CGLIB是通过生成目标类子类的方式生成代理类的,被final、static修饰的方法,无法被子类重写。
11当前类没有被Spring管理

例如 Service类中没有@Service注解.

@Transactional事务生效的前提条件是需要代理类对目标方法的调用,才能触发事务处理,而代理类是在springBoot启动时创建bean的时候,处理的。如果我们的类没有@Service注解,就不会交给spring容器初始化处理,也就无法为目标类生成代理类。

Java 异常捕获注意事项

Java 异常 (Exception) 剖析 与 用户自定义异常_蚩尤后裔的博客-CSDN博客

Spring boot 2.x 开始默认使用 cglib 动态代理

1、Spring 5.x 中 AOP 默认依旧使用 JDK 动态代理。

2、Spring Boot 2.x 开始,为了解决使用 JDK 动态代理可能导致的类型转化异常而默认使用 CGLIB。如果需要使用 JDK 动态代理可以通过配置项 spring.aop.proxy-target-class=false 进行修改。

# AOP
spring.aop.auto=true # Add @EnableAspectJAutoProxy.
spring.aop.proxy-target-class=true # 是否要创建基于子类(CGLIB)的代理(true),而不是标准的基于Java 接口的代理(false)。

@EnableAspectJAutoProxy(exposeProxy = true)

注意事项描述
AOP 失效  比如定义了一个 AOP 切面(@Pointcut)拦截 Service 层中的方法 B,当从其他类调用方法 B 时(比如 Controller 层),会正常切入拦截,而从本类其他方法中调用方法 B 时,无法切入拦截,因为此时默认并不是通过代理对象调用的,而是直接通过 this 对象来调的。
事务失效  同类中调用者方法自己没有事务,自己也未加 @Transactionl,被调用方法上面加了 @Transaction 注解,此时事务不会开启,不会生效。事务管理是基于动态代理对象的代理逻辑实现的,在类内部调用类内部的事务方法时,这个调用事务方法的过程并不是通过代理对象来调用的,而是直接通过 this 对象来调用方法,绕过的代理对象。

 1、@EnableAspectJAutoProxy 表示支持处理标记有 @Aspect 注解的组件,有两个属性:

exposeProxy default false是否要创建基于子类(CGLIB)的代理(true),而不是标准的基于Java 接口的代理(false)。
proxyTargetClass default false指示 AOP 框架应将代理公开到 ThreadLocal 中, 以便通过 AopContext 类进行检索,默认是关闭的。

2、既然 AOP 和 @Transactional 事务(声明式事务也是基于AOP)都是针对代理对象才能生效,显然解决失效的办法就是不再使用原生对象调用,而是改为由代理对象调用。

第一步:启动类或者配置类上添加注解,指示 AOP 框架应将代理公开到 ThreadLocal 中, 以便通过 AopContext 类进行检索,默认是关闭的。


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
 * EnableAspectJAutoProxy(exposeProxy = true)
 * 指示 AOP 框架应将代理公开为 ThreadLocal, 以便通过{@link.org.springframework.aop.framework.AopContext}类进行检索,默认是关闭的。
 */
@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true)
public class JpaTransactionalApplication {
    public static void main(String[] args) {
        SpringApplication.run(JpaTransactionalApplication.class, args);
    }
}


第二步:同类中调用时不再直接调用,而是先用 AopContext 获取到本类的代理对象,然后由代理对象调用:

@Service
public class TVServiceImpl2 implements TVService {
    @Resource
    private TVRepository tvRepository;
    @Override
    public List<TV> findAll() {
        // 查询所有数据,并以主键 tvId 倒序排序
        List<TV> repositoryAll = tvRepository.findAll(Sort.by(Sort.Direction.DESC, "tvId"));
		// 删除最后一条
        if (!ObjectUtils.isEmpty(repositoryAll)) {
			// 自己上面没有事务,删除方法上有事务,改由代理对象调用,删除方法上的事务才能生效。
            // 尝试返回当前 AOP 代理对象。只有当通过 AOP 调用了调用方法,并且 AOP 框架被设置为公开代理时,该方法才可用。
            // 此方法从不返回 null,如果找不到代理,则直接抛出 IllegalStateException。
            TVServiceImpl2 tvServiceProxy = (TVServiceImpl2) AopContext.currentProxy();
            tvServiceProxy.deleteById(repositoryAll.get(0).getTvId());
        }
        return repositoryAll;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteById(int id) {
        tvRepository.deleteById(id);
        System.out.println("123".split(",")[1]);
    }
}

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐