@Transactional 不生效总结

Wesley13
• 阅读 874

坑1: @Transactional 不生效?

解决思路如下:

1. 是否添加依赖

新项目经常会忘记添加各种依赖导致(Transactional依赖AOP实现,因此需要导入aop相关依赖)

compile 'org.springframework.boot:spring-boot-starter-aop'

2. 方法是否是公开的( pubilc )

@Transactional
public void test() {
    // 要求 test() 方法必须是 public 修饰, 如果是IDEA编辑器, 甚至直接警告了
    // TODO
}

3. @Transactional 所属类被 spring 所管理? 类上是否包含 @Controller | @Service | @Component …

@Service
// 要求所属类必须被 spring 容器所管理, 否则不生效那就很正常啦
public class TestService {

    @Transactional
    public void test() {
    }
    
}

4. @Transactional 有些异常没有回滚? 注明 rollbackFor (阿里巴巴规范也要求)

  • 可查的异常(checked exceptions):Exception下除了RuntimeException外的异常
  • 不可查的异常(unchecked exceptions):RuntimeException及其子类和错误(Error)

@Transactional 事务的回滚仅仅对于unchecked的异常有效。对于checked异常无效。也就是说事务回滚仅仅发生在出现RuntimeException或Error的时候。
显示设置 rollbackFor 非常有必要,否则程序出现莫名异常可能会导致事务没有回滚生效

@Transactional(rollbackFor = Throwable.class)
public void test() {
}

5. 查看数据库或表,设置的引擎。MyISAM是不支持事务的,必须改为InnoDB

@Transactional 不生效总结

坑2: 本类方法调用本类事务方法会导致事务不生效

问题源码

public void doBusiness() {
    // ...
    //
    // update sql
    this.update();
    // ...
}


@Transactional(rollbackFor = Throwable.class)
public void update() {
    // do many update sql
    // ...
    // throw new RuntimeException();
    // 这个时候 update() 并不会回滚
}

这里面涉及挺多知识点,在这里主要简单说一下原因就好了,其实也不能说是 @Transactional 的坑, 应该说是 aop 的坑, 因为所有的aop都会有这个问题,原因如下:

我们都知道aop(切面,方法增强),至于它是怎么做到的? 简单问题说明: @Transactional 不生效总结

解决方案1: 配置暴露aop代理类

  1. 配置类添加配置 暴露代理类

    @EnableAspectJAutoProxy(exposeProxy = true)

  2. 使用 AopContext.currentProxy() 获取当前代理对象调用目标方法

    // this.update(); ((TestService) AopContext.currentProxy()).update();

解决方案2: 利用ApplicationContext 获取实例[1]调用目标方法

这里的实例[1]可以理解为代理对象, 但是又不是真正的代理对象

@Autowired
private ApplicationContext applicationContext;

public void doBusiness() {
    // ...
    // update sql
    // this.update();
    applicationContext.getBean(this.getClass()).update();
    throw new RuntimeException();
}

解决方案3: 注入自身实例

@Service
@Slf4j
public class TestService {

    @Autowired
    private TestService testService; // 注入自身实例

    public void doBusiness() {
        // ...
        // update sql
        // this.update();
        testService.update(); // 利用注入实例调用目标方法
    }

    @Transactional(rollbackFor = Throwable.class, propagation = Propagation.NESTED)
    public void update() {
        // do many update sql

    }

}

坑3: Transactional 结合 try-finally 使用偶尔感觉 finally 方法块不执行?

问题源码

@Transactional(rollbackFor = Throwable.class)
public void update() {
    try {
        // 操作一些业务,
    } finally {
        // 业务最终都需要 删除一些临时数据, 因此确保代码执行, 放在finally代码块中
    }
}

这个问题我是在实际开发中遇到的,当时排查了十几分钟大概知道问题所在,问题导致原因是因为 try-finally 中 try 可能会发生异常, 那就会抛出异常,因此就算执行到了finally代码块中, 执行了业务(删除一些临时数据),但是由于整个方法没有catch住异常,导致异常上抛,事务生效,给人假象就是 finally 没有执行一样

解决方案1: 添加 catch 代码块

添加 catch 代码块捕获异常, 不让异常上抛

@Transactional(rollbackFor = Throwable.class)
public void update() {
    try {
        // 操作一些业务,
    } catch(Exception e) {
        log.warn("update() ", e);
    } finally {
        // 业务最终都需要 删除一些临时数据, 因此确保代码执行, 放在finally代码块中
    }
}

解决方案2: 独立finally代理块, 开启新事物提交

独立finally代理块, 开启新事物提交

@Transactional(rollbackFor = Throwable.class)
public void update() {
    try {
        // 操作一些业务
    } finally {
        // 业务最终都需要 删除一些临时数据, 因此确保代码执行, 再放finally代码块中
        applicationContext.getBean(this.getClass()).releaseData();
    }
}

@Transactional(rollbackFor = Throwable.class, propagation = Propagation.REQUIRES_NEW)
public void releaseData() {
    // do many update sql
}

坑4: Transactional 结合 synchronized 使用仍存在并发问题

