spring事务使用最佳实践(代码片段)

Andyzty Andyzty     2023-02-02     149

关键词:

目录

1 Spring事务最佳实践

1.1、Spring事务传播机制

1.2、隔离规则

1.3、Spring事务实现方式

1.3.1、编程式事务实现

1.3.2、声明式事务实现

1.3.3、Spring声明式事务使用注意事项

2、事务问题治理

2.1、大事物的危害

2.1.1 事务问题原因分类

2.1.2、大事物带来的潜在风险

2.2、治理方案


1 Spring事务最佳实践

1.1、Spring事务传播机制

Spring定义了七种传播行为:默认为:PROPAGATION_REQUIRED

传播行为

含义

TransactionDefinition.PROPAGATION_REQUIRED

如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到这个事务中。这是最常见的选择。(默认传播行为)

TransactionDefinition.PROPAGATION_SUPPORTS

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

TransactionDefinition.PROPAGATION_MANDATORY

表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常

TransactionDefinition.PROPAGATION_REQUIRED_NEW

表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。

TransactionDefinition.PROPAGATION_NOT_SUPPORTED

表示该方法不应该运行在事务中。如果当前存在事务,就把当前事务挂起。

TransactionDefinition.PROPAGATION_NEVER

表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常

TransactionDefinition.PROPAGATION_NESTED

如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

传播行为是指:当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。该问题只有一个函数不存在函数之间的调用,故排除传播行为的问题。

1.2、隔离规则

Spring的 isolation用于指定事务的隔离规则,默认值为DEFAULT。总共五种隔离规则,如下所示:

@isolation属性

事务属性-隔离规则

含义

脏读

不可重复读

幻读

DEFAULT

TransactionDefinition.ISOLATION_DEFAULT

使用后端数据库默认的隔离级别

 

 

 

READ_UNCOMMITTED

TransactionDefinition.ISOLATION_READ_UNCOMMITTED

允许读取尚未提交的数据变更(最低的隔离级别)

READ_COMMITTED

TransactionDefinition.ISOLATION_READ_COMMITTED

允许读取并发事务已经提交的数据

REPEATABLE_READ

TransactionDefinition.ISOLATION_REPEATABLE_READ

对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改

SERIALIZABLE

TransactionDefinition.ISOLATION_SERIALIZABLE

最高的隔离级别,完全服从ACID的隔离级别,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

MySQL默认隔离级别,是可重复读(RR)

1.3、Spring事务实现方式

目前Spring有两种方式实现事务,分别是编程式和声明式两种,Spring事务的本质其实就是数据库对事务的使用,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的

1.3.1、编程式事务实现

编程式事务就是以代码编程的方式来控制事务的运行。举个例子

//dataSource配置略

//事务管理器
    <bean id="tradeTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="tradeShardDataSource"/>
    </bean>
//事务模板类
    <bean id="tradeTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="tradeTransactionManager"/>
        <property name="isolationLevelName" value="ISOLATION_READ_COMMITTED"/>
    </bean>

通过手动代码就完成了以编程式事务对业务方法的管理。它的缺点也很明显,事务代码和业务代码糅杂在一起,破坏了业务代码条理性,而且也不利于维护和扩展,优点作用范围可控不容易出问题,适用于业务简单、少量事务的场景,而且事务作用范围能够控制在代码块级别


    public boolean saveBscancOrderAndPayOrder(final BscancOrderPO bscancOrderPO, final PayOrderPO payOrderPO) 
        boolean result = false;
        try 
            result = tradeTransactionTemplate.execute(new TransactionCallback<Boolean>() 
                @Override
                public Boolean doInTransaction(TransactionStatus transactionStatus) 
                    try 
                        int row = bscancOrderWrapper.insert(bscancOrderPO);
                        if (row <= 0) 
                            return false;
                        

                        row = payOrderWrapper.insert(payOrderPO);
                        if (row <= 0) 
                            transactionStatus.setRollbackOnly();//关联插入失败回滚
                            return false;
                        
                        return true;
                     catch (Exception e) 
                        transactionStatus.setRollbackOnly();//出现异常回滚
                        return false;
                    
                
            );
         catch (Exception e) 
            LOGGER.error("saveBscancOrderAndPayOrder 获取事务异常订单插入失败, bscancOrderPO:, payOrderPO:", JsonUtil.toJson(bscancOrderPO), JsonUtil.toJson(payOrderPO), e);
        

        return result;
    

