Spring事务失效常见场景

 1. Spring事务相关可见:

Spring中的编程式、声明式事务icon-default.png?t=O83Ahttps://blog.csdn.net/weixin_44929475/article/details/142559556

 

2. 常见场景

Spring事务失效可能发生在多种场景下,以下是一些常见的导致Spring事务失效的原因:

  1. 未指定回滚异常@Transactional注解默认只对运行时异常(RuntimeException)回滚,如果没有指定回滚异常类型,抛出其他类型的异常可能不会导致事务回滚。

  2. 异常被捕获:如果在方法内部通过try-catch捕获了异常,Spring AOP无法捕获到异常信息,导致无法进行事务回滚。

  3. 方法内部直接调用:如果在一个@Transactional注解的方法内部直接调用了另一个@Transactional注解的方法,内部方法的事务会被忽略,导致事务失效。

  4. 异步多线程:在异步执行的线程中,事务不会自动传播,需要手动管理事务的边界。

  5. 使用了错误的事务传播机制:事务的传播行为设置不正确可能会导致事务失效。

  6. 方法被private或者final修饰:Spring的事务代理通常是通过动态代理实现的,这些代理要求目标方法是公开可访问的(public)。私有方法(private)或最终方法(final)无法被代理,因此事务将无效。

  7. 当前类没有被Spring容器托管:如果当前实体类上面没有打上@Service@Repository等注解,Spring容器不会对其进行管理,事务注解将不会生效。

  8. 数据库不支持事务:某些数据库存储引擎(如MySQL的MyISAM)不支持事务,只有支持事务的存储引擎(如InnoDB)才能使用Spring事务管理。

  9. 未启用Spring事务管理功能:需要在配置中启用Spring的事务管理功能,例如通过@EnableTransactionManagement注解。

  10. 业务和Spring事务代码不在同一个线程中:由于Spring事务实现中使用了ThreadLocal,业务代码必须和Spring事务代码在同一个线程中,否则事务将不会生效。

3. 实例

3.1 环境

        相关环境搭建参考Spring事务文章。

3.2 代码

3.2.1 新增事务实现接口
public interface ITransactionalService {

    /**
     * 事务失效场景1:未指定回滚异常
     */
    void rollbackException() throws Exception;
    /**
     * 事务失效场景2:异常被捕获
     */
    void tryCatchException();

    /**
     * 事务失效场景3:方法内部调用
     */
    void methodInnerInvoke();

    /**
     * 事务失效场景4:异步多线程
     */
    void asyncThread();

    /**
     * 事务失效场景5:错误的事务传播机制
     */
    void errorPropagation();

    /**
     * 事务失效场景6:方法被private或者final修饰
     */
    void decoratedMethod();

    /**
     * 事务失效场景7:当前类没有被Spring容器托管
     */
    void springNotManaged();

    /**
     * 事务失效场景8:数据库不支持事务
     */
    void databaseNotSupportTransaction();

    /**
     * 事务失效场景9:未启用Spring事务管理功能
     */
    void notEnabledTransaction();

    /**
     * 事务失效场景10:业务和Spring事务代码不在同一个线程中
     */
    void notSameThread();

}
3.2.2 实现类
package com.xiaokai.service.impl;

import com.xiaokai.service.IOrderService;
import com.xiaokai.service.ITransactionalService;
import com.xiaokai.service.IUserService;
import com.xiaokai.tran.entity.OrderEntity;
import com.xiaokai.tran.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * Author:yang
 * Date:2024-09-27 14:39
 * Description:事务服务实现类,用于测试事务相关功能
 */
@Service
@Slf4j
public class TransactionalService implements ITransactionalService {

    @Autowired
    private IOrderService orderService;
    @Autowired
    private IUserService userService;


