Spring:五、编程式事务

news2025/7/17 14:39:27

Spring:五、编程式事务

1 前言

spring支持声明式和编程式事务,因spring事务基于AOP,使用cglib作为代理,为父子类继承的代理模式,故而声明式事务@Transactional中,常见事务失效的场景,如方法内自调用(this.xxx的this不是代理对象)、方法修饰private(代理子类无法调用父类的private方法)、方法修饰final(因final修饰的方法,子类可以继承和重载,但无法重写)、类没有被spring管理等等,避免此类易被忽略而导致事务失效的问题,更推荐使用编程式事务

spring官方文档:

https://docs.spring.io/spring-framework/docs/5.3.25/reference/html/data-access.html#transaction-programmatic

2 使用

依赖:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.5.4</version>
</parent>

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.hibernate.validator</groupId>
        <artifactId>hibernate-validator</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct</artifactId>
        <version>${mapstruct.version}</version>
    </dependency>

    <dependency>
        <groupId>org.mapstruct</groupId>
        <artifactId>mapstruct-processor</artifactId>
        <version>${mapstruct.version}</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
    </dependency>

	<!--     spring连接驱动时,如com.mysql.cj.jdbc.Driver使用   -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.12.0</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-text</artifactId>
        <version>1.9</version>
    </dependency>
   
    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>30.1.1-jre</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-configuration-processor</artifactId>
        <optional>true</optional>
    </dependency>
    
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.15</version>
    </dependency>

</dependencies>

启动类:

@MapperScan(basePackages = "com.xiaoxu.boot.mapper")
@SpringBootApplication(scanBasePackages = "com.xiaoxu")
@ImportResource(locations = {"classpath*:pool/*.xml"})
public class MainApplication {
    public static void main(String[] args) {
        SpringApplication.run(MainApplication.class,args);
    }
}

PeopleMapper:

package com.xiaoxu.boot.mapper;

import com.xiaoxu.boot.dto.PeopleDTO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

//@Mapper
public interface PeopleMapper {
    List<PeopleDTO> queryPeopleByAge(int age);

    @Select("select * from my_people")
    List<PeopleDTO> queryAllPeople();

    int updatePeopleById(@Param("id") long id, @Param("myAge") String my_age);
}

PeopleDaoMapper.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaoxu.boot.mapper.PeopleMapper">
    <select id="queryPeopleByAge" resultType="com.xiaoxu.boot.dto.PeopleDTO">
        select * from my_people where my_age = #{age}
    </select>

    <update id="updatePeopleById">
        update my_people
        <set>
            <if test="myAge != null">
                my_age = #{myAge}
            </if>
        </set>
        <where>
            id = #{id}
        </where>
    </update>

</mapper>

PeopleService:

@Service
public class PeopleService {
    @Autowired
    PeopleMapper peopleMapper;

    public List<PeopleDTO> getPeoples(int Age){
        return peopleMapper.queryPeopleByAge(Age);
    }

    public List<PeopleDTO> getAllPeople(){
        return peopleMapper.queryAllPeople();
    }

    public long updatePeopleAgeById(String age, int id){
        return peopleMapper.updatePeopleById(id, age);
    }

}

druid.properties:

druid.driverClassName = com.mysql.cj.jdbc.Driver
druid.url = jdbc:mysql://localhost:3306/xiaoxu?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
druid.userName = root
druid.password = ******

DataSource.xml(配置TransactionTemplate bean,用于编程式事务):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context = "http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:property-placeholder location="classpath*:druid.properties"/>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${druid.driverClassName}"/>
        <property name="url" value="${druid.url}"/>
        <property name="username" value="${druid.userName}"/>
        <property name="password" value="${druid.password}"/>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager">
            <ref bean="transactionManager"/>
        </property>
    </bean>

</beans>

AbstractTest:

package mybatis;

import com.xiaoxu.boot.MainApplication;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * @author xiaoxu
 * @date 2023-02-03
 * spring_boot:mybatis.AbstractTest
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = MainApplication.class)
public abstract class AbstractTest {
}

单测类:

public class TestUserQuery extends AbstractTest{
    @Autowired
    PeopleService peopleService;

    @Autowired
    TransactionTemplate template;

