SpringBoot 项目的方法名是否添加@Transactional注解,以及SQL语句(SQLServer数据库)是否添加SET NOCOUNT ON时的备忘

news/2024/10/18 16:32:58/文章来源:https://www.cnblogs.com/passacaglia/p/18474306

项目改用Spring Data JDBC 并手动配置DataSource之后,@Transactional注解一直不起作用。这两天研究了一下,注解不起作用,主要是没有配置 TransactionManager 的事,配置完 TransactionManager 之后,@Transactional注解就起作用了。

但是配置完又发现,用jdbcTemplate.queryForList()方法执行SQL代码时,能够在遇到RAISERROR()的时候回滚,但用jdbcTemplate.update()/execute()方法时,却不能。
搜索了一阵,大概总结一下就是,queryForList被设计为取得一个ResultSet,如果遇到RAISERROR(),那么方法会抛出异常,然后Spring遇到这个unchecked exception(RuntimeException),就会回滚数据。
而execute更通用,而且并不期望获得一个 ResultSet,遇到RAISERROR() 的时候,不需要立即抛出异常,特别是在已经成功执行某些代码之后了。Spring没看到有异常,也就不需要回滚数据。

那么,如何在使用execute方法,同时遇到RAISERROR()时,让数据也能回滚呢?尝试了一下一篇问答里提到的一种方法,就是在SQL语句开头中添加SET NOCOUNT ON; 就可以了,想想原来是跟之前操作返回多数据集一个做法。😊

测试执行结果备忘(方法是否添加@Transactional注解和SQL语句是否添加SET NOCOUNT ON;的各种情况下)

  1. 方法上不添加@Transactional注解,同时SQL语句不添加SET NOCOUNT ON; SET NOCOUNT OFF;
    1. 如果SQL语句有语法错误,用queryForList/execute/update方法时,那么整段SQL(sqlWithErrors)都会运行失败。其他queryForList/execute/update调用的SQL(sqlA)可以正常执行。
    2. 如果SQL语句没有语法错误,语句运行到RAISERROR(),用queryForList方法时,能捕获exception,但是RAISERROR前后的代码都会正常执行(sqlWithErrors & sqlA)(RAISERROR似乎没啥用了)。【有问题,但是凑合,毕竟有异常抛出】
    3. 如果SQL语句没有语法错误,语句运行到RAISERROR(),用execute/update方法时,不能捕获exception,RAISERROR前后的代码都会正常执行(sqlWithErrors & sqlA),因为不会抛出任何异常,系统正常返回。【非常严重的问题】
  2. 方法上不添加@Transactional注解,但SQL语句添加了SET NOCOUNT ON; SET NOCOUNT OFF;
    1. 如果SQL语句有语法错误,用queryForList/execute/update方法时,那么整段SQL(sqlWithErrors)都会运行失败。其他queryForList/execute/update调用的SQL(sqlA)可以正常执行。
    2. 如果SQL语句没有语法错误,语句运行到RAISERROR(),用queryForList/update/execute方法时,能捕获exception,但是RAISERROR前后的代码都会正常执行(sqlWithErrors & sqlA)(RAISERROR似乎没啥用了,但好处是还能捕获exception,比前面的强一点点)。
  3. 方法上添加了@Transactional注解,同时SQL语句不添加SET NOCOUNT ON; SET NOCOUNT OFF;
    1. 如果SQL语句有语法错误,用queryForList/execute/update方法时,那么整段SQL(sqlWithErrors)都会运行失败。其他queryForList/execute/update调用的SQL(sqlA)即使成功也会回滚。
    2. 如果SQL语句没有语法错误,语句运行到RAISERROR(),用queryForList方法时,能捕获exception,同时整个方法内的所有数据库操作都会回滚。(sqlWithErrors & sqlA)【勉强可用,但是不能返回多数据集】
    3. 如果SQL语句没有语法错误,语句运行到RAISERROR(),用execute/update方法时,不能捕获exception,RAISERROR前后的代码都会正常执行,因为不会抛出任何异常,系统正常返回。(sqlWithErrors & sqlA)【非常严重的问题】
  4. 方法上添加了@Transactional注解,同时SQL语句添加SET NOCOUNT ON; SET NOCOUNT OFF;
    1. 如果SQL语句有语法错误,用queryForList/execute/update方法时,那么整段SQL(sqlWithErrors)都会运行失败。其他queryForList/execute/update调用的SQL(sqlA)即使成功也会回滚。
    2. 如果SQL语句没有语法错误,语句运行到RAISERROR(),用queryForList/execute/update方法时,能捕获exception,同时整个方法内的所有数据库操作都会回滚。(sqlWithErrors & sqlA)【可用,期待的结果】