    // 默认回滚异常为RuntimeException
    @Transactional
    @Override
    public void rollbackException() throws Exception {
        // 第一个业务逻辑
        UserEntity user = getUserEntity();
        Integer countUser = userService.createUser(user);
        log.info("用户创建成功,用户ID:{}", user.getId());

        // 抛出非运行时异常,测试事务回滚、
        if (countUser ==1){
            throw new ClassNotFoundException("非运行时异常,测试事务回滚");
        }

        // 第二个业务逻辑
        OrderEntity order = getOrderEntity(countUser, user);
        Integer countOrder = orderService.createOrder(order);
        log.info("订单创建成功");
    }


    // 捕获异常后,不能进行异常回滚
    @Transactional
    @Override
    public void tryCatchException() {
        // 第一个业务逻辑
        UserEntity user = getUserEntity();
        Integer countUser = userService.createUser(user);
        log.info("用户创建成功,用户ID:{}", user.getId());

        // 抛出异常,测试事务回滚
        try {
            int i = 1/0;
            // 第二个业务逻辑
            OrderEntity order = getOrderEntity(countUser, user);
            Integer countOrder = orderService.createOrder(order);
            log.info("订单创建成功");
        }catch (Exception e){
            log.error("捕获异常,测试事务回滚失败", e);
        }

    }

    // 内部调用事务方法
    @Transactional
    @Override
    public void methodInnerInvoke() {
        // 业务逻辑
        innerInvoke();
        // 业务逻辑
    }

    @Transactional
    public void innerInvoke() {
        UserEntity user = getUserEntity();
        userService.createUser(user);
        // 制造异常
        int i = 1/0;

        OrderEntity order = getOrderEntity(user.getId(), user);
        orderService.createOrder(order);
    }

    // 异步调用事务方法
    @Async
    @Override
    public void asyncThread() {
        innerInvoke();
    }

    // 错误的传播属性
    @Transactional(propagation = Propagation.NEVER)
    @Override
    public void errorPropagation() {
        UserEntity user = getUserEntity();
        userService.createUser(user);
        // 制造异常
        int i = 1/0;

        OrderEntity order = getOrderEntity(user.getId(), user);
        orderService.createOrder(order);
    }

    @Transactional
    @Override
    public void decoratedMethod() {
        notSupportTransaction();
    }

    private void notSupportTransaction() {
        UserEntity user = getUserEntity();
        userService.createUser(user);
        // 制造异常
        int i = 1/0;

        OrderEntity order = getOrderEntity(user.getId(), user);
        orderService.createOrder(order);
    }

    // 该事务方法在另一个类中没有交给Spring管理
    @Transactional
    @Override
    public void springNotManaged() {

    }

    // 数据库不支持事务MyISAM
    @Transactional
    @Override
    public void databaseNotSupportTransaction() {

    }

    // 事务未开启,没有使用@EnableTransactionManagement注解和@transactional注解
    @Transactional
    @Override
    public void notEnabledTransaction() {

    }

    // 事务方法在不同线程中调用
    @Transactional
    @Override
    public void notSameThread() {
        new Thread(() -> {
            innerInvoke();
        }).start();
    }


    private static OrderEntity getOrderEntity(Integer countUser, UserEntity user) {
        OrderEntity order = new OrderEntity().builder()
                .userId(countUser)
                .number(10)
                .name(user.getName())
                .build();
        return order;
    }
    private static UserEntity getUserEntity() {
        UserEntity user = new UserEntity().builder()
                .name("test")
                .iphone("12345678901")
                .address("test address")
                .money(1000)
                .password("123456")
                .build();
        return user;
    }
}

 

3.2.3 order和user相关服务添加查询总记录数的方法和sql语句

IOrderService.java

public interface IOrderService {
    Integer createOrder(OrderEntity orderEntity);

    Integer totalOrderCount();
}

IUserService.java

public interface IUserService {

    Integer createUser(UserEntity userEntity);

    Integer totalUserCount();
}

ServiceImpl.java

@Service
public class IUserServiceImpl implements IUserService {

    @Autowired
    private UserMapper usermapper;

    @Override
    public Integer createUser(UserEntity userEntity) {
        Integer count  = usermapper.insertUser(userEntity);
        return count;
    }

    @Override
    public Integer totalUserCount() {

        return usermapper.totalUserCount();
    }
}


@Service
public class IOrderServiceImpl implements IOrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Override
    public Integer createOrder(OrderEntity orderEntity) {
        int count = orderMapper.insertOrder(orderEntity);
        return count;
    }

    @Override
    public Integer totalOrderCount() {
        return orderMapper.totalOrderCount();
    }
}

与数据库相关的部分照猫画虎即可!!!

3.3 测试

3.3.1 执行测试前后打印信息
// 执行测试前后,打印用户和订单数量
@Before
public void test_beforeCount() {
    Integer userCount = userService.totalUserCount();
    Integer orderCount = orderService.totalOrderCount();
    log.info("before--- userCount: {},orderCount: {}", userCount, orderCount);
}

@After
public void test_afterCount() {
    Integer userCount = userService.totalUserCount();
    Integer orderCount = orderService.totalOrderCount();
    log.info("after--- userCount: {},orderCount: {}", userCount, orderCount);
}
3.3.2 测试方法
3.3.2.1 无指定异常,抛出非默认异常时事务失效
// 执行测试
@Test
public void test_rollback() {
    log.info("测试未指定异常回滚,抛出非运行时异常");
    try {
        transactionalService.rollbackException();
    } catch (Exception e) {
        log.error("捕获异常", e);
    }
}

结果:可以看到执行方法前后user表增加了,order没有,事务回滚失效。

before--- userCount: 14,orderCount: 12
测试未指定异常回滚,抛出非运行时异常
用户创建成功,用户ID:21
捕获异常

java.lang.ClassNotFoundException: 非运行时异常,测试事务回滚
after--- userCount: 15,orderCount: 12
3.3.2.2 捕获异常,回滚失败
@Test
public void test_tryCatch() {
    log.info("测试捕获异");
    transactionalService.tryCatchException();
}

结果:可以看到执行方法前后user表增加了,order没有,事务回滚失效。

DatebaseHikariPool - Starting...
DatebaseHikariPool - Start completed.
before--- userCount: 15,orderCount: 12
测试捕获异
用户创建成功,用户ID:22
捕获异常,测试事务回滚失败

java.lang.ArithmeticException: / by zero

after--- userCount: 16,orderCount: 12

其他失效场景可以添加方法测试!实践出真知

注:完整测试类代码如下

package com;

import com.xiaokai.TransactionApplication;
import com.xiaokai.service.IOrderService;
import com.xiaokai.service.ITransactionalService;
import com.xiaokai.service.IUserService;
import com.xiaokai.service.impl.IOrderServiceImpl;
import com.xiaokai.tran.AnnotationService;
import com.xiaokai.tran.entity.OrderEntity;
import com.xiaokai.tran.entity.UserEntity;
import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.support.TransactionTemplate;

/**
 * Author:yang
 * Date:2024-09-26 11:09
 */
@SpringBootTest(classes = {TransactionApplication.class})
@Slf4j
@RunWith(SpringRunner.class)
public class TransactionTest {


    @Autowired
    private IOrderService orderService;
    @Autowired
    private IUserService userService;
    @Autowired
    private AnnotationService annotationService;

    @Autowired
    private ITransactionalService transactionalService;

    // 注入编程式事务管理bean
    @Autowired
    private TransactionTemplate transactionTemplate;

    @Test
    public void test_connection() {
        log.info("testTransaction");
        Integer count = orderService.createOrder(new OrderEntity().builder()
                .userId(1)
                .name("xiaokai")
                .number(10)
                .build());
        log.info("createOrder count: {}", count);
    }

    @Test
    public void test_key_id() {
        log.info("test_key_id");
        UserEntity user = new UserEntity().builder()
                .address("beijing")
                .name("xiaokai")
                .password("123456")
                .iphone("13812345678")
                .money(1000)
                .build();
        Integer count = userService.createUser(user);
    }