1.3.2、声明式事务实现

利用Spring AOP对方法进行拦截。在方法开始之前创建或者加入一个事务,在方法执行完毕之后根据情况提交或回滚事务,声明式事务配置

//datasorce省略

//事务管理器
<bean id="mybatisTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg ref="dataSource"/>
    </bean>
    
//声明式事务配置
<tx:annotation-driven id="txAdvice" transaction-manager="mybatisTransactionManager"/>


//通过AOP定义事务作用范围 
  <aop:config>  
    <aop:pointcut id="serviceMethods" 
        expression="execution(* com.zzc.qrcode.order.core.dao.service..*(..))"/>  
    <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods"/>  
</aop:config>   
@Transactional(rollbackFor = Exception.class) 
public void saveBscancOrderAndPayOrder(final BscancOrderPO bscancOrderPO, final PayOrderPO payOrderPO) 
        bscancOrderWrapper.insert(bscancOrderPO);
        payOrderWrapper.insert(payOrderPO);
    

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别

 

@Transactional 注解的属性信息

属性

描述

name

当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器

propagation

事务的传播行为,默认值为 REQUIRED

isolation

事务的隔离度,默认值采用 DEFAULT

timeout

事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务,不配置默认是30秒

read-only

指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true

rollback-for

用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔

no-rollback- for

抛出 no-rollback-for 指定的异常类型,不回滚事务

1.3.3、Spring声明式事务使用注意事项

因配置不正确,导致方法上的事务没生效

  • @Transactional注解标记的方法是public的,Spring默认通过动态代理的方式实现AOP,对目标方法进行增强,private方法无法代理到,Spring自然也无法动态增强事务处理逻辑

  • @Transactional必须通过代理过的类从外部调用目标方法才能生效。即Spring注入的Bean进行调用的方法

/**
 * @author zhangtianyou on 2021/4/23.
 */
public interface OrderService 

    void insertPayOrder();

    void saveOrder();



import javax.transaction.Transactional;

/**
 * @author zhangtianyou on 2021/4/23.
 */
@Service("orderService")
public class OrderServiceImpl implements OrderService 

    @Override
    @Transactional
    public void insertPayOrder() 
        //DB插入支付订单
        //DB插入订单流水
    

    @Override
    public void saveOrder() 
      //dosomething
        insertPayOrder();
       //dosomething
    

import org.springframework.beans.factory.annotation.Autowired;

/**
 * @author zhangtianyou on 2021/4/23.
 */
public class TransactionTest 


    @Autowired
    private OrderService orderService;

    public void saveOrderTest() 
        //do something
        orderService.saveOrder();

        //do something
    


上面insertPayOrder()处理异常不会回滚,因为事务不生效,应该直接代理的saveOrder()方法无事务,而调用虽然调用了带有注解的内部方法insertPayOrder(),但因insertPayOrder()不是通过代理调用,顾不生效。

解决上述问题有两种办法,1.在orderService.saveOrder()加上事务@Transactional注解,但会增加事务的范围。2.将orderService.saveOrder()单独抽一层服务biz层中即orderServiceBiz.saveOrder(),在通过orderService.insertPayOrder调用

 

可以打开debug日志 logging.level.org.springframework.orm.jpa=DEBUG 观察

因异常处理不正确,导致事务虽然生效但出现异常时没回滚

Spring默认只会对标记@Transactional注解的方法出现了RuntimeException和Error的时候回滚,如果我们的方法捕获了异常,那么需要通过手动编码处理事务回滚。如果希望Spring针对其他异常也可以回滚,那么可以相应配置@Transactional注解的rollbackFor和noRollbackFor属性来覆盖其默认设置。

  • 对事务内做了try..catch,SpringAOP无法感知事务

    @Override
        @Transactional
        public void insertPayOrder() 
            try 
    						//DB插入支付订单
            		//DB插入订单流水
             catch (Exception e) 
                
            
            
        

