SpringBoot3.2新特性:JdbcClient
文章目录
- 一、简介
- 二、使用
- 1、支持隐式位置参数
- 2、通过索引设置位置参数
- 3、支持 Name / Value 对命名参数
- 4、通过 Map 设置命名参数
- 5、使用 JdbClient 执行更新操作
- 6、使用示例
- 参考资料
一、简介
Spring 6.1
中新添加了 JdbcClient 接口,它提供了 Fluent 风格的 API,统一了 JdbcTemplate 和 NamedParameterJdbcTemplate 的 Facade,支持链式操作。
有了 JdbcClient 后就可以使用 Fluent 风格的 API 定义查询、设置参数以及执行数据库操作了。
该功能简化了 JDBC 操作,使其更易读、更易懂。然而,对于 JDBC 批处理操作和存储过程的调用,仍然需要使用 JdbcTemplate 和 NamedParameterJdbcTemplate 类。
SpringBoot3.2中也集成了JdbcClient ,配置也很简单,只要引入相关包,Spring Boot 框架会自动发现 application.properties 中的 DB 连接属性
,并在应用启动时创建 JdbcClient Bean。之后,JdbcClient Bean 可以在任何类中注入、使用。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
@Component
public class DbService {// 直接使用即可@Autowiredprivate JdbcClient jdbcClient;
}
二、使用
1、支持隐式位置参数
使用占位符 ?
来通过位置绑定 SQL 参数。
List<Student> getStudentsOfGradeStateAndGenderWithPositionalParams(int grade, String state, String gender) {String sql = "select student_id, student_name, age, grade, gender, state from student"+ " where grade = ? and state = ? and gender = ?";return jdbcClient.sql(sql).param(grade).param(state).param(gender).query(new StudentRowMapper()).list();
}@Test
void givenJdbcClient_whenQueryWithPositionalParams_thenSuccess() {List<Student> students = studentDao.getStudentsOfGradeStateAndGenderWithPositionalParams(1, "New York", "Male");assertEquals(6, students.size());
}
在上述方法中,参数 grade、state 和 gender 是按照方法 param() 的调用顺序隐式注册的。最后,当调用 query() 方法时,语句将被执行,并通过 RowMapper 转换、获取结果,和 JdbcTemplate 一样。
query() 方法还支持 ResultSetExtractor 和 RowCallbackHandler 参数。
在调用 list() 方法之前,不会检索到任何结果。此外,还支持其他获取结果的操作,如 optional()、set()、single() 和 stream()。
还可以通过 params 方法使用可变参数来设置 SQL 参数:
Student getStudentsOfGradeStateAndGenderWithParamsInVarargs(int grade, String state, String gender) {String sql = "select student_id, student_name, age, grade, gender, state from student"+ " where grade = ? and state = ? and gender = ? limit 1";return jdbcClient.sql(sql).params(grade, state, gender).query(new StudentRowMapper()).single();
}@Test
void givenJdbcClient_whenQueryWithParamsInVarargs_thenSuccess() {Student student = studentDao.getStudentsOfGradeStateAndGenderWithParamsInVarargs(1, "New York", "Male");assertNotNull(student);
}
如上所示,使用 params() 方法替换了 param() 方法,后者使用可变参数。最后通过 single() 方法来检索一条记录。
params() 方法还有一个重载版本,可以接收一个参数 List。
Optional<Student> getStudentsOfGradeStateAndGenderWithParamsInList(List params) {String sql = "select student_id, student_name, age, grade, gender, state from student"+ " where grade = ? and state = ? and gender = ? limit 1";return jdbcClient.sql(sql).params(params).query(new StudentRowMapper()).optional();
}@Test
void givenJdbcClient_whenQueryWithParamsInList_thenSuccess() {List params = List.of(1, "New York", "Male");Optional<Student> optional = studentDao.getStudentsOfGradeStateAndGenderWithParamsInList(params);if(optional.isPresent()) {assertNotNull(optional.get()); } else {assertThrows(NoSuchElementException.class, () -> optional.get());}
}
除了 params(List<?> values)
,这里还通过 optional() 方法返回了 Optional<Student>
对象。
2、通过索引设置位置参数
如果需要设置 SQL 语句参数的位置,可以使用 param(int jdbcIndex, Object value) 方法:
List<Student> getStudentsOfGradeStateAndGenderWithParamIndex(int grade, String state, String gender) {String sql = "select student_id, student_name, age, grade, gender, state from student"+ " where grade = ? and state = ? and gender = ?";return jdbcClient.sql(sql).param(1, grade).param(2, state).param(3, gender).query(new StudentResultExtractor());
}@Test
void givenJdbcClient_whenQueryWithParamsIndex_thenSuccess() {List<Student> students = studentDao.getStudentsOfGradeStateAndGenderWithParamIndex(1, "New York", "Male");assertEquals(6, students.size());
}
在该方法中,明确指定了参数的位置。此外,还使用了 query(ResultSetExtractor rse) 方法。
3、支持 Name / Value 对命名参数
JdbcClient 还支持使用占位符 : 绑定命名的 SQL 语句参数,这是 NamedParameterJdbcTemplate 的特性。
param() 方法也可以设置键值对类型的参数:
int getCountOfStudentsOfGradeStateAndGenderWithNamedParam(int grade, String state, String gender) {String sql = "select student_id, student_name, age, grade, gender, state from student"+ " where grade = :grade and state = :state and gender = :gender";RowCountCallbackHandler countCallbackHandler = new RowCountCallbackHandler();jdbcClient.sql(sql).param("grade", grade).param("state", state).param("gender", gender).query(countCallbackHandler);return countCallbackHandler.getRowCount();
}@Test
void givenJdbcClient_whenQueryWithNamedParam_thenSuccess() {Integer count = studentDao.getCountOfStudentsOfGradeStateAndGenderWithNamedParam(1, "New York", "Male");assertEquals(6, count);
}
在上述方法中,使用了命名参数。此外,还使用了 query(RowCallbackHandler rch) 方法来获取结果。
4、通过 Map 设置命名参数
也可以通过 params(Map<String,?> paramMap)
方法,使用 Map 对象来设置命名参数:
List<Student> getStudentsOfGradeStateAndGenderWithParamMap(Map<String, ?> paramMap) {String sql = "select student_id, student_name, age, grade, gender, state from student"+ " where grade = :grade and state = :state and gender = :gender";return jdbcClient.sql(sql).params(paramMap).query(new StudentRowMapper()).list();
}@Test
void givenJdbcClient_whenQueryWithParamMap_thenSuccess() {Map<String, ?> paramMap = Map.of("grade", 1,"gender", "Male","state", "New York");List<Student> students = studentDao.getStudentsOfGradeStateAndGenderWithParamMap(paramMap);assertEquals(6, students.size());
}
5、使用 JdbClient 执行更新操作
与查询一样,JdbcClient 也支持创建、更新和删除记录等数据库操作。与前面的章节类似,可以通过各种重载版本的 param() 和 params() 方法绑定参数。因此,这里不再赘述。
不过,在执行 INSERT、UPDATE 和 DELETE SQL 语句时,不调用 query() 方法,而是调用 update() 方法。
在 student 表中插入记录:
Integer insertWithSetParamWithNamedParamAndSqlType(Student student) {String sql = "INSERT INTO student (student_name, age, grade, gender, state)"+ "VALUES (:name, :age, :grade, :gender, :state)";Integer noOfrowsAffected = this.jdbcClient.sql(sql).param("name", student.getStudentName(), Types.VARCHAR).param("age", student.getAge(), Types.INTEGER).param("grade", student.getGrade(), Types.INTEGER).param("gender", student.getStudentGender(), Types.VARCHAR).param("state", student.getState(), Types.VARCHAR).update();return noOfrowsAffected;
}@Test
void givenJdbcClient_whenInsertWithNamedParamAndSqlType_thenSuccess() {Student student = getSampleStudent("Johny Dep", 8, 4, "Male", "New York");assertEquals(1, studentDao.insertWithSetParamWithNamedParamAndSqlType(student));
}
上述方法使用 param(String name, Object value, int sqlType) 绑定参数。它有一个额外的 sqlType 参数,用于指定参数的数据类型。最后,update() 方法还会返回受影响的行数。
在上述方法中,getSampleStudent() 返回一个 Student 对象。然后将 Student 对象传递给 insertWithSetParamWithNamedParamAndSqlType() 方法,在 student 表中创建一条新记录。
与 JdbcTemplate 一样,JdbcClient 也有 update(KeyHolder generatedKeyHolder) 方法,用于获取插入记录时生成的自增ID。
6、使用示例
// 按主键查询
public MyData findDataById(Long id) {return jdbcClient.sql("select * from my_data where id = ?").params(id).query(MyData.class).single();}// 自定义条件查询
public List<MyData> findDataByName(String name) {return jdbcClient.sql("select * from my_data where name = ?").params(name).query(MyData.class).list();}// 变量占位符查询
public Integer insertDataWithNamedParam(MyData myData) {Integer rowsAffected = jdbcClient.sql("insert into my_data values(:id,:name) ").param("id", myData.id()).param("name", myData.name()).update();return rowsAffected;}// Map变量查询
public List<MyData> findDataByParamMap(Map<String, ?> paramMap) {return jdbcClient.sql("select * from my_data where name = :name").params(paramMap).query(MyData.class).list();}// 当查询返回的结果不能简单的映射到一个类时,可以编写RowMapper,适用于SQL语句比较复杂的场景:
public List<MyData> findDataWithRowMapper() {return jdbcClient.sql("select * from my_data").query((rs, rowNum) -> new MyData(rs.getLong("id"), rs.getString("name"))).list();}// 查询记录数
public Integer countByName(String name) {return jdbcClient.sql("select count(*) from my_data where name = ?").params(name).query(Integer.class).single();}
// 占位符插入
public Integer insertDataWithParam(MyData myData) {Integer rowsAffected = jdbcClient.sql("insert into my_data values(?,?) ").param(myData.id()).param(myData.name()).update();return rowsAffected;}// 命名参数插入
public Integer insertDataWithNamedParam(MyData myData) {Integer rowsAffected = jdbcClient.sql("insert into my_data values(:id,:name) ").param("id", myData.id()).param("name", myData.name()).update();return rowsAffected;}// 插入整个对象
public Integer insertDataWithObject(MyData myData) {Integer rowsAffected = jdbcClient.sql("insert into my_data values(:id,:name) ").paramSource(myData).update();return rowsAffected;}
参考资料
https://springdoc.cn/spring-6-jdbcclient-api/
https://www.baeldung.com/spring-6-jdbcclient-api
相关文章:
SpringBoot3.2新特性:JdbcClient
文章目录 一、简介二、使用1、支持隐式位置参数2、通过索引设置位置参数3、支持 Name / Value 对命名参数4、通过 Map 设置命名参数5、使用 JdbClient 执行更新操作6、使用示例 参考资料 一、简介 Spring 6.1 中新添加了 JdbcClient 接口,它提供了 Fluent 风格的 A…...
Dify:启动 Web 服务的详细指南
1. 进入 web 目录 cd web解释: cd 是 “change directory” 的缩写,用于切换当前工作目录。您需要进入项目的 web 目录,这是前端代码所在的位置。在这个目录下,您可以执行构建和启动 Web 服务的相关命令。 2. 安装依赖 pnpm in…...
3.1 HarmonyOS NEXT分布式数据管理实战:跨设备同步、端云协同与安全保护
HarmonyOS NEXT分布式数据管理实战:跨设备同步、端云协同与安全保护 在万物互联的时代,数据的跨设备流转与安全共享是全场景应用的核心需求。HarmonyOS NEXT通过分布式数据管理技术,实现了设备间数据的实时同步与端云协同,为开发…...

