【JavaEE】Spring事务-事务的基本介绍-事务的实现-@Transactional基本介绍和使用

【JavaEE】Spring事务(1)

在这里插入图片描述

文章目录

  • 【JavaEE】Spring事务(2)
    • 1. 为什么要使用事务
    • 2. Spring中事务的实现
      • 2.1 事务针对哪些操作
      • 2.2 MySQL 事务使用
      • 2.3 Spring 编程式事务(手动挡)
      • 2.4 Spring 声明式事务(自动挡)
      • 2.5 小疑问(@Transactional注解原理)
        • 2.5.1 @Transactional注解原理
        • 2.5.2 为什么必须被五大类注解修饰
        • 2.5.3 为什么@Transactional不支持static方法
    • 3. 实践
      • 3.1 创建项目
      • 3.2 编写代码
      • 3.3 测试
      • 3.4 注意事项
        • 3.4.1 事务不会自动回滚解决方案1:重新抛出
        • 3.4.2 事务不会自动回滚解决方案2:手动回滚

【JavaEE】Spring事务(2)

1. 为什么要使用事务

比如跟钱相关的两个操作:

第一步操作:小马卡里 - 100元

第二步操作:老马卡里 + 100元

这就是一个事务,捆在一起的一组行为,就是事务

白色背景中捆在一起的两个蜡烛图片下载 - 觅知网

而它能保证的是,这个行为的原子性,一致性,隔离性,持久性:

  1. 两个操作都成功
  2. 两个操作都失败

要么一起成功,要么一起失败

但是,如果没有事务呢,则两个操作逻辑上是分开的:

  • 第一个操作成功,第二个操作失败,则小马直接亏了100!

2. Spring中事务的实现

Spring中的事务操作主要分为两类:

  1. 编程式事务(原生方式去写代码操作事务)
  2. 声明式事务(利用注解,“约定规则”去自动开启和提交事务)

2.1 事务针对哪些操作

事务一般针对的是

  1. 持久化相关的操作,如数据库操作、文件系统操作等

正如刚才的那样,两个用户的转账操作

  1. 保证数据完整性的操作,如消息队列等

通过使用事务,可以在消息队列中提供可靠的消息传递机制,减少消息丢失或重复处理的可能性,同时确保系统在出现故障情况下能够正确恢复

事务的概念适用于需要保证一系列操作的原子性和一致性的任何场景

  • 而其中,被持久化的数据,被传播的数据…等操作,都具有**“持久性影响”**的作用,所以要通过事务来控制其影响不要太糟糕
  • 而一些操作,比如打印,都打印到控制台了,不会回滚的,也没有必要回滚,例如查看执行日志…
    • 至于其他的不可见的操作,又没有持久化,是没有影响力的,程序出异常后,这些数据也销毁了~

2.2 MySQL 事务使用

--- 开启事务
start transaction;
--- transaction就是事务的意思--- 提交事务
commit;--- 回滚事务
rollback;

三个重要的操作:

  1. 开启事务
  2. 提交事务
  3. 回滚事务

2.3 Spring 编程式事务(手动挡)

与MySQL操作事务类似:

  1. 开启事务(获取一个事务/创建一个事务并获取)
  2. 提交事务
  3. 回滚事务

SpringBoot 内置了两个对象:

  1. DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或 回滚事务的
  2. TransactionDefinition 是事务的属性,在获取事务的时候需要将 TransactionDefinition 传递进去从而获得⼀个事务 TransactionStatus

实现代码如下:

@RestController
public class UserController {@Resourceprivate UserService userService;// JDBC 事务管理器@Resourceprivate DataSourceTransactionManager dataSourceTransactionManager;// ------------定义事务属性------------@Resourceprivate TransactionDefinition transactionDefinition;@RequestMapping("/sava")public Object save(User user) {// ------------开启事务------------TransactionStatus transactionStatus = dataSourceTransactionManager.getTransaction(transactionDefinition);// ------------插⼊数据库------------int result = userService.save(user);// ------------提交事务------------dataSourceTransactionManager.commit(transactionStatus);// // ------------回滚事务------------// dataSourceTransactionManager.rollback(transactionStatus);return result;}
}