    @Test
    public void test_01(){

        template.execute(new TransactionCallback<Object>() {
            @Override
            public Object doInTransaction(TransactionStatus transactionStatus) {

                List<PeopleDTO> allPeople = peopleService.getAllPeople();
                allPeople.forEach(System.out::println);
                System.out.println("first over");

                List<PeopleDTO> allPeoples = peopleService.getAllPeople();
                allPeoples.forEach(System.out::println);
                System.out.println("second over");

                long l = peopleService.updatePeopleAgeById("16", 1);
                System.out.println("更新结果:" + l);

                List<PeopleDTO> allPeople1 = peopleService.getAllPeople();
                allPeople1.forEach(System.out::println);
                System.out.println("third over");

                String a = "1";
                if(a.equals("1")){
//                    throw new RuntimeException("1212");
                    transactionStatus.setRollbackOnly();
                }

                return 0;
            }
        });
    }

}

执行前于application.yml增加sql日志打印:

#mybatis的相关配置
mybatis:
  #mapper配置文件
  mapper-locations: classpath:mapper/*.xml
#  #mybatis配置文件
#  config-location: classpath:mybatis-config.xml
#  config-location和configuration不能同时存在
  #开启驼峰命名
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

执行结果如下:

在这里插入图片描述
在这里插入图片描述

同一个事务中,mybatis的sqlSession是同一个(mybatis底层使用JDK动态代理,执行比如selectList方法时,如果上下文中开启了事务,那么sqlSession是同一个对象。而mybatis的1级缓存,在BaseExecutor中的PerpetualCache localCache中,是sqlSession维度的,即同一个sqlSession同享1级缓存),故而一个事务中多次查询,因使用的是同一个sqlSession,又因为mybatis的一级缓存是同一个sqlSession共用,故而连续两次查询一定得到的是同样的结果。如果第一次查询后有更新、插入、删除操作,那么mybatis一级缓存将会刷新。

故而上述第二次查询时,没有打印sql日志,取的数据为1级缓存中的数据。而后执行更新(或插入、删除)后,再次查询,此时打印查询sql日志。

另在TransactionTemplate事务执行中,由execute方法源码可知,默认捕获RuntimeException、Error后,执行事务回滚,当然,亦可同上述操作,在事务处理的代码逻辑捕获异常后,手动执行transactionStatus.setRollbackOnly(),亦可使事务回滚。

3 事务执行拓展

事务执行中,常见问题是,一般不能在事务执行中,执行非事务型操作,如非事务型的消息、rpc调用等等。因为若数据库事务回滚,但是消息已经发送(或rpc调用已造成影响),会造成数据不一致问题。若希望事务执行完成后,再执行部分操作如消息发送等,可以尝试如下拓展。

class DoTrans implements TransactionSynchronization{

    private final Runnable runnable;

    public DoTrans(Runnable runnable) {
        this.runnable = runnable;
    }

    @Override
    public void afterCompletion(int status) {
        if(status == STATUS_COMMITTED){
            /* 0 */
            System.out.println("事务已提交");
            this.runnable.run();
        }else if(status == STATUS_ROLLED_BACK){
            /* 1 */
            System.out.println("事务已回滚, 不做处理.");
        }else if(status == STATUS_UNKNOWN){
            /* 2 */
            System.out.println("未知状态, 不做处理.");
        }
    }
}

单测方法:

@Test
public void test_02(){

    Runnable runnable = () -> {
        System.out.println("事务已执行, 发送消息.");
    };

    template.execute(new TransactionCallback<Object>() {
        @Override
        public Object doInTransaction(TransactionStatus transactionStatus) {

            if(TransactionSynchronizationManager.isActualTransactionActive()){
                System.out.println("事务已开启.");
                TransactionSynchronizationManager.registerSynchronization(new DoTrans(runnable));
            }

            List<PeopleDTO> allPeople = peopleService.getAllPeople();
            allPeople.forEach(System.out::println);
            System.out.println("first over");

            List<PeopleDTO> allPeoples = peopleService.getAllPeople();
            allPeoples.forEach(System.out::println);
            System.out.println("second over");

//                String a = "1";
//                if(a.equals("1")){
//                    transactionStatus.setRollbackOnly();
//                }

            return 0;
        }
    });

}

执行结果如下:

在这里插入图片描述

TransactionSynchronization 源码:

public interface TransactionSynchronization extends Ordered, Flushable {
    int STATUS_COMMITTED = 0;
    int STATUS_ROLLED_BACK = 1;
    int STATUS_UNKNOWN = 2;

    default int getOrder() {
        return 2147483647;
    }

