【JavaEE】Spring事务-事务的基本介绍-事务的实现-@Transactional基本介绍和使用
【JavaEE】Spring 事务(1)
文章目录
- 【JavaEE】Spring 事务(1)
- 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 事务(1)
1. 为什么要使用事务
比如跟钱相关的两个操作:
第一步操作:小马卡里 - 100元
第二步操作:老马卡里 + 100元
这就是一个事务,捆在一起的一组行为,就是事务
![]()
而它能保证的是,这个行为的原子性,一致性,隔离性,持久性:
- 两个操作都成功
- 两个操作都失败
要么一起成功,要么一起失败
但是,如果没有事务呢,则两个操作逻辑上是分开的:
- 第一个操作成功,第二个操作失败,则小马直接亏了100!
2. Spring中事务的实现
Spring中的事务操作主要分为两类:
- 编程式事务(原生方式去写代码操作事务)
- 声明式事务(利用注解,“约定规则”去自动开启和提交事务)
2.1 事务针对哪些操作
事务一般针对的是
- 持久化相关的操作,如数据库操作、文件系统操作等
正如刚才的那样,两个用户的转账操作
- 保证数据完整性的操作,如消息队列等
通过使用事务,可以在消息队列中提供可靠的消息传递机制,减少消息丢失或重复处理的可能性,同时确保系统在出现故障情况下能够正确恢复
事务的概念适用于需要保证一系列操作的原子性和一致性的任何场景
- 而其中,被持久化的数据,被传播的数据…等操作,都具有 “持久性影响” 的作用,所以要通过事务来控制其影响不要太糟糕
- 而一些操作,比如打印,都打印到控制台了,不会回滚的,也没有必要回滚,例如查看执行日志…
- 至于其他的不可见的操作,又没有持久化,是没有影响力的,程序出异常后,这些数据也销毁了~
2.2 MySQL 事务使用
--- 开启事务
start transaction;
--- transaction就是事务的意思--- 提交事务
commit;--- 回滚事务
rollback;
三个重要的操作:
- 开启事务
- 提交事务
- 回滚事务
2.3 Spring 编程式事务(手动挡)
与MySQL操作事务类似:
- 开启事务(获取一个事务/创建一个事务并获取)
- 提交事务
- 回滚事务
SpringBoot 内置了两个对象:
- DataSourceTransactionManager ⽤来获取事务(开启事务)、提交或 回滚事务的
- 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 注解 就可以实现了
无需手动开启/提交/回滚事务:
- 进入方法,自动开启事务
- 方法执行完会,自动提交事务
- 如果中途发生了没有处理的异常,自动回滚事务
具体规则/作用范围是:
- 加在类上,内部的所有非静态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;
}
跟往常的注解版和不使用注解版的代码一样:
- 不使用注解版: 灵活,能实现很多功能,但是麻烦,使用困难,甚至正常人压根没法写,例如事务传播机制的代码实现起来就比较复杂
- 使用注解版: 使用规则约束,实现特定功能,但是方便,使用简单,且足以面对正常开发环境,不关心一些极端的不正常开发
- 对于注解的使用,就是:遵循约定,坐享其成,明白逻辑(作用),合理使用(逻辑分析合理)
编程式就相当于车的手动挡,声明式就相当于车的自动挡,那么现实咱们买不起偏贵的自动挡车,而我们现在可以无条件舒适地使用自动挡,那咋不用嘞🤣🤣🤣
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)
相关文章:
【JavaEE】Spring事务-事务的基本介绍-事务的实现-@Transactional基本介绍和使用
【JavaEE】Spring 事务(1) 文章目录 【JavaEE】Spring 事务(1)1. 为什么要使用事务2. Spring中事务的实现2.1 事务针对哪些操作2.2 MySQL 事务使用2.3 Spring 编程式事务(手动挡)2.4 Spring 声明式事务&…...
CentOs下面安装jenkins记录
目录 一、安装jenkins 二、进入jenkins 三、安装和Gitee,Maven , Publish Over SSH等插件 四、构建一个maven项目 一、安装jenkins 1 wget -O /etc/yum.repos.d/jenkins.repo \ https://pkg.jenkins.io/redhat-stable/jenkins.repo 2 rpm --im…...
海康威视相机-LINUX SDK 开发
硬件与环境 相机: MV-CS020-10GC 系统:UBUNTU 22.04 语言:C 工具:cmake 海康官网下载SDK 运行下面的命令进行安装 sudo dpkg -i MVSXXX.deb安装完成后从在/opt/MVS 路径下就有了相关的库,实际上我们开发的时候只需要…...
AI助力智能安检,基于图像目标检测实现危险品X光智能安全检测系统
基于AI相关的技术来对一些重复性的但是又比较重要的工作来做智能化助力是一个非常有潜力的场景,关于这方面的项目开发实践在我之前的文章中也有不少的实践,感兴趣的话可以自行移步阅读即可:《AI助力智能安检,基于目标检测模型实现…...
开源软件的崛起:历史与未来
🌷🍁 博主猫头虎 带您 Go to New World.✨🍁 🦄 博客首页——猫头虎的博客🎐 🐳《面试题大全专栏》 文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺 &a…...
apk 静默安装
apk 静默安装 - 欧颜柳 - 博客园 (cnblogs.com) 如果需要应用进行静默安装,则需要满足一下两个条件 1 必须添加权限 <uses-permission android:name"android.permission.INSTALL_PACKAGES" /> 2 必须是系统应用,或者系统签名应用 方法…...
Unity记录4.2-存储-从json文件获取Tile路径
文章首发见博客:https://mwhls.top/4813.html。 无图/格式错误/后续更新请见首发页。 更多更新请到mwhls.top查看 欢迎留言提问或批评建议,私信不回。 汇总:Unity 记录 摘要:从json文件获取Tile材质路径。 确定保存方案-2023/08/1…...
vue3页面传参?
...
NB水表和LoRa水表有哪些不同之处?
NB水表和LoRa水表是两种目前市场上常见的智能水表,它们在功能、性能、应用场景等方面存在一些不同之处。 一、技术方面 NB水表采用NB-IoT技术,而LoRa水表采用LoRa技术。NB-IoT技术是窄带物联网技术,它具有良好的低功耗、低成本、高覆盖、高可…...
Java进阶(6)——抢购问题中的数据不安全(非原子性问题) Java中的synchronize和ReentrantLock锁使用 死锁及其产生的条件
目录 引出场景:大量请求拥挤抢购事务的基本特征ACID线程安全的基本特征 加锁(java)synchronized锁ReentrantLock锁什么是可重入锁?如何保证可重入 滥用锁的代价?(死锁)死锁的四个必要条件死锁的案例 总结 引出 1.大量请…...
SpringBoot初级开发--加入Log4j进行日志管理打印(6)
日志记录在整个java工程开发中占着很重要的比重,因为很多问题的排查需要通过日志分析才能确认。在SpringBoot中我用得最多的就是log4j这个日志框架。接下来我们具体配置log4j. log4j定义了8个级别的log(除去OFF和ALL,可以说分为6个级别&#…...
计算机竞赛 基于GRU的 电影评论情感分析 - python 深度学习 情感分类
文章目录 1 前言1.1 项目介绍 2 情感分类介绍3 数据集4 实现4.1 数据预处理4.2 构建网络4.3 训练模型4.4 模型评估4.5 模型预测 5 最后 1 前言 🔥 优质竞赛项目系列,今天要分享的是 基于GRU的 电影评论情感分析 该项目较为新颖,适合作为竞…...
android logcat问题 怎么换成旧版
参考 如果想切换回旧版LOGCAT,按照下方步骤设置即可 File->Settings->Expermental->Logcat->Enable new Logcat tool window:取消勾选 设置好后上方会有一个Toast,询问你是否使用新版logcat,关掉即可 最新测试版移…...
监听的用法watch
1、当想停止某页面定时刷新(监听路由的变化) /**组件被移除时调用 */deactivated() {clearInterval(this.timer);this.timer null;},/**监听路由变化是否刷新 */watch: {// 方法1 //监听路由是否变化$route(to, from) {if (to.name "xxx") {…...
XML—标记语言
什么是XML? Extensible Markup Language,可扩展标记语言。 那标记语言是什么? 用文字做标记表达一些效果或携带一些数据。比如:HTML、XML 我的理解:用倾盆大雨表达雨很大 那XML为什么说是可扩展的呢? 还…...
图数据库Neo4j学习五渲染图数据库neo4jd3
文章目录 1.现成的工具2.Neo4j JavaScript Driver3.neovis4.neo4jd34.1neo4jd3和neovis对比4.2获取neo4jd34.3neo4jd3的数据结构4.4Spring data neo4.4.1 定义返回数据格式4.4.1.1NeoResults4.4.1.2GraphVO4.4.1.3NodeVO4.4.1.4ShipVO 4.4.2 SDN查询解析4.4.2.1 Repo查询语句4.…...
AI增强的社交网络·导师·电话客服……
本月共更新80条知识, 智能时代,人与人之间的差距,体现在前沿知识的整合上。 # BeFake AI AI-augmented social network AI增强的社交网络,用户使用文本提示来生成图像,拍摄自己的“AI”版本。任何人都可以创建全新的虚…...
c# Task异步使用
描述 Task出现之前,微软的多线程处理方式有:Thread→ThreadPool→委托的异步调用,虽然可以满足基本业务场景,但它们在多个线程的等待处理方面、资源占用方面、延续和阻塞方面都显得比较笨拙,在面对复杂的业务场景下&am…...
QuickLook概述和使用以及常用插件
1、QuickLook概述 QuickLook: 是可以快速预览的工具,开源、免费。通过空格键即可快速查看文件内容。 文件无需打开就可以用QuickLook一键快速预览。 说明文档:https://en.wikipedia.org/wiki/Quick_Look github地址:https://git…...
1A快恢复整流二极管型号汇总
快恢复整流二极管是二极管中的一种,开关特性好、反向恢复时间短,在开关电源、PWM脉宽调制器、变频器等电子电路中经常能看到它的身影。快恢复整流二极管的内部结构与普通PN结二极管不同,它属于PIN结型二极管,即在P型硅材料与N型硅…...
【Axure高保真原型】引导弹窗
今天和大家中分享引导弹窗的原型模板,载入页面后,会显示引导弹窗,适用于引导用户使用页面,点击完成后,会显示下一个引导弹窗,直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
DeepSeek 赋能智慧能源:微电网优化调度的智能革新路径
目录 一、智慧能源微电网优化调度概述1.1 智慧能源微电网概念1.2 优化调度的重要性1.3 目前面临的挑战 二、DeepSeek 技术探秘2.1 DeepSeek 技术原理2.2 DeepSeek 独特优势2.3 DeepSeek 在 AI 领域地位 三、DeepSeek 在微电网优化调度中的应用剖析3.1 数据处理与分析3.2 预测与…...
条件运算符
C中的三目运算符(也称条件运算符,英文:ternary operator)是一种简洁的条件选择语句,语法如下: 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true,则整个表达式的结果为“表达式1”…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
【2025年】解决Burpsuite抓不到https包的问题
环境:windows11 burpsuite:2025.5 在抓取https网站时,burpsuite抓取不到https数据包,只显示: 解决该问题只需如下三个步骤: 1、浏览器中访问 http://burp 2、下载 CA certificate 证书 3、在设置--隐私与安全--…...
SpringBoot+uniapp 的 Champion 俱乐部微信小程序设计与实现,论文初版实现
摘要 本论文旨在设计并实现基于 SpringBoot 和 uniapp 的 Champion 俱乐部微信小程序,以满足俱乐部线上活动推广、会员管理、社交互动等需求。通过 SpringBoot 搭建后端服务,提供稳定高效的数据处理与业务逻辑支持;利用 uniapp 实现跨平台前…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
PAN/FPN
import torch import torch.nn as nn import torch.nn.functional as F import mathclass LowResQueryHighResKVAttention(nn.Module):"""方案 1: 低分辨率特征 (Query) 查询高分辨率特征 (Key, Value).输出分辨率与低分辨率输入相同。"""def __…...



















