自定义编程式事务

相信大家平时CRUD肯定少不了用事务处理问题,说到事务第一个想到的肯定是 @Transactional 注解,这玩意方便,放到方法头就能开盖即食,但是方便的同时也有一些弊端,最近就刚好碰到事务常见的一个问题:长事务

先提前预热一下事务的相关常识

1、什么是事务

事务是数据一致性最基本的保证,也就是说一个事务中的操作要么都成功,要么都失败,不允许部分成功。我们常说的事务就是jdbc事务

2、事务的传播属性

1) REQUIRED(默认属性)

如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
被设置成这个级别时,会为每一个被调用的方法创建一个逻辑事务域。如果前面的方法已经创建了事务,那么后面的方法支持当前的事务,如果当前没有事务会重新建立事务。

2) MANDATORY

支持当前事务,如果当前没有事务,就抛出异常。

3) NEVER

以非事务方式执行,如果当前存在事务,则抛出异常。

4) NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

5) REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起。

6) SUPPORTS

支持当前事务,如果当前没有事务,就以非事务方式执行。

7) NESTED

支持当前事务,新增Savepoint点,与当前事务同步提交或回滚。

嵌套事务一个非常重要的概念就是内层事务依赖于外层事务。外层事务失败时,会回滚内层事务所做的动作。而内层事务操作失败并不会引起外层事务的回滚。

3、长事务的危害

一个事务如果过长会有什么影响?简单来说,在事务被开启到commit为止,数据库会一直占用锁资源,其次所有的事务视图会一直保存着,暂用存储空间,如果涉及到大量数据变更或io连接的话,就更加难堪了,最严重的就是数据库挂掉,这要是到生产影响可就不是一毛两毛的事了

4、解决方案

解决长事务第一个想到的就是缩小事务的粒度,把需要控制事务的方法抽出来独立加事务处理;@Transactional 也被称为声明式事务管理,通过AOP的方式由Spring容器集中管理,另一种就是编程式事务管理了,可以自由的控制事务的范围,虽然没有@Transactional 那么舒服,但是至少透明可控啊,代码如下
解决长事务第一个想到的就是缩小事务的粒度,把需要控制事务的方法抽出来独立加事务处理;@Transactional** 也被称为声明式事务管理,通过AOP的方式由Spring容器集中管理,另一种就是编程式事务管理了,可以自由的控制事务的范围,虽然没有@Transactional 那么舒服,但是至少透明可控啊,代码如下

定义一个执行接口

public interface TransactionCallBack { T doInTransaction(TransactionStatus status) throws Exception; }

定义编程式事务工具

@Component
@Slf4j
public class TransactionTemplate extends DefaultTransactionDefinition implements InitializingBean {

    /**
     * 事务管理器
     */
    @Autowired
    private PlatformTransactionManager transactionManager;

    @Override
    public void afterPropertiesSet() {
        // 校验管理器是否被spring注入
        if (this.transactionManager == null) {
            throw new IllegalArgumentException("Property 'transactionManager' is required");
        }
    }

    /**
     * 事务执行器
     * @param action
     * @param <T>
     * @return
     */
    @Transactional
    public <T> T execute(TransactionCallBack<T> action){
        TransactionStatus status = this.transactionManager.getTransaction(this);
        T result = null;
        try {
            result = action.doInTransaction(status);
        }catch (Exception e){
            // 事务回滚
            this.transactionManager.rollback(status);
            log.error("事务执行异常",e);
            return result;
        }
        // 事务提交
        this.transactionManager.commit(status);
        return result;
    }

}

如果不声明传播属性的话,默认是REQUIRED,当然也可以根据实际情况设置不同的方式

@Component
public class CustomizeTransactionTemplate extends TransactionTemplate{

    /**
     * 设置事务传播行为
     * PROPAGATION_REQUIRED : 如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务
     * PROPAGATION_MANDATORY  : 支持当前事务,如果当前没有事务,就抛出异常。
     * PROPAGATION_NEVER   : 以非事务方式执行,如果当前存在事务,则抛出异常。
     * PROPAGATION_NOT_SUPPORTED   : 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
     * PROPAGATION_REQUIRES_NEW    : 新建事务,如果当前存在事务,把当前事务挂起。
     * PROPAGATION_SUPPORTS     : 支持当前事务,如果当前没有事务,就以非事务方式执行。
     * PROPAGATION_NESTED      : 支持当前事务,新增Savepoint点,与当前事务同步提交或回滚。
     *
     * @return
     * @author gqq  2022/9/27 - 15:50
     **/
    public void setPropagationName(String constantName){
        this.setPropagationBehaviorName(constantName);
    }
}

测试类如下

public class DemoTest {

    @Autowired
    private StoreInfoMapper storeInfoMapper;

    @Autowired
    private CustomizeTransactionTemplate transactionTemplate;

    public void transactionTest(){
        transactionTemplate.execute(status -> {
            StoreInfo storeInfo = storeInfoMapper.selectById(1);
            storeInfo.setUpdateTime(new Date());
            // 更新时间
            storeInfoMapper.updateById(storeInfo);
            System.out.println(1/0);
            return status;
        });
    }
}

执行结果

2022-09-27 17:36:59.005 ERROR 12324 --- [           main] c.c.f.c.s.t.TransactionTemplate          : 事务执行异常

java.lang.ArithmeticException: / by zero
at com.food.service.sub.job.DemoTest.lambda$transactionTest$0(DemoTest.java:34) [classes/:na]
at com.food.common.starter.transaction.TransactionTemplate.execute(TransactionTemplate.java:47) ~[classes/:na]
at com.food.common.starter.transaction.TransactionTemplate$$FastClassBySpringCGLIB$$508a8f86.invoke(<generated>) [classes/:na]
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218) [spring-core-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779) [spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163) [spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) [spring-aop-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123) [spring-tx-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388) [spring-tx-5.3.8.jar:5.3.8]
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119) [spring-tx-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186) [spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750) [spring-aop-5.3.8.jar:5.3.8]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692) [spring-aop-5.3.8.jar:5.3.8]
        ......

由于1/0的原因,当然这个sql是不会被更新的,直接回滚,用到后面会发现,其实编程式事务管理还是很香的,只需注入CustomizeTransactionTemplate就可以直接使用

5、总结

事务还有很深的学问,这里就不一一多说了,平时开发还是要尽量避免长事务的代码,减少事务粒度,就能少吃点bug了
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值