    default void suspend() {
    }

    default void resume() {
    }

    default void flush() {
    }

    default void beforeCommit(boolean readOnly) {
    }

    default void beforeCompletion() {
    }

    default void afterCommit() {
    }

    default void afterCompletion(int status) {
    }
}

源码中可见,afterCompletion会在事务执行后,执行对应的trigger方法进行调用。另可注册多个TransactionSynchronization对象,因实现了Ordered接口,亦可指定其执行的顺序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/360969.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于UIAutomation+Python+Unittest+Beautifulreport的WindowsGUI自动化测试框架common目录解析

文章目录1 框架工具说明2 技术栈说明3 框架截图4 源码解析/common目录4.1 common/baseinfo.py4.2 common/creenShot.py4.3 common/logOut.py4.4 common/reportOut.py4.5 common/sendMail.py注&#xff1a; 1、本文为本站首发&#xff0c;他用请联系作者并注明出处&#xff0c;谢…

从源码中探究React中的虚拟DOM

引文 通过本文你将了解到 什么是虚拟DOM&#xff1f;虚拟DOM有什么优势&#xff1f;React的虚拟Dom是如何实现的&#xff1f;React是如何将虚拟Dom转变为真实Dom&#xff1f; 一、概念 虚拟DOM实际上是一种用来模拟DOM结构的javascript对象。当页面变化的时候通过一种算法来…

美国拟发布纽扣电池或硬币电池安全标准和通知要求ANSI C18. 3M

2023年2月10日&#xff0c;美国向WTO提交G/TBT/N/USA/1964号通报&#xff0c;拟发布纽扣电池或硬币电池以及含有此类电池的消费品的安全标准和通知要求&#xff0c;征求意见截止日期为2023年3月13日&#xff0c;拟通过日期和生效日期待定。联[1]系 拟定规则通知根据H.R.5313瑞…

微服务保护之sentinel熔断器

文章目录 目录 文章目录 前言 一、解决微服务雪崩的问题 二、使用步骤 三、熔断器的使用 3.1 限流规则 3.1.1流控模式 3.1.2流控效果 3.2 隔离和降级 3.2.1 隔离 3.2.2 降级 四、sentinel规则持久化 总结 前言 在基于 SpringCloud 构建的微服务体系中&#xff0c;服务间的调用…

宝塔搭建实战人才求职管理系统mobile手机端vue源码(五)

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 上一期给大家分享骑士cms会员管理member前端vue在本地运行打包、宝塔发布部署的方式&#xff0c;本期给大家分享&#xff0c;mobile移动端vue怎么在本地运行&#xff0c;打包&#xff0c;实现线上功能更新替换的方…

南卡和wiwu电容笔哪款更值得入手?开学季电容笔推荐

在如今科技飞速发展的时代&#xff0c;数码产品层出不穷&#xff0c;尤其是电容笔。最近一段时间&#xff0c;电容笔非常受欢迎&#xff0c;很多人们都会使用电容笔来搭配平板电脑&#xff0c;不管在学习上或者工作上都随处可见。而随着电容笔的种类越来越多&#xff0c;人们对…

墨菲安全参与信息通信软件供应链安全社区成员大会并获自主研发创新成果奖

2023年2月16日&#xff0c;首届ICT软件供应链安全治理论坛暨信息通信软件供应链安全社区第二届成员大会在北京成功举办&#xff0c;多位业界顶级专家与工业和信息化部网络安全管理局相关领导出席&#xff0c;为现场观众分享了关于软件供应链可持续性与安全治理行业的前瞻与思考…

ICLR 2023|VLDet:从图像-文本对中学习区域-词语对齐的开放词汇式目标检测

原文链接&#xff1a;https://www.techbeat.net/article-info?id4614&isPreview1 作者&#xff1a;林闯 目标检测任务在AI工业界具有非常广泛的应用&#xff0c;但由于数据获取和标注的昂贵&#xff0c;检测的目标一直被限制在预先设定好的有限类别上。而在学术界&#xf…

2023最新软件测试面试题(带答案)

