文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

美团二面:Spring事务不生效的15种场景

2024-11-30 16:37

关注

日常开发中,我们经常使用到spring事务。最近星球一位还有去美团面试,被问了这么一道面试题: Spring 事务在哪几种情况下会不生效? 今天田螺哥跟大家聊聊,spring事务不生效的15种场景。

1. 你的service类没有被Spring管理

//@Service (注释了@Service)
public class TianLuoServiceImpl implements TianLuoService {

@Autowired
private TianLuoMapper tianLuoMapper;

@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

@Transactional
public void addTianLuo(TianLuo tianluo) {
//保存tianluo实体数据库记录
tianLuoMapper.save(tianluo);
//保存tianluo流水数据库记录
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}

2.没有在Spring配置文件中启用事务管理器

@Configuration
public class AppConfig {
// 没有配置事务管理器
}

@Service
public class MyService {
@Transactional
public void doSomething() {
// ...
}
}
@Configuration
public class AppConfig {
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dataSource());
}
}

@Service
public class MyService {
@Transactional
public void doSomething() {
// ...
}
}

如果是Spring Boot项目,它默认会自动配置事务管理器并开启事务支持。

3. 事务方法被final、static关键字修饰

@Service
public class TianLuoServiceImpl {

@Autowired
private TianLuoMapper tianLuoMapper;

@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

@Transactional
public final void addTianLuo(TianLuo tianluo) {
//保存tianluo实体数据库记录
tianLuoMapper.save(tianluo);
//保存tianluo流水数据库记录
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}

4. 同一个类中,方法内部调用

@Service
public class TianLuoServiceImpl implements TianLuoService {

@Autowired
private TianLuoMapper tianLuoMapper;

@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

public void addTianLuo(TianLuo tianluo){
// 调用内部的事务方法
this.executeAddTianLuo(tianluo);
}

@Transactional
public void executeAddTianLuo(TianLuo tianluo) {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}
@Service
public class TianLuoExecuteServiceImpl implements TianLuoExecuteService {

@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

@Transactional
public void executeAddTianLuo(TianLuo tianluo) {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}

@Service
public class TianLuoAddServiceImpl implements TianLuoAddService {

@Autowired
private TianLuoExecuteService tianLuoExecuteService;

public void addTianLuo(User user){
tianLuoExecuteService.executeAddTianLuo(user);
}
}

当然,有时候你也可以在该 Service 类中注入自己,或者通过AopContext.currentProxy()获取代理对象。

5.方法的访问权限不是public

@Service
public class TianLuoServiceImpl implements TianLuoService {

@Autowired
private TianLuoMapper tianLuoMapper;

@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

@Transactional
private void addTianLuo(TianLuo tianluo) {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}

6. 数据库的存储引擎不支持事务

Spring事务的底层,还是依赖于数据库本身的事务支持。在MySQL中,MyISAM存储引擎是不支持事务的,InnoDB引擎才支持事务。因此开发阶段设计表的时候,确认你的选择的存储引擎是支持事务的。

7 .配置错误的 @Transactional 注解

@Transactional(readOnly = true)
public void updateUser(User user) {
userDao.updateUser(user);
}

8.事务超时时间设置过短

@Transactional(timeout = 1)
public void doSomething() {
//...
}

9. 使用了错误的事务传播机制

@Service
public class TianLuoServiceImpl {

@Autowired
private TianLuoMapper tianLuoMapper;
@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void doInsertTianluo(TianLuo tianluo) throws Exception {
tianLuoMapper.save(tianluo);
tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo));
}
}

帮大家复习一下,Spring提供了七种事务传播机制。它们分别是:

10. rollbackFor属性配置错误

@Service
public class TianLuoServiceImpl implements TianLuoService {

@Autowired
private TianLuoMapper tianLuoMapper;

@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

@Transactional(rollbackFor = Error.class)
public void addTianLuo(TianLuo tianluo) {
//保存tianluo数据库记录
tianLuoMapper.save(tianluo);
//保存tianluo流水数据库记录
tianLuoFlowMapper.saveFlow(tianluo);
//模拟异常抛出
throw new Exception();
}
}

大家可以看一下Transactional注解源码哈:

11.事务注解被覆盖导致事务失效

public interface MyRepository {
@Transactional
void save(String data);
}

public class MyRepositoryImpl implements MyRepository {
@Override
public void save(String data) {
// 数据库操作
}
}

public class MyService {

@Autowired
private MyRepository myRepository;

@Transactional
public void doSomething(String data) {
myRepository.save(data);
}
}

public class MyTianluoService extends MyService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void doSomething(String data) {
super.doSomething(data);
}
}

12.嵌套事务的坑

@Service
public class TianLuoServiceInOutService {

@Autowired
private TianLuoFlowService tianLuoFlowService;
@Autowired
private TianLuoMapper tianLuoMapper;

@Transactional
public void addTianLuo(TianLuo tianluo) throws Exception {
tianLuoMapper.save(tianluo);
tianLuoFlowService.saveFlow(tianluo);
}
}

@Service
public class TianLuoFlowService {

@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

@Transactional(propagation = Propagation.NESTED)
public void saveFlow(TianLuo tianLuo) {
tianLuoFlowMapper.save(tianLuo);
throw new RuntimeException();
}
}

以上代码使用了嵌套事务,如果saveFlow出现运行时异常,会继续往上抛,到外层addTianLuo的方法,导致tianLuoMapper.save也会回滚啦。如果不想因为被内部嵌套的事务影响,可以用try-catch包住,如下:

@Transactional
public void addTianLuo(TianLuo tianluo) throws Exception {
tianLuoMapper.save(tianluo);
try {
tianLuoFlowService.saveFlow(tianluo);
} catch (Exception e) {
log.error("save tian luo flow fail,message:{}",e.getMessage());
}
}

13. 事务多线程调用

@Service
public class TianLuoService {

@Autowired
private TianLuoMapper tianLuoMapper;

@Autowired
private TianLuoFlowService tianLuoFlowService;

@Transactional
public void addTianLuo(TianLuo tianluo) {
//保存tianluo数据库记录
tianLuoMapper.save(tianluo);
//多线程调用
new Thread(() -> {
tianLuoFlowService.saveFlow(tianluo);
}).start();
}
}

@Service
public class TianLuoFlowService {

@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

@Transactional
public void save(TianLuo tianLuo) {
tianLuoFlowMapper.saveFlow(tianLuo);
}
}

在Spring事务管理器中,通过TransactionSynchronizationManager类来管理事务上下文。TransactionSynchronizationManager内部维护了一个ThreadLocal对象,用来存储当前线程的事务上下文。在事务开始时,TransactionSynchronizationManager会将事务上下文绑定到当前线程的ThreadLocal对象中,当事务结束时,TransactionSynchronizationManager会将事务上下文从ThreadLocal对象中移除。

14.异常被捕获并处理了,没有重新抛出

@Service
public class TianLuoServiceImpl implements TianLuoService {

@Autowired
private TianLuoMapper tianLuoMapper;

@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

@Transactional
public void addTianLuo(TianLuo tianluo) {
try {
//保存tianluo数据库记录
tianLuoMapper.save(tianluo);
//保存tianluo flow数据库记录
tianLuoFlowMapper.saveFlow(tianluo);
} catch (Exception e) {
log.error("add TianLuo error,id:{},message:{}", tianluo.getId(),e.getMessage());
}
}

}
public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean {

//这方法会省略部分代码,只留关键代码哈
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {

if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {

TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
//Spring AOP中MethodInterceptor接口的一个方法,它允许拦截器在执行被代理方法之前和之后执行额外的逻辑。
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
//用于在发生异常时完成事务(如果Spring catch不到对应的异常的话,就不会进入回滚事务的逻辑)
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}

//用于在方法正常返回后提交事务。
commitTransactionAfterReturning(txInfo);
return retVal;
}
}

在invokeWithinTransaction方法中,当Spring catch到Throwable异常的时候,就会调用completeTransactionAfterThrowing()方法进行事务回滚的逻辑。但是,在TianLuoServiceImpl类的spring事务方法addTianLuo中,直接把异常catch住了,并没有重新throw出来,因此 Spring自然就catch不到异常啦,因此事务回滚的逻辑就不会执行,事务就失效了。

@Service
public class TianLuoServiceImpl implements TianLuoService {

@Autowired
private TianLuoMapper tianLuoMapper;

@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

@Transactional(rollbackFor = Exception.class)
public void addTianLuo(TianLuo tianluo) {
try {
//保存tianluo数据库记录
tianLuoMapper.save(tianluo);
//保存tianluo flow数据库记录
tianLuoFlowMapper.saveFlow(tianluo);
} catch (Exception e) {
log.error("add TianLuo error,id:{},message:{}", tianluo.getId(),e.getMessage());
throw e;
}
}
}

15. 手动抛了别的异常

@Service
public class TianLuoServiceImpl implements TianLuoService {

@Autowired
private TianLuoMapper tianLuoMapper;

@Autowired
private TianLuoFlowMapper tianLuoFlowMapper;

@Transactional
public void addTianLuo(TianLuo tianluo) throws Exception {
//保存tianluo数据库记录
tianLuoMapper.save(tianluo);
//保存tianluo流水数据库记录
tianLuoFlowMapper.saveFlow(tianluo);
throw new Exception();
}
}

注解为事务范围的方法中,事务的回滚仅仅对于unchecked的异常有效。对于checked异常无效。也就是说事务回滚仅仅发生在,出现RuntimeException或Error的时候。通俗一点就是:代码中出现的空指针等异常,会被回滚。而文件读写、网络超时问题等,spring就没法回滚了。

来源:捡田螺的小男孩内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