反正就是,麻烦,难记,不简洁,容易出错(难写)

2.4 Spring 声明式事务(自动挡)

声明式事务的实现很简单

  • 只需要在需要的类或者方法上添加 @Transactional 注解 就可以实现了

无需手动开启/提交/回滚事务:

  1. 进入方法,自动开启事务
  2. 方法执行完会,自动提交事务
  3. 如果中途发生了没有处理的异常,自动回滚事务

具体规则/作用范围是:

  • 加在类上,内部的所有非静态public方法都相当于加了 @Transactional 注解
  • 加在非静态public方法上,这个方法就是一个事务
  • 所在的类,必须被五大类注解修饰,这跟其事务的实现有关
    • 而且有了五大类注解,Spring开发才能进行呀~

代码实现:

@Service
@Transactional
public class Test {@Autowiredprivate Mapper mapper;public int save(User user) {mapper.save(user);}
}

@RequestMapping("/save")
@Transactional
public Object save(User user) {int result = userService.save(user);return result;
}

跟往常的注解版不使用注解版的代码一样:

  1. 不使用注解版: 灵活,能实现很多功能,但是麻烦,使用困难,甚至正常人压根没法写,例如事务传播机制的代码实现起来就比较复杂
  2. 使用注解版: 使用规则约束,实现特定功能,但是方便,使用简单,且足以面对正常开发环境,不关心一些极端的不正常开发
    • 对于注解的使用,就是:遵循约定,坐享其成,明白逻辑(作用),合理使用(逻辑分析合理)

编程式就相当于车的手动挡,声明式就相当于车的自动挡,那么现实咱们买不起偏贵的自动挡车,而我们现在可以无条件舒适地使用自动挡,那咋不用嘞🤣🤣🤣

2.5 小疑问(@Transactional注解原理)

2.5.1 @Transactional注解原理

在这里插入图片描述

  • 这个行为,可能你也意识到了,其实就是AOP,对@Transactional注解下的代码,进行统一的处理
    • 当然,对于不同的事务/复杂事务,处理可能不同~
  • 这个在执行日志中也能看到,可以平时观察观察~

@Transactional 实现思路图:

在这里插入图片描述

@Transactionl执行思路图:

在这里插入图片描述

默认就是这么一个事务管理器执行这样的逻辑

  • 而如果配置了多个事务管理器,则需要通过参数value/transactionManager去指定

2.5.2 为什么必须被五大类注解修饰

其实就是因为

@Transactional注解是基于Spring AOP的,而Spring AOP则通过JDK的或者CGLib的动态代理来实现AOP

对于使用@Transactional注解来实现事务管理,确实是通过动态代理来实现的

  • 当你在一个类或方法上添加了@Transactional注解时,Spring会通过动态代理在运行时为该类或方法创建一个代理对象。这个代理对象会拦截调用,并在适当的时机开启、提交或回滚事务

由于动态代理的实现方式,确实需要满足一些条件才能使@Transactional注解生效

  • 具体来说,被注解的类或方法必须是Spring容器中的bean,而Spring容器会自动为标注了@Service@Controller@Repository@Component@Configuration等注解的类创建bean实例。这也是为什么我之前提到了五大类注解

2.5.3 为什么@Transactional不支持static方法

其实就是因为

无论JDK还是CGlib都无法对静态方法提供代理。原因在于静态方法是类级别的,调用需要知道类信息,而类信息在编译器就已经知道了,并**不支持在运行期的动态绑定**

3. 实践

3.1 创建项目

为了方便,我就直接使用之前mybatis项目里写过的代码了

  • 因为我们目前侧重学习的点是在事务的实现!

model.UserInfo:

@Component
@Data
public class UserInfo {private int id;private String username;private String password;private String photo;private LocalDateTime createtime;private LocalDateTime updatetime;private Integer state;public UserInfo(String username, String password, Integer state) {this.username = username;this.password = password;this.state = state;}public UserInfo() {}
}

mapper.UserMapper:

@Mapper
//跟五大类注解@Repository,say 拜拜
public interface UserMapper {List<UserInfo> getAll(); //获得所有用户信息UserInfo getUserById(Integer id);//通过id查找用户UserInfo getUserByUsername(@Param("username") String username);//通过username查找用户List<UserInfo> getAll2(@Param("option") String option);UserInfo login(@Param("username") String username, @Param("password") String password);int update(UserInfo userInfo);//删除状态为state的用户int delete(@Param("state") Integer state);//增加用户int insert(UserInfo userInfo);List<UserInfo> getAllLikeSome(@Param("likeString") String likeString);//用户注册提交信息
//    int add(UserInfo userInfo);int add(String username, String password, Integer state, Integer id);int add2(UserInfo userInfo);List<UserInfo> select1(UserInfo userInfo);int update2(UserInfo userInfo);int deleteByIDs(List<Integer> list);int insertByUsers(List<UserInfo> list, List<UserInfo> list2);}

mybatis.UserInfoMapper.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.example.demo.mapper.UserMapper"><resultMap id="BaseMap" type="com.example.demo.model.UserInfo"></resultMap><select id="getAll" resultMap="BaseMap">select * from userinfo</select><!--    <select id="getAll" resultType="com.example.demo.model.UserInfo">-->
<!--        select id, username as name, password, photo,-->
<!--            createtime, updatetime, state from userinfo-->
<!--    </select>--><select id="getUserById" resultType="com.example.demo.model.UserInfo">select * from userinfo where id = #{id}</select><select id="getUserByUsername" resultType="com.example.demo.model.UserInfo">select * from userinfo where username = ${username}</select><select id="getAll2" resultType="com.example.demo.model.UserInfo">select * from userinfo order by id ${option}</select><select id="login" resultType="com.example.demo.model.UserInfo">select * from userinfo where username = '${username}'and password = '${password}'</select><update id="update">update userinfo set state = #{state} where username = #{username}</update><delete id="delete">delete from userinfo where state = #{state}</delete><insert id="insert" useGeneratedKeys="true"keyColumn="id" keyProperty="id">
<!--    自增主键 id 不能为null也没有默认值,如果id不设置或者设置为null,都会导致自增    -->insert into userinfo (username, password) values (#{username}, #{password});</insert><select id="getAllLikeSome" resultType="com.example.demo.model.UserInfo">select * from userinfo where username like concat('%', #{likeString}, '%')</select><insert id="add">insert into userinfo (<if test="id != 0">id,</if><if test="username != null">username,</if><if test="password != null">password,</if><if test="state != null">state</if>) values (<if test="id != 0">#{id},</if><if test="username != null">#{username},</if><if test="password != null">#{password},</if><if test="state != null">#{state}</if>)</insert><insert id="add2">insert into userinfo<trim prefix="(" suffix=")"suffixOverrides=","><if test="id != 0">id,</if><if test="username != null">username,</if><if test="password != null">password,</if><if test="state != null">state</if></trim>values<trim prefix="(" suffix=")"suffixOverrides=","><if test="id != 0">#{id},</if><if test="username != null">#{username},</if><if test="password != null">#{password},</if><if test="state != null">#{state}</if></trim></insert><select id="select1" resultType="com.example.demo.model.UserInfo">select * from userinfo<where><if test="id != 0">id = #{id}</if><if test="username != null">or username = #{username}</if><if test="password != null">or password = #{password}</if><if test="state != null">or state = #{state}</if></where>
<!--        <trim prefix="where" prefixOverrides="and">-->
<!--            <trim prefixOverrides="or">-->
<!--                <if test="id != 0">-->
<!--                    id = #{id}-->
<!--                </if>-->
<!--                <if test="username != null">-->
<!--                    or username = #{username}-->
<!--                </if>-->
<!--                <if test="password != null">-->
<!--                    or password = #{password}-->
<!--                </if>-->
<!--                <if test="state != null">-->
<!--                    or state = #{state}-->
<!--                </if>-->
<!--            </trim>-->
<!--        </trim>--></select><update id="update2">update userinfo<set><if test="username != null">username = #{username},</if><if test="password != null">password = #{password},</if><if test="state != null">state = #{state}</if></set>where id = #{id}</update><delete id="deleteByIDs">delete from userinfo where id in<foreach collection="list" open="(" close=")" item="x" separator=",">#{x}</foreach></delete><insert id="insertByUsers">insert into userinfo(username, password, state) values<foreach collection="list" item="x" open="(" close=")" separator="),(">#{x.username}, #{x.password}, #{x.state}</foreach>,<foreach collection="list2" item="x" open="(" close=")" separator="),(">#{x.username}, #{x.password}, #{x.state}</foreach></insert>
</mapper>

application.properties:

spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test_db?characterEncoding=utf8
# MyBatis 基于jdbc实现~ 底层用的就是jdbc:mysql协议,这个地址是本地数据库的地址,test_db就是我们的那个数据库
spring.datasource.username=root
# 用户名,默认固定是root
spring.datasource.password=mmsszsd666
# 密码,是数据库的密码
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver# MyBatis配置信息
mybatis.mapper-locations=classpath:mybatis/*Mapper.xml#  执行时打印SQL
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#由于其默认情况下的日志类型为Debug,重要程度不高,所以我们需要设置我们对应的目录下的日志级别
logging.level.com.example.demo.controller=debug#将数据库中的下换线转换成驼峰,比如 user_name -> userName
mybatis-plus.configuration.map-underscore-to-camel-case=true

目录结构:

在这里插入图片描述

3.2 编写代码

同样的controller接受请求,service调用方法~

在这里插入图片描述

在这里插入图片描述

加@Transactional:

在这里插入图片描述

3.3 测试

在这里插入图片描述

访问路由前:

delete from userinfo;

现在userinfo一条数据都没有了~

效果:

浏览器:

在这里插入图片描述

控制台:

在这里插入图片描述

数据库:

在这里插入图片描述

  • 符合预期:还是空的
    • 因为发生了因为@Transactional捕获到了异常,发生回滚

在这里插入图片描述

去这段代码后,效果:

在这里插入图片描述

在这里插入图片描述

3.4 注意事项

@Transactional 在异常被 try{}catch(){} 捕获的情况下,不会进行事务自动回滚,这也很好理解,因为 try{}catch(){} 后,后面的代码可以继续运行,这个异常是被我们写的 try{}catch(){} 抢走处理了,注解是捕获不到的~

代码:

在这里插入图片描述

效果:

浏览器:

在这里插入图片描述

控制台:

在这里插入图片描述

数据库:

在这里插入图片描述

说明没有回滚

3.4.1 事务不会自动回滚解决方案1:重新抛出

在这里插入图片描述

效果:

在这里插入图片描述

在这里插入图片描述

无新增数据,代表回滚成功

但是这不太美观,“优雅”,过于暴力

3.4.2 事务不会自动回滚解决方案2:手动回滚

TransactionAspectSupport.currentTransactionStatus() 可以得到当前的事务,然后设置回滚方法setRollbackOnly就可以实现将当前事务的回滚了

  • 跟切面有关=>aop

在这里插入图片描述

效果:

在这里插入图片描述

在这里插入图片描述

无新增数据,代表回滚成功

这种方式就比较“优雅”了~


文章到此结束!谢谢观看
可以叫我 小马,我可能写的不好或者有错误,但是一起加油鸭🦆

代码:事务/src/main · 游离态/马拉圈2023年8月 - 码云 - 开源中国 (gitee.com)


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

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

相关文章

【线程池】ThreadPoolExecutor的使用示例

文章目录 通过ThreadPoolExecutor创建线程池。线程的处理结果如何获取&#xff1f; 通过ThreadPoolExecutor创建线程池。 ThreadPoolExecutor构造方法参数&#xff1a; int corePoolSize //核心线程数量int maximumPoolSize//最大线程数long keepAliveTime//当线程数大于核心…

【无线点对点网络时延分析和可视化】模拟无线点对点网络中的延迟以及物理层和数据链路层之间的相互作用(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Win解答 | 解决键盘中 字母+空格 导致的输入法弹窗导致的一系列问题

近三个月来&#xff0c;一直都有一个键盘组合键的问题影响我的电脑使用&#xff0c;不管是打字还是打游戏&#xff0c;都会出现按键盘的 字母空格 弹出一个特殊符号的候选框&#xff0c;如下图所示 图片中为 S空格 所出现的弹窗 一个看似方便&#xff0c;实则难受的功能 其实打…

SpringBoot入门篇2 - 配置文件格式、多环境开发、配置文件分类

目录 1.配置文件格式&#xff08;3种&#xff09; 例&#xff1a;修改服务器端口。&#xff08;3种&#xff09; src/main/resources/application.properties server.port80 src/main/resources/application.yml&#xff08;主要用这种&#xff09; server:port: 80 src/m…

[Go版]算法通关村第十四关白银——堆高效解决的经典问题(在数组找第K大的元素、堆排序、合并K个排序链表)

目录 题目&#xff1a;在数组中找第K大的元素解法1&#xff1a;维护长度为k的最小堆&#xff0c;遍历n-k个元素&#xff0c;逐一和堆顶值对比后&#xff0c;和堆顶交换&#xff0c;最后返回堆顶复杂度&#xff1a;时间复杂度 O ( k ( n − k ) l o g k ) O(k(n-k)logk) O(k(n−…

CSS实现内凹圆角,从而实现圆角边框

1、代码 <!DOCTYPE html> <html><head><style>.uu {position: relative;width: 400px;height: 300px;}img {width: 100%;height: 100%;z-index: 1;}.box_right_top {background-image: radial-gradient(circle at left bottom, transparent 50px, whi…

统信OS国产操作系统身份证读卡器社保卡读卡web网页开发使用操作流程

用于DONSEE系列身份证阅读器谷歌Chrome火狐Firefox插件&#xff0c;支持的型号有&#xff1a;EST-100、EST-100GS、EST-100G、EST-100U、EST-200G、EST-J13X等。 本方案无缝支持最新版本谷歌Chrome火狐Firefox等网页浏览器&#xff0c;支持H5、Vue、React、Node.js、Electron、…

设计模式--单例模式(Singleton Pattern)

一、什么是单例模式 单例模式是一种创建型设计模式&#xff0c;它旨在确保一个类只有一个实例&#xff0c;并提供一个全局访问点来访问该实例。换句话说&#xff0c;单例模式限制了类的实例化次数为一个&#xff0c;并提供一种在应用程序中共享一个实例的方式。这对于需要只有…

Linux系统编程:线程控制

目录 一. 线程的创建 1.1 pthread_create函数 1.2 线程id的本质 二. 多线程中的异常和程序替换 2.1 多线程程序异常 2.2 多线程中的程序替换 三. 线程等待 四. 线程的终止和分离 4.1 线程函数return 4.2 线程取消 pthread_cancel 4.3 线程退出 pthread_exit 4.4 线程…

Day44|leetcode 518.零钱兑换II、377. 组合总和 Ⅳ

完全背包理论基础 视频链接&#xff1a;带你学透完全背包问题&#xff01; 和 01背包有什么差别&#xff1f;遍历顺序上有什么讲究&#xff1f;_哔哩哔哩_bilibili 完全背包与01背包不同的地方就是&#xff1a;01背包每种物品只能取一次&#xff0c;而完全背包每种物品可以取…

2023年国赛数学建模思路 - 案例:粒子群算法

文章目录 1 什么是粒子群算法&#xff1f;2 举个例子3 还是一个例子算法流程算法实现建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是粒子群算法&#xff1f; 粒子群算法&#xff08;Pa…

yolov5中添加ShuffleAttention注意力机制

ShuffleAttention注意力机制简介 关于ShuffleAttention注意力机制的原理这里不再详细解释.论文参考如下链接here   yolov5中添加注意力机制 注意力机制分为接收通道数和不接受通道数两种。这次属于接受通道数注意力机制,这种注意力机制由于有通道数要求,所示我们添加的时候…

学习JAVA打卡第四十四天

Scanner类 ⑴Scanner对象 scanner对象可以解析字符序列中的单词。 例如&#xff1a;对于string对象NBA 为了解析出NBA的字符序列中的单词&#xff0c;可以如下构造一个scanner对象。 将正则表达式作为分隔标记&#xff0c;即让scanner对象在解析操作时把与正则表达式匹配的字…

【Linux】多线程概念线程控制

文章目录 多线程概念Linux下进程和线程的关系pid本质上是轻量级进程id&#xff0c;换句话说&#xff0c;就是线程IDLinux内核是如何创建一个线程的线程的共享和独有线程的优缺点 线程控制POSIX线程库线程创建线程终止线程等待线程分离 多线程概念 Linux下进程和线程的关系 在…

使用StreamLold写入 Starrocks报错:Caused by org

问题描述 使用StreamLoad写入Starrocks报错&#xff0c;报这个错误:Caused by: org.apache.http.ProtocolException: Content-Length header already present 代码案例 引入依赖 <!-- Starrocks使用StreamLoad发送Http请求 --><dependency><groupId>or…

系统架构设计高级技能 · 面向服务架构设计理论与实践

系列文章目录 系统架构设计高级技能 软件架构概念、架构风格、ABSD、架构复用、DSSA&#xff08;一&#xff09;【系统架构设计师】 系统架构设计高级技能 系统质量属性与架构评估&#xff08;二&#xff09;【系统架构设计师】 系统架构设计高级技能 软件可靠性分析与设计…

【自动驾驶】TI SK-TDA4VM 开发板上电调试,AI Demo运行

1. 设备清单 TDA4VM Edge AI 入门套件【略】USB 摄像头(任何符合 V4L2 标准的 1MP/2MP 摄像头,例如:罗技 C270/C920/C922)全高清 eDP/HDMI 显示屏最低 16GB 高性能 SD 卡连接到互联网的 100Base-T 以太网电缆【略】UART电缆外部电源或电源附件要求: 标称输出电压:5-20VDC…

Jmeter 接口测试总结

背景介绍 对于 Android 项目来说&#xff0c;使用的是 Java 开发&#xff0c;网络请求接口的数量庞大且复杂&#xff0c;测试人员无法很直观的判断、得出网络请求是否存在问题。另一方面&#xff0c;为了验证请求接口是否能够在大负荷条件下&#xff0c;长时间、稳定、正常的运…

【JMeter】常用线程组设置策略

目录 一、前言 二、单场景基准测试 1.介绍 2.线程组设计 3.测试结果 三、单场景并发测试 1.介绍 2.线程组设计 3.测试结果 四、单场景容量/爬坡测试 1.介绍 2.线程组设计 3.测试结果 五、混合场景容量/并发测试 1.介绍 六、稳定性测试 1.介绍 2.线程组设计 …

C++中的const成员变量和成员函数

在类中&#xff0c;如果你不希望某些数据被修改&#xff0c;可以使用const关键字加以限定。const 可以用来修饰成员变量和成员函数。 const成员变量 const 成员变量的用法和普通 const 变量的用法相似&#xff0c;只需要在声明时加上 const 关键字。初始化 const 成员变量只有…