1. 请自我介绍一下(需简单清楚的表述自已的基本情况&#xff0c;在这过程中要展现出自信&#xff0c;对工作有激情&#xff0c;上进&#xff0c;好学) 面试官您好&#xff0c;我叫###&#xff0c;今年26岁&#xff0c;来自江西九江&#xff0c;就读专业是电子商务&#xff0c;毕…

OpenGL学习日记之模型绘制

自己编译运行过程中遇到的一些问题 下载Assimp已编译的lib(因为我们公司的电脑有很多权限和限制&#xff0c;也不能自己安装一些没有报备的软件&#xff0c;所以愁方便我就没有用cMake自己编译了)找到一位免费分享的博主的。 https://blog.csdn.net/lady_killer9/article/deta…

聊聊8万8的私董会,很扎心

聊聊8万8的私董会&#xff0c;很扎心 道几句真心话&#xff0c;很扎心&#xff0c;但也很现实。 如果你喜欢刷抖音&#xff0c;这种感觉应该会更加明显。 股市哀鸿遍野&#xff0c;实体一地鸡毛&#xff0c;我们办公室楼下的门面换了一波又一波。 别说那些不起眼的小生意&a…

第48章 抽离Axios拦截守及其全局变量定义

1 准备工作 1.1 重构src\store\index.js import { createStore } from vuex; export default createStore({ state: { //通过该全局变量&#xff0c;获取全局化存储的1个指定用户的令牌字符串。 token: localStorage.getItem(Token) ? localStorage.getItem(Token) : , //通…

CHAPTER 3 Web Server - httpd配置(二)

Web Server - httpd配置二3.1 httpd配置3.1.1 基于用户的访问控制3.1.2 basic认证配置示例&#xff1a;1. 添加用户2. 添加网页文件3. 定义安全域4. 修改父目录权限5. 访问效果6. 在配置文件中定义一个".htaccess"隐藏文件7. 添加组3.1.3 虚拟主机1. 构建方案2. 基于…

Storage

WebStorage主要提供了一种机制&#xff0c;可以让浏览器提供一种比cookie更直观的key、value存储方式&#xff1a; localStorage&#xff1a;本地存储&#xff0c;提供的是一种永久性的存储方法&#xff0c;在关闭掉网页重新打开时&#xff0c;存储的内容依然保留&#xff1b;…

TCP/IP网络协议族分成及其每层作用

1、可以分为应用层、传输层、网络层、链路层 2、各层的作用 应用层(可以想象成是快递打包过程) 决定了向用户提供应用服务时通信的活动&#xff0c;将要进行的操作或者数据进行一个打包。 传输层&#xff08;可以理解为选择顺丰、圆通等快递公司&#xff09; 提供数据传输的方…

从混沌到清晰,阿里全球商品类目域建设思考

作者&#xff1a;丁浩然 阿里全球化业务平台团队 商品是电商产品体系核心之一&#xff0c;类目则是商品模型核心之一&#xff0c;类目系统提供的基础业务数据贯穿了整个电商体系。本文将为大家分享商品类目域在全球化过程中的建设与思考。 众所周知&#xff0c;商品是电商产品体…

vue后台管理系统项目-table选择多行数据分页列表、一键全选重置功能

table选择多行数据 功能介绍&#xff1a; 1.列表分页功能&#xff1b; 2.一键全选&#xff0c;选中列表所有数据&#xff1b; 3.全选&#xff0c;选中当前页数据&#xff1b; 4.重置&#xff0c;清除选中状态&#xff1b; 5.列表搜索查询&#xff1b; 效果&#xff1a; 1.列表分…

剑指 Offer 28. 对称的二叉树

剑指 Offer 28. 对称的二叉树 难度&#xff1a;easy\color{Green}{easy}easy 题目描述 请实现一个函数&#xff0c;用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样&#xff0c;那么它是对称的。 例如&#xff0c;二叉树 [1,2,2,3,4,4,3] 是对称的。 但是下…

项目管理中,如何制定一个好的项目计划?

项目计划&#xff0c;是一个项目的起点。计划不清晰&#xff0c;执行力再强也只会让项目跑偏。 制定一个好的项目计划有哪些要点&#xff1a; 1、确定目标 项目目标是项目所要达到的期望结果&#xff0c;拥有明确的目标能够帮助我们做好规划&#xff0c;用有效的方式做正确…

一文带你看透前端世界里的日期时间,对就是Date

很高兴我们能够通过不同空间&#xff0c;不同时间&#xff0c;通过这篇博客相识&#xff0c;那一定是一种缘分&#xff0c;一种你和狗哥的缘分。今天我希望通过这篇博客对我所熟知的前端世界里的日期时间做一个汇总&#xff0c;不止是代码上的汇总哦&#xff01; 目录 一、时区…