Aop + 注解实现数据字典类型转换 EasyExcel导出
Aop 注解 实现数据字典类型转换 文章目录 Aop 注解 实现数据字典类型转换一、基础方式✅字典转换简介👉实现步骤✅ 1. 定义自定义注解Dict ✅ 2. 定义查询字典项的两个方法✅ 3. 定义Aop拦截我们查询的方法✅ 4. VO映射类✅ 5. Controller层✅ 6. serviceImpl✅ 7. …...
Python 元组方法全集详解
Python 元组方法全集详解 在 Python 中,元组(tuple)是不可变序列类型,因此支持的操作比列表少。以下是元组支持的所有方法和操作: 一、元组核心方法 1. 创建元组 # 标准创建 t = (1, 2, 3) # (1, 2, 3) t = tuple(...
Selenium 中 JavaScript 点击操作的原理及应用
在 Selenium 中使用 JavaScript 执行点击操作(如 driver.execute_script("arguments[0].click();", element))的原理涉及 WebDriver 架构、浏览器事件机制以及 JavaScript 对 DOM 的直接操作,以下是详细解释: 1. Selen…...

Xilinx超过256m bit flash固件跳转失败问题
问题描述 按照 链接: Xilinx 7系列fpga在线升级和跳转 这个方式跳转失败 问题排查 进一步排查现象如下 上面这个现象呈现出明显的以16m为周期的规律。感觉很大概率是因为flash超过了16m(256bit)导致的地址越界问题。另外我在CSDN上也找到类似的问题…...