默认情况下,出现RuntimeException(非受检异常)或Error的时候,Spring才会回滚事务,如果要对其他业务异常也回滚,则需要配置 rollback-for

@Override
    @Transactional(rollbackFor = Exception.class)
    public void insertPayOrder() throws Exception 
       
						//DB插入支付订单
        		//DB插入订单流水
            //do somethring
        
    

因事务传播方式配置不正确,导致事务不符合预期

如果方法涉及多次数据库操作,并希望将它们作为独立的事务进行提交或回滚,那么我们需要考虑进一步细化配置事务传播方式,也就是@Transactional注解的Propagation属性

2、事务问题治理

2.1、大事物的危害

2.1.1 事务问题原因分类

根据使用经验,将主要事物问题汇总如下:

事务问题分类

详细问题

备注

无需事务

单条更新、插入、删除无需单独显试引入事务,因为对于这类SQL数据库本身开启事务处理

 

对于全部为查询操作,无需引入事务

 

对于跨数据库使用事务(目前项目中未发现)

 

事务范围太大

在一个事务里面, 包含太多流程处理,增加事务处理时长

 

在一个事务里面,包含不必要且耗时的查询DB功能

 

事务范围越大,出现死锁的可能性会越高 

多个事务循环嵌套(和事物传播设置相关)

 

事务使用不当

事务类包含了耗时太多的操作,目前梳理出来的有:

  • RPC调用

  • Spring事件消息

  • 开线程池任务调用(包括循环任务调度,存在大任务量风险)

  • 埋点操作

 

使用了Spring声明式事务,但是方法内部使用try、catch导致事务无效

 

@Transactional 注解使用不当,导致事务无效

 

 

2.1.2、大事物带来的潜在风险

  • 并发情况下,数据库连接池容易被撑爆(如果配置连接池大小虽然不会撑爆、但会占用大量连接池资源)

  • 锁定太多的数据,造成大量的阻塞和锁超时

  • 执行时间长,容易造成主从延迟,而且造成大量锁等待

  • 回滚所需要的时间比较长

  • undo log日志膨胀,不仅增加了存储的空间,而且可能降低查询的性能

  • zebra 事务是走主库,滥用事务有可能导致主库压力过大,数据库整体处理能力下降等

2.2、治理方案

目前从以下几个方面优化:

移除无效声事务

  • 只有读取操作

  • 只有一条单条的更新操作

  • 多次更新操作不在同一个数据库

缩短事务作用范围

  • 减少事务处理范围,将需要处理的事务SQL操作单数封装方法,事务内提出无效的流程和查询

事务内耗时操作

  • RPC调用/接口埋点/leaf调用/线程池任务/Spring事件

事务最佳实践

  • Spring声明式事务最佳实践,异常处理等

  • 将事务使用方法专门抽象一层,避免分散

spring事务使用最佳实践(代码片段)

目录1Spring事务最佳实践1.1、Spring事务传播机制1.2、隔离规则1.3、Spring事务实现方式1.3.1、编程式事务实现1.3.2、声明式事务实现1.3.3、Spring声明式事务使用注意事项2、事务问题治理2.1、大事物的危害2.1.1事务问题原因分类2.1.2、大... 查看详情

第06篇:springaop面向切面编程,最佳实践分享。(代码片段)

...(这种关注点在AOP文献中通常被称为“横切”关注点。)Spring的关键组件之一是AOP框架。虽然SpringIoC容器不依赖AOP(这意味着如果您不想使用AOP,则无需使用AOP),AOP补充了SpringIoC以提供非 查看详情

spring最佳实践~1propertyplaceholderconfigurer乱码解决方案(代码片段)

PropertyPlaceholderConfigurer乱码解决方案一、问题描述使用PropertyPlaceholderConfigurer读取配置文件时,发现对于中文的处理会出现乱码现象。二、示例app-context-processor.xml<?xmlversion="1.0"encoding="UTF-8"?>& 查看详情

使用 Spring JMS 进行错误处理的最佳实践

】使用SpringJMS进行错误处理的最佳实践【英文标题】:BestpracticeforerrorhandlingwithSpringJMS【发布时间】:2011-10-2301:12:45【问题描述】:我正在开发一个基于消息的服务,该服务将所有传入请求排队并稍后处理它们。处理错误的最佳... 查看详情