    @Test
    public void test_program() {
        log.info("test_program_transaction");
        transactionTemplate.execute(status -> {
            try {
                // 添加用户
                UserEntity user = new UserEntity().builder()
                        .name("xiaokai")
                        .iphone("13812345678")
                        .money(1000)
                        .address("beijing")
                        .build();
                Integer countUser = userService.createUser(user);
                if (countUser != 1) {
                    log.error("createUser error count: {}", countUser);
                }else {
                    log.info("createUser count: {},user: {}", countUser, user.toString());
                }
                // 创建异常
                //int i = 1 / 0;

                OrderEntity order = new OrderEntity().builder()
                        .userId(user.getId())
                        .name("xiaokai")
                        .number(10)
                        .build();
                Integer countOrder = orderService.createOrder(order);
                if (countOrder != 1) {
                    log.error("createOrder count: {}", countOrder);
                }else {
                    log.info("createOrder count: {},order: {}", countOrder, order.toString());
                }
            } catch (Exception e) {
                status.setRollbackOnly();
                log.error("transaction rollback", e);
            }
            return 1;
        });
        log.info("test_program_transaction end");
    }

    @Test
    public void test_statement() {
        log.info("test_statement_transaction");
        annotationService.statementTransaction();
        log.info("test_statement_transaction end");
    }

    // 执行测试前后,打印用户和订单数量
    @Before
    public void test_beforeCount() {
        Integer userCount = userService.totalUserCount();
        Integer orderCount = orderService.totalOrderCount();
        log.info("before--- userCount: {},orderCount: {}", userCount, orderCount);
    }

    @After
    public void test_afterCount() {
        Integer userCount = userService.totalUserCount();
        Integer orderCount = orderService.totalOrderCount();
        log.info("after--- userCount: {},orderCount: {}", userCount, orderCount);
    }

    // 执行测试
    @Test
    public void test_rollback() {
        log.info("测试未指定异常回滚,抛出非运行时异常");
        try {
            transactionalService.rollbackException();
        } catch (Exception e) {
            log.error("捕获异常", e);
        }
    }

    @Test
    public void test_tryCatch() {
        log.info("测试捕获异");
        transactionalService.tryCatchException();
    }
}

Spring事务失效场景主要有以下几种: 1. 事务传播属性设置错误:Spring中的事务传播属性指定了事务方法调用其他事务方法时,事务如何传播。如果事务传播属性设置错误,事务可能会失效。例如,设置了Propagation.REQUIRED_NEW属性的方法在调用其他事务方法时,会将当前事务挂起,新开一个事务,如果调用的方法没有使用事务注解,则新开的事务失效。 2. 异常被捕获并处理:如果在事务方法中捕获了异常并进行了处理,事务可能会因为异常被处理而不回滚。例如,在try-catch块中捕获了异常并使用了Logger输出异常信息,而没有将异常再次抛出,这样事务就不会回滚。 3. 事务方法中使用了ThreadLocal:ThreadLocal是线程局部变量,它可以在当前线程中存储数据,但是在事务方法中使用ThreadLocal可能会导致事务失效。因为ThreadLocal存储的数据只在当前线程中可见,如果在事务方法中使用了ThreadLocal存储了一些数据,但在事务提交时这些数据并没有被清空,那么这些数据将会影响到下一次事务的执行,从而导致事务失效。 4. 数据库引擎不支持事务:有些数据库引擎不支持事务,例如MyISAM引擎,如果在使用这些引擎的表上执行事务操作,事务将会失效。 5. 并发情况下使用乐观锁:在并发情况下,如果事务方法中使用了乐观锁,可能会导致事务失效。因为在事务提交时,如果数据已经被其他事务修改过,那么乐观锁会认为数据没有被修改过,从而导致事务提交失败。 需要注意的是,以上场景只是一些常见的情况,实际上事务失效的原因可能是多种多样的,需要具体问题具体分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值