SpringCloud 分布式锁Redisson锁的重入性与看门狗机制 高并发 可重入
可重入 Redisson 的锁支持 可重入性,这意味着同一个线程在获取锁后,如果再次尝试获取该锁,它可以成功地获得锁,而不会被阻塞。 每次一个线程成功获取锁后,它的持有次数会增加。当线程再次获取该锁时,Redi…...

02 APP 自动化-Appium 运行原理详解
环境搭建见 01 APP 自动化-环境搭建 文章目录 一、Appium及Appium自动化测试原理二、Appium 自动化配置项三、常见 ADB 命令四、第一个 app 自动化脚本 一、Appium及Appium自动化测试原理 Appium 跨平台、开源的 app 自动化测试框架,用来测试 app 应用程序&#x…...

由docker引入架构简单展开说说技术栈学习之路
想象一下,你开了一家线上小卖部(单机版),突然爆单了怎么办?别急,技术架构的升级打怪之路,可比哆啦A梦的口袋还神奇! 第1关:单枪匹马的创业初期(单机架构&…...

linux 1.0.5
环境变量到底是什么 也就是windows上面的环境变量 就是这个东东,用户变量和系统变量,那这些到底是啥呢? 主包只是用过,配置来配置去的,就是不知道是啥意思 windows上面的环境变量 windows的ls命令是dir 输入calc可有…...

强化学习的前世今生(五)— SAC算法
书接前四篇 强化学习的前世今生(一) 强化学习的前世今生(二) 强化学习的前世今生(三)— PPO算法 强化学习的前世今生(四)— DDPG算法 本文为大家介绍SAC算法 7 SAC 7.1 最大熵强化…...
SQL进阶之旅 Day 8:窗口函数实用技巧
【SQL进阶之旅 Day 8】窗口函数实用技巧 在现代数据库开发中,处理复杂的业务逻辑和大规模数据时,仅仅依靠传统的GROUP BY和JOIN操作已经无法满足需求。**窗口函数(Window Function)**作为SQL标准的一部分,为开发者提供…...

生成对抗网络(GAN)基础原理深度解析:从直观理解到形式化表达
摘要 本文详细解析 生成对抗网络(GAN) 的 核心原理,从通俗类比入手,结合印假钞与警察博弈的案例阐述生成器 与 判别器 的对抗机制;通过模型结构示意图,解析 噪声采样、样本生成 及判别流程;基于…...
ubuntu 安装redis-6.2.9 源码安装和相关配置详解
目录 1 查看redis 软件列表 2 操作系统信息 3 redis软件下载并编译安装 4 redis 配置文件 5 启动redis 6 redis登录测试 7 设置redis开机启动 8 redis 配置详解 1 查看redis 软件列表 https://download.redis.io/releases/ 2 操作系统信息 rootu24-redis-120:~# cat /…...
c++之数组
目录 C数组基础概念 数组常见操作 二维数组定义与初始化 二维数组遍历方法 二维数组与函数 C数组基础概念 数组是C中用于存储相同类型元素的连续内存结构。通过索引访问元素,索引从0开始。数组大小必须在编译时确定,属于静态数据结构。 #include &…...
torch.distributed.launch 、 torchrun 和 torch.distributed.run 无法与 nohup 兼容
问题现象: 使用nohup 启动torch的分布式训练后, 由于ssh断开与服务器的连接, 导致训练过程出错: WARNING:torch.distributed.elastic.agent.server.api:Received 1 death signal, shutting down workers WARNING:torch.distribu…...
[SC]C++ 中 struct vs. class 的唯一区别
SystemC中 struct vs. class 的唯一区别 一、背景: 在 SystemC 示例里你会常看到这样的写法:SC_MODULE(Top) {// … ports, signals, 进程注册 … };而如果你展开宏 SC_MODULE(Top),它本质上就是:struct Top : sc_core::sc_module {// public:// Top(sc_core::sc_module_…...
React从基础入门到高级实战:React 高级主题 - React设计模式:提升代码架构的艺术
React设计模式:提升代码架构的艺术 引言 在React开发中,设计模式是构建可维护、可扩展和高性能应用的关键。随着应用复杂性的增加,掌握高级设计模式不仅是技术上的挑战,更是打造优雅架构的艺术。对于有经验的开发者而言…...

【GitHub开源AI精选】WhisperX:70倍实时语音转录、革命性词级时间戳与多说话人分离技术
系列篇章💥 No.文章1【GitHub开源AI精选】LLM 驱动的影视解说工具:Narrato AI 一站式高效创作实践2【GitHub开源AI精选】德国比勒费尔德大学TryOffDiff——高保真服装重建的虚拟试穿技术新突破3【GitHub开源AI精选】哈工大(深圳)…...
【leetcode】459.重复的子字符串
文章目录 题目题解枚举 题目 459.重复的子字符串 给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。 示例 1: 输入: s “abab” 输出: true 解释: 可由子串 “ab” 重复两次构成。 示例 2: 输入: s “aba” 输出: false 示例 3: 输入…...

华为OD机试真题——文件目录大小(2025 A卷:100分)Java/python/JavaScript/C++/C语言/GO六种语言最佳实现
2025 A卷 100分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《文件目录大小》: 目录 题…...
【Java】mybatis-plus乐观锁与Spring重试机制
上一篇【Java】mybatis-plus乐观锁-基本使用 讲到了mybatis-plus的基本使用,简单的使用Version和一个基础配置类即可实现乐观锁。 但是mybatis-plus本身并没有自带重试机制。 即当我们带上版本号去更新数据,但是由于另一个线程已经将版本号修改了&#x…...
Linux 与 Windows:哪个操作系统适合你?
Linux vs Windows:系统选择的关键考量 在数字化转型浪潮中,操作系统作为底层基础设施的重要性日益凸显。Linux与Windows作为主流选择,其差异不仅体现在技术架构上,更深刻影响着开发效率、运维成本与安全性。本文将从7个核心维度展开对比分析,并提供典型应用场景建…...
C#委托的概念与使用方法
一、委托的基本概念 委托是一种引用类型,它允许将方法作为参数进行传递。简单来说,委托就像是对方法的引用,可以通过委托来调用对应的方法。 委托具有类型安全性,它会检查方法的签名是否与委托的签名匹配,这有助于避免…...

消费者行为变革下开源AI智能名片与链动2+1模式S2B2C商城小程序的协同创新路径
摘要:在信息爆炸与消费理性化趋势下,消费者从被动接受转向主动筛选,企业营销模式面临重构挑战。本文提出开源AI智能名片与链动21模式S2B2C商城小程序的协同创新框架,通过AI驱动的精准触达、链动裂变机制与S2B2C生态赋能࿰…...

软考 系统架构设计师系列知识点之杂项集萃(78)
接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(77) 第139题 以下关于软件测试工具的叙述,错误的是()。 A. 静态测试工具可用于对软件需求、结构设计、详细设计和代码进行评审、走查和审查 B. 静…...
解决MyBatis参数绑定中参数名不一致导致的错误问题
前言 作为一名Java开发者,我在实际项目中曾多次遇到MyBatis参数绑定的问题。其中最常见的一种情况是:在Mapper接口中定义的参数名与XML映射文件中的占位符名称不一致,导致运行时抛出Parameter xxx not found类异常。这类问题看似简单&#x…...

如何解决MySQL Workbench中的错误Error Code: 1175
错误描述: 在MySQL Workbench8.0中练习SQL语句时,执行一条update语句,总是提示如下错误: Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE that uses a KEY columnTo disab…...

Docker 镜像(或 Docker 容器)中查找文件命令
在 Docker 镜像(或 Docker 容器)中运行如下两个命令时: cd / find . -name generate.py它们的含义如下,我们来一行一行详细拆解,并结合例子讲解: ✅ 第一行:cd / ✅ 含义 cd 是“change dire…...