这里假设单实例环境,并且不考虑事务级别问题

问题源码

@Transactional(rollbackFor = Throwable.class)
public synchronized void doProcess(String id) {
    
    // 操作一些业务
    // 根据ID查询 如果存在就更新, 否则新增
    if(this.getById(id) == null) {
        // 根据 id 新增一条记录
        // ...
        // 这里操作多张表 do many update sql
    } else {
        // 根据ID 更新某些字段
        // ...
        // 这里操作另外一些表 do many update sql
    }
    
}

产生问题:在并发情况下有可能会新增多条重复的记录会有这种情况:A线程根据ID查询了不存在,新增流程往下执行,出了synchronized 作用域准备提交事务的时候(还没提交),被B线程抢夺CPU执行权了,获得执行权的B线程携带ID刚好就是A线程的ID,因此B线程查询了也是不存在,执行新增流程,最终就会有多个新增记录

@Transactional 不生效总结

解决方案1: 数据库表设置限制

数据库表设置唯一主键或者唯一索引进行限制,那么就会确保不会新增重复记录,如果重复记录就会抛出异常

解决方案2: synchronized 作用域包含 proxy 处理事务

synchronized 作用域包含 proxy 处理事务即可, 如下:

public synchronized void doProcess(String id) {
    // 开始事务 在 synchronized 作用域
    applicationContext.getBean(this.getClass()).doBusiness(id);
    // 提交或者回滚 在 synchronized 作用域
}

@Transactional(rollbackFor = Throwable.class)
public void doBusiness(String id) {
    // 操作一些业务
    // 根据ID查询 如果存在就更新, 否则新增
    if(this.getById(id) == null) {
        // 根据 id 新增一条记录
        // ...
        // 这里操作多张表 do many update sql
    } else {
        // 根据ID 更新某些字段
        // ...
        // 这里操作另外一些表 do many update sql
    }
}
点赞
收藏
评论区
推荐文章
捉虫大师 捉虫大师
2年前
Go能实现AOP吗?
hello大家好,我是小楼,今天分享的话题是Go是否能实现AOP?背景写Java的同学来写Go就特别喜欢将两者进行对比,就经常看到技术群里讨论,比如Go能不能实现Java那样的AOP啊?Go写个事务好麻烦啊,有没有Spring那样的@Transactional注解啊?遇到这样的问题我通常会回复:没有、实现不了、再见。直到看了《Go语言底层原理剖析》这本书,
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Easter79 Easter79
3年前
sprintboot
一、邮件发送使用springboot自带的邮件系统就能实现邮件的发送,首先导入依赖:1、新建springboot项目,添加依赖<dependency<groupIdorg.springframework.boot</groupId<artifactIdspringbootstartermail
Wesley13 Wesley13
3年前
@Transactional 回滚不生效原因
事务的管理方式有两种,第一种是编程式事务管理,需要将数据库的自动提交等取消,并且需要自己编写事务代码,第二种则是声明式事务管理模式,spring利用springAOP特性编写了注解即题目中所提到的方式来管理事务,避免开发人员编写大量的事务代码。一、特性先来了解一下@Transactional注解的特性吧,可以更好排查问题1\.service类
Stella981 Stella981
3年前
Spring boot源码分析之Spring循环依赖揭秘
!(https://oscimg.oschina.net/oscnet/be79581de12c41704c44e976d329ad35ad1.gif)若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。或许刚说到这,有的小伙伴就会大惊失色了。Spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就
Wesley13 Wesley13
3年前
@Transactional注解失效的解决方案
一、前言  开发中我们经常使用@Transactional注解来启用Spring事务管理,但是如果使用方法不当,会遇到注解不生效该事务回滚的地方却没有回滚的问题。总结下一般是以下几个原因:1.@Transactional注解只能应用到public可见度的方法上。如果应用在protected、private或者p
Wesley13 Wesley13
3年前
JPA之SQL修改语句
昨天遇到一个小问题,是使用JPA的注解对数据进行修改操作对:@Transactional@Modifying@Query("updatePersonpsetp.name?1")voidupdateById(Stringname);这里要注意的是@Transactional注解和@
Stella981 Stella981
3年前
SpringBoot(2.0.4.RELEASE)+Elasticsearch(6.2.4)+Gradle简单整合
记录一下SpringBoot(2.0.4.RELEASE)Elasticsearch(6.2.4)Gradle整合的一个小例子。1.在Gradle内加入相关jar包的依赖:compile('org.springframework.boot:springbootstarterweb')compile('org.springf
Stella981 Stella981
3年前
GreenDao 使用和数据库升级
1使用方法   一.添加依赖 在bulid.gradle文件下的dependencies下添加所需依赖1.compile'org.greenrobot:greendao:3.2.2'//addlibrary2.compile'org.greenrobot:greendaogenerator:3.2.2
Easter79 Easter79
3年前
SpringCloud动态刷新配置信息
有时候在配置中心有些参数是需要修改的,这时候如何不重启而达到实时生效的效果呢?添加依赖<dependencies...<dependency<groupIdorg.springframework.boot</groupId<artifactIdspringboot