希望相同功能的两个方法,一个启用事务,另一个不启用事务

如果希望有两个相同的方法,一个启用了事务,另外一个不启用事务,那么可以考虑在启用了事务的方法里面,直接调用不启用事务的方法,到时候按需调用就可以了。

参考资料备忘

  • When we execute a stored procedure in JDBC we get back a series of zero or more "results". We can then process those "results" sequentially by calling CallableStatement#getMoreResults(). Each "result" can contain
    • zero or more rows of data that we can retrieve with a ResultSet object,
    • an update count for a DML statement (INSERT, UPDATE, DELETE) that we can retrieve with CallableStatement#getUpdateCount(), or
    • an error that throws an SQLServerException.
      For "Issue 1" the problem is often that the stored procedure does not begin with SET NOCOUNT ON; and executes a DML statement before doing a SELECT to produce a result set. The update count for the DML is returned as the first "result", and the data rows are "stuck behind it" until we call getMoreResults.

"Issue 2" is essentially same problem. The stored procedure produces a "result" (usually a SELECT, or possibly an update count) before the error occurs. The error is returned in a subsequent "result" and does not cause an exception until we "retrieve" it using getMoreResults.

In many cases the problem can be avoided by simply adding SET NOCOUNT ON; as the first executable statement in the stored procedure. However, a change to the stored procedure is not always possible and the fact remains that in order to get everything back from the stored procedure we need to keep calling getMoreResults until, as the Javadoc says:

There are no more results when the following is true:

 // stmt is a Statement object((stmt.getMoreResults() == false) && (stmt.getUpdateCount() == -1))

That sounds simple enough but as usual, "the devil is in the details", as illustrated by the following example. For a SQL Server stored procedure ...


点击查看代码
   @Transactional(value = "myTransactionManager")public void testMethodWithTran() {testMethod();}public void testMethod() {var sqlA = """INSERT INTO dbo.TestTable (UID, Content, CreateAT, Sec)SELECT NEWID(), N'DataA', GETDATE(), N'DataA query section'select 1;""";jdbcTemplate.queryForList(sqlA);var sqlWithError = """SET NOCOUNT ON;INSERT INTO dbo.TestTable (UID, Content, CreateAT, Sec)SELECT NEWID(), N'DataB', GETDATE(), N'DataB SECTION'-- Bad SQL Grammar--INSERT INTO dbo.TestTable (UID, Content, CreateAT, Sec)--SELECT NEWID(), N'DataB2/*'*/, GETDATE(), N'DataB SQL ERROR SECTION'RAISERROR(N'Raised Error After DataB and before DataC', 16, 1);INSERT INTO dbo.TestTable (UID, Content, CreateAT, Sec)SELECT NEWID(), N'DataC', GETDATE(), N'DataC SECTION'SET NOCOUNT OFF;""";jdbcTemplate.execute(sqlWithError);}

参考资料

  1. No Exception thrown for obvious error
    https://github.com/Microsoft/mssql-jdbc/issues/826
  2. How to get everything back from a stored procedure using JDBC
    https://stackoverflow.com/questions/42169951/how-to-get-everything-back-from-a-stored-procedure-using-jdbc/42169952#42169952

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

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