openfeign的远程调用使用httpclient优化性能及最佳实践方式(代码片段)

(目录)Feign远程调用先来看我们以前利用RestTemplate发起远程调用的代码:存在下面的问题:1.Feign替代RestTemplate1)引入依赖<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeig 查看详情

spring最佳实践~1propertyplaceholderconfigurer乱码解决方案(代码片段)

....0"encoding="UTF-8"?><beansxmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/sch... 查看详情

php小部件最佳实践:使用函数(代码片段)

查看详情

最佳实践数据库事务和将文件存储到 PHP 中的文件系统

】最佳实践数据库事务和将文件存储到PHP中的文件系统【英文标题】:BestpracticedatabasetransactionsandstoringfilesontofilesysteminPHP【发布时间】:2012-07-2408:48:13【问题描述】:如果用户将用户数据与文件一起上传,用户数据存储在数据库... 查看详情

spring security 手动登录最佳实践

】springsecurity手动登录最佳实践【英文标题】:springsecuritymanualloginbestpractice【发布时间】:2018-04-2407:07:42【问题描述】:我正在使用SpringSecurity来实现程序化的手动用户登录。我有一个场景,我已经确定了用户的身份,并希望让... 查看详情

markdown在vuex中使用apollo的最佳实践(代码片段)

查看详情

jmeter之最佳实践(代码片段)

....org/usermanual/best-practices.html 翻译:16.最佳实践16.1始终使用最新版本的JMeterJMeter的性能正在不断提高,因此强烈建议用户使用最新版本。确保始终阅读更改列表以了解新的改进和组件。一定要避免使用与最新版本相差3个版本... 查看详情

编码最佳实践——单一职责原则(代码片段)

...,可以提升代码适应变更的能力。但是凡事要有度,过度使用虽然可以让代码有很高的自适应能力,但是会导致层次粒度过小而难以理解或使用,还会影响代码的可读性。单一职责原则单一职责原则(SingleResponsibilityprinciple)要... 查看详情

Spring @Repository 最佳实践

】Spring@Repository最佳实践【英文标题】:Spring@Repositorybestpractices【发布时间】:2015-03-1808:38:23【问题描述】:上下文:Web应用程序我以前没有使用过Spring,但是根据Spring文档,所有的bean都是singleton,除非我们将它们声明为prototype... 查看详情

使用nacos实现多环境配置以及最佳实践(代码片段)

...一个简单示例,并介绍了配置原理等内容。我们这篇博文使用Nacos实现多环境配置,并在文章最后阐述在产线应用上的最佳实践。针对这篇博文,我们使用上篇博文代码示例,大家可以在cloudalibaba-config-nacos-client3377应用中进行实... 查看详情

spring中事务的使用与配置(代码片段)

文章目录Spring中事务的使用与配置1.事务概念2.事务操作(模拟事务操作环境)3.事务管理(Spring事务管理)4.事务操作(注解声明式事务管理)5.事务操作(声明式事务管理参数配置)6.事务操作ÿ... 查看详情

线程池最佳实践(代码片段)

简单演示一下如何使用线程池privatestaticfinalintCORE_POOL_SIZE=5;privatestaticfinalintMAX_POOL_SIZE=10;privatestaticfinalintQUEUE_CAPACITY=100;privatestaticfinalLongKEEP_ALIVE_TIME=1L;publicstaticvoidmain(String[]a 查看详情

推荐学java——spring事务(代码片段)

目录前情回顾事务概念Spring事务管理器Spring事务定义接口Spring事务隔离级别与传播Spring添加事务Spring事务应用案例场景需求实现流程分析使用Transactional注解添加事务使用AspectJ框架声明事务控制两种方式对比总结前情回顾已经学... 查看详情

spring事务管理(代码片段)

文章目录PlatformTransactionManagerTransactionDefinition属性方法SpringAPI事务管理方式使用底层方法管理事务使用TransactionTemplate管理事务Spring声明式管理事务基于TransactionInterceptor的事务管理基于TransactionProxyFactoryBean声明事务基于\\命名空间... 查看详情