MySQL 之 本地事务

@Transactional 是 Spring 框架提供的一个注解,用于声明事务性方法。它可以用于类、方法和接口上,以便对数据库操作进行事务管理。通过使用这个注解,我们可以控制事务的传播行为、隔离级别、超时时间以及回滚条件等参数,从而实现更加灵活和高效的事务管理。

下面详细介绍一下 @Transactional 注解的关键属性和用法:

关键属性

(1)propagation(传播行为): 定义了事务方法的传播行为,即当一个事务方法被另一个事务方法调用时,如何共享事务。常见的传播行为包括:

  1. Propagation.REQUIRED(默认):如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
  2. Propagation.SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。
  3. Propagation.MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。
  4. Propagation.REQUIRES_NEW:新建事务,如果当前存在事务,把当前事务挂起。
  5. Propagation.NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  6. Propagation.NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  7. Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,就执行 REQUIRED 行为。

(2)isolation(隔离级别): 定义了事务的隔离级别,即多个并发事务之间的隔离程度。常见的隔离级别包括:

  1. Isolation.READ_UNCOMMITTED:允许读取尚未提交的数据变更,可能导致脏读、不可重复读和幻读。
  2. Isolation.READ_COMMITTED:允许读取并发事务已经提交的数据,可以防止脏读,但是可能出现不可重复读和幻读。
  3. Isolation.REPEATABLE_READ:对同一字段的多次读取结果都是一致的,可以防止脏读和不可重复读,但可能出现幻读。
  4. Isolation.SERIALIZABLE:完全服从ACID的隔离级别,最高级别,事务序列化执行,资源消耗大,性能低下。
  5. timeout(超时时间): 定义了事务的超时时间,以秒为单位。如果在该时间内事务没有完成,则抛出异常。

  6. rollbackFor(回滚条件): 定义了哪些异常类型会导致事务回滚。默认情况下,只有运行时异常(RuntimeException)和错误(Error)会导致事务回滚。如果需要其他异常也触发回滚,可以指定这个属性。

  7. readOnly(只读): 标记事务为只读,用于优化。只读事务不会进行更新操作,因此数据库引擎可能会为只读事务提供优化。

使用方法

@Transactional 注解可以应用于类、方法和接口上。当应用于类上时,类中的所有 public 方法都将具有该类型的事务属性。如果同时应用于方法和类上,方法级别的事务属性将覆盖类级别的定义。

需要注意的是,Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时才会生效。另外,@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP(面向切面编程)的本质决定的。

@Transactional 注解不生效的八大场景

1、数据库引擎不支持事务

这里以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。

2、没有被 Spring 管理

如下面例子所示:

// @Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
    
}

如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。

3、方法不是 public 的

@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

以下来自 Spring 官方文档:

When using proxies, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings. Consider the use of AspectJ (see below) if you need to annotate non-public methods.
也是说的上面那个意思。

4、自身调用问题

来看两个示例:

@Service
public class OrderServiceImpl implements OrderService {

    public void update(Order order) {
        updateOrder(order);
    }
    
    @Transactional
    public void updateOrder(Order order) {
        // update order
    }
    
}

update方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 updateOrder 方法,updateOrder 方法上的事务管用吗?

再来看下面这个例子:

@Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void update(Order order) {
        updateOrder(order);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateOrder(Order order) {
        // update order
    }
    
}

这次在 update 方法上加了 @Transactional,updateOrder 加了 REQUIRES_NEW 新开启一个事务,那么新开的事务管用么?

这两个例子的答案是:不管用!

因为它们发生了自身调用,就调该类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效,这也是老生常谈的经典问题了。

5、数据源没有配置事务管理器

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}

如上面所示,当前数据源若没有配置事务管理器,那也是白搭!

6、不支持事务

来看下面这个例子:

@Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void update(Order order) {
        updateOrder(order);
    }
    
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void updateOrder(Order order) {
        // update order
    }
    
}

Propagation.NOT_SUPPORTED: 表示不以事务运行,当前若存在事务则挂起,都主动不支持以事务方式运行了,那事务生效也是白搭。

7、异常被吃了

这个也是出现比较多的场景:

// @Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
            
        }
    }
    
}

把异常吃了,然后又不抛出来,事务怎么回滚吧!

8、异常类型错误

上面的例子再抛出一个异常:

// @Service
public class OrderServiceImpl implements OrderService {

    @Transactional
    public void updateOrder(Order order) {
        try {
            // update order
        } catch {
            throw new Exception("更新错误");
        }
    }
    
}

这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:

@Transactional(rollbackFor = Exception.class)

这个配置仅限于 Throwable 异常类及其子类。

參考鏈接:总结Spring事务注解@transactional无法生效的八个场景 | w3cschool笔记

@Transactional 注解不生效的特殊场景

(1)同一个方法,但是不同线程之间不会生效

@Transactional
public void insertDevelopment(){
    CompletableFuture.supplyAsync(() -> {
        log.info("one 线程名称:{}",Thread.currentThread().getName());
        Development development = new Development();
        development.setDevName("大数据");
        development.setDevDesc("bigData");
        development.setCreateTime(LocalDateTime.now());
        int insert = mapper.insert(development); //成功
        return development;
    }, executorService).thenAcceptAsync(res -> {
        log.info("two 线程名称:{},参数 {}",Thread.currentThread().getName(),res);
        int insert = mapper.insert(res); //成功
        int i = 1 / 0; //此处异常不会回滚
        redisTemplate.opsForValue().set(res.getDevDesc(),res);
    },executorService).exceptionally(exe -> {
        exe.printStackTrace();
        return null;
    });
}

(2)看下面的例子

@Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
public void handle(){
   throw new RuntimeException("");
}

注意:该方法原本的是目的是不参与到事务中去,但如果该方法执行过程中抛出了任何Exception类型的异常,则调用该方法之前已存在的事务都会被回滚,但是,如果该注解标注的方法中存在与@Transactional注解中操作同一张表,则该注解中的执行的方法也会回滚。

举例:


@Service
public class User2ServiceImpl implements User2Service{
 @Autowired
 private User2Mapper user2Mapper;
​
 @Autowired
 private IncentiveServiceImpl incentiveService;
​
 @Override
 @Transactional(rollbackFor = Exception.class)
 public void testTransactional() {
         deleteById();
         incentiveService.updateById();
         insertNewUser();
 }
​
 public void deleteById() {
     Example example = new Example(User2.class);
     example.createCriteria()
             .andLessThan("createTime",LocalDateTime.now());
     user2Mapper.deleteByExample(example);
 }
​
 @Transactional(rollbackFor = Exception.class,propagation = Propagation.NOT_SUPPORTED)
 public void insertNewUser(){
     User2 user2 = new User2();
     user2.setUserId(String.valueOf((int) (Math.random() * 100) +1))
            .setUserName("那一年的春天,我遇见了你。阳光明媚,花儿盛开,仿佛是上天安排的缘分。
            你的微笑如同阳光,温暖了我的心灵,让我感受到生命的美好"
            .substring((int) (Math.random()*10 + 1),(int) (Math.random()*12 + 1)))
            .setCreateTime(LocalDateTime.now());
     user2Mapper.insert(user2);
     int i = 1/0;
 }
}
insertNewUser()方法虽然不参与事务,但操作的表User 和 deleteById() 方法操作的是同一张表,所以
 insertNewUser()中报异常后,insertNewUser()中执行的写入User表中的操作也会回滚。

解决方法通常涉及以下几个方面:

  • 确保类被 Spring 管理。

  • 使用公开的方法。

  • 确保正确使用接口。

  • 确保事务管理方式(如果使用 CGLIB)一致。

  • 使用代理对象调用事务方法。

  • 合理处理异常,或使用rollbackFor属性。

  • 根据需要正确设置propagation属性。

  • 检查数据库和事务管理器配置。

  • 检查和修复 Spring 配置问题。

  • 确保注解处理器工作正常。

针对具体问题,需要检查并调整配置或代码以确保@Transactional注解能够正常工作。

注意事项

  • 并非所有异常都会导致事务回滚。默认情况下,只有运行时异常(RuntimeException)和错误(Error)会触发回滚。如果需要其他类型的异常也触发回滚,可以通过 rollbackFor 属性来指定。

  • 使用 @Transactional 注解时,需要注意事务的传播行为和隔离级别对性能的影响。不合理的配置可能导致性能下降或数据不一致。

  • 在并发环境中,需要特别注意事务的隔离级别和并发控制,以避免脏读、不可重复读和幻读等问题。

总之,@Transactional 注解是 Spring 框架中强大的事务管理工具,通过合理配置和使用这个注解,可以实现对数据库操作的高效和可靠管理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值