相关文章

20222408 2024-2025-1 《网络与系统攻防技术》实验二实验报告

1.实验内容 1.1本周学习内容 本次实验中,学习的重点是后门的实现与启动方式,学习内容还有后门的定义、原理以及可能影响,netcat、socat、MSF meterpreter软件的应用。 1.2实验内容简述使用netcat获取主机操作Shell,利用cron启动一项任务 使用socat获取主机操作Shell, 利用创…

京东APP百亿级商品与车关系数据检索实践

作者:京东零售 张强导读 本文主要讲解了京东百亿级商品车型适配数据存储结构设计以及怎样实现适配接口的高性能查询。通过京东百亿级数据缓存架构设计实践案例,简单剖析了jimdb的位图(bitmap)函数和lua脚本应用在高性能场景。希望通过本文,读者可以对缓存的内部结构知识有一…

专题(二十) cut

一、作用与介绍cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符、字段写至标准输出。 二、用法选项 用法说明 举例说明 备注-b 按字节截取 who | cut -b 3 输出每行的第三个字节-c 按字符截取,常用于中文 cut -c 2 输出每行的第二个中文字符-d 指定以什么为…

【DevExpress】(多行粘贴、块粘贴)

复制是GridControl自带的属性,主要解决的是多个单元格复制的问题,这里涉及到两个参数。 主要是粘贴的 先定义两个全局变量,在单元格点击事件的时候获取单元格的行号和列号1 //获取当前选中单元格所在的列序号2 int curntindex;3 //获取获取当前选中单元格所在的行…

Jenkins+Coverage的代码覆盖率集成实践

Jenkins+Coverage的代码覆盖率集成实践 一、工具介绍Jenkins: Jenkins是一个开源的、基于Java开发的持续集成工具,它可以帮助开发人员自动化构建、测试和部署软件项目。 Coverage: Coverage是一个Python代码覆盖率工具,用于测量代码执行过程中哪些代码行被执行到,从而评估…

C++顺序结构(3)、数据类型_____教学

一、设置域宽setw() 输出的内容所占的总宽度成为域宽,有些高级语言中称为场宽。 使用setw()前,必须包含头文件iomanip,即#include<iomanip> 头文件iomanip,用来声明一些“流操作符”,需要一定格式输入输出时,就需要用到它,比较常用的有设置域宽、设置左右对齐、设置…

OpenCity: Open Spatio-Temporal Foundation Models for Traffic Prediction

1. 数据准备 在这个数据处理过程中,以数据集 PEMS07M 为例,整个数据抽取和划分过程如下:初始数据维度:原始训练数据 data_train 的维度为 (12672, 228, 3)。其中:12672 表示时间步数,代表不同的时间点采样的数据。 228 表示空间节点数(例如不同的交通站点)。 3 表示每个…

04C++顺序结构(3)

1、设置域宽setw(); 2、cin语句; 3、输入输出.格式化一、设置域宽setw() 输出的内容所占的总宽度成为域宽,有些高级语言中称为场宽。 使用setw()前,必须包含头文件iomanip,即#include<iomanip> 头文件iomanip,用来声明一些“流操作符”,需要一定格式输入输出时,就…

轻松上手-MVVM模式_关系型数据库_云函数T云数据库

作者:狼哥 团队:坚果派 团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁…

九宫格自由流转拼图游戏

作者:狼哥 团队:坚果派 团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁…

数据预处理-DataFrame切片

此Blog仅作为日常学习工作中记录使用,Blog中有不足之处欢迎指出 以kaggle中房屋预测的训练集为例,说明DataFrame切片常用操作 一、读入数据 import numpy as np import pandas as pdfile_path = ***\kaggle_house_pred_train.csv data = pd.read_csv(file_path)data.columns …

轻松上手-Navigation路由 H5

作者:狼哥 团队:坚果派 团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁…