Spring:JDBCTemplate 的源码分析

一:JdbcTemplate的简介

JdbcTemplate 是 Spring Template设置模式中的一员。类似的还有 TransactionTemplate、 MongoTemplate 等。通过 JdbcTemplate 我们可以使得 Spring 访问数据库的过程简单化。

二:执行SQL语句的方法

1:在JdbcTemplate中执行SQL语句的方法大致分为3类

execute:可以执行所有SQL语句,但是没有返回值。一般用于执行DDL(数据定义语言,主要的命令有CREATE、ALTER、DROP等)语句。
update:用于执行INSERT、UPDATE、DELETE等DML语句。
queryXxx:用于DQL数据查询语句。

2: 基本使用方式如下
       JdbcTemplate jdbcTemplate = run.getBean(JdbcTemplate.class);// 查询List<User> maps = jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));// 更新int update = jdbcTemplate.update("update user set password = ? where id = 1", "6666");

三:核心方法 - execute

实际上,无论是query 或者 update,其底层调用的都是 JdbcTemplate#execute(org.springframework.jdbc.core.StatementCallback<T>) 方法。

execute:作为数据库操作的核心入口,其实实现逻辑和我们一起最基础的写法类似。将大多数数据库操作对相同的统一封装,而将个性化的操作使用 StatementCallback 进行回调,并进行数据库资源释放的一些收尾操作。
execute 方法的作用是获取数据库连接,准备好Statement, 随后调用预先传入的回调方法。

下面我们直接来看 JdbcTemplate#execute(org.springframework.jdbc.core.StatementCallback<T>) 方法:

public <T> T execute(StatementCallback<T> action) throws DataAccessException {Assert.notNull(action, "Callback object must not be null");// 从数据源中获取数据连接Connection con = DataSourceUtils.getConnection(obtainDataSource());Statement stmt = null;try {// 创建 Statement 。stmt = con.createStatement();// 应用一些设置applyStatementSettings(stmt);// 回调执行个性化业务T result = action.doInStatement(stmt);// 警告处理handleWarnings(stmt);return result;}catch (SQLException ex) {// Release Connection early, to avoid potential connection pool deadlock// in the case when the exception translator hasn't been initialized yet.// 释放数据库连接,避免当异常转换器没有被初始化的时候出现潜在的连接池死锁String sql = getSql(action);JdbcUtils.closeStatement(stmt);stmt = null;DataSourceUtils.releaseConnection(con, getDataSource());con = null;throw translateException("StatementCallback", sql, ex);}finally {JdbcUtils.closeStatement(stmt);DataSourceUtils.releaseConnection(con, getDataSource());}}

另一种形式的 execute 。思路基本相同,不再赘述

public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action)throws DataAccessException {Assert.notNull(psc, "PreparedStatementCreator must not be null");Assert.notNull(action, "Callback object must not be null");if (logger.isDebugEnabled()) {String sql = getSql(psc);想·logger.debug("Executing prepared SQL statement" + (sql != null ? " [" + sql + "]" : ""));}Connection con = DataSourceUtils.getConnection(obtainDataSource());PreparedStatement ps = null;try {ps = psc.createPreparedStatement(con);applyStatementSettings(ps);T result = action.doInPreparedStatement(ps);handleWarnings(ps);return result;}catch (SQLException ex) {// Release Connection early, to avoid potential connection pool deadlock// in the case when the exception translator hasn't been initialized yet.if (psc instanceof ParameterDisposer) {((ParameterDisposer) psc).cleanupParameters();}String sql = getSql(psc);psc = null;JdbcUtils.closeStatement(ps);ps = null;DataSourceUtils.releaseConnection(con, getDataSource());con = null;throw translateException("PreparedStatementCallback", sql, ex);}finally {if (psc instanceof ParameterDisposer) {((ParameterDisposer) psc).cleanupParameters();}JdbcUtils.closeStatement(ps);DataSourceUtils.releaseConnection(con, getDataSource());}}
1:获取数据库连接
Connection con = DataSourceUtils.getConnection(obtainDataSource());

obtainDataSource() 就是简单的获取 dataSource。这里的 dataSource 是 JdbcTemplate 在创建的时候,作为构造注入时候的参数传递进来。

	protected DataSource obtainDataSource() {DataSource dataSource = getDataSource();Assert.state(dataSource != null, "No DataSource set");return dataSource;}

DataSourceUtils.getConnection 方法中调用了 doGetConnection 方法。下面我们来看看 doGetConnection 方法。

在数据库连接方面,Spring 主要考虑的是关于事务方面的处理。基于事务处理的特殊性,Spring需要保证线程中的数据库操作都是使用同一个事务连接。

public static Connection doGetConnection(DataSource dataSource) throws SQLException {Assert.notNull(dataSource, "No DataSource specified");// 获取当前线程的数据库连接持有者。这里是事务中的连接时同一个db连接ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);// 如果存在持有者 && (存在连接 || 和事务同步状态)if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {// 标记引用次数加一conHolder.requested();// 如果当前线程存在持有者,并且与事务同步了,如果仍然没有DB 连接,那么说明当前线程就是不存在数据库连接,则获取连接绑定到持有者上。if (!conHolder.hasConnection()) {logger.debug("Fetching resumed JDBC Connection from DataSource");// 持有者不存在连接则获取连接conHolder.setConnection(fetchConnection(dataSource));}// 返回持有者所持有的连接return conHolder.getConnection();}// Else we either got no holder or an empty thread-bound holder here.// 到这里没返回,则说明 没有持有者 || 持有者没有同步绑定logger.debug("Fetching JDBC Connection from DataSource");// 获取到 DB 连接Connection con = fetchConnection(dataSource);// 如果当前线程的事务同步处于活动状态if (TransactionSynchronizationManager.isSynchronizationActive()) {try {// Use same Connection for further JDBC actions within the transaction.// Thread-bound object will get removed by synchronization at transaction completion.// 如果持有者为null则创建一个,否则将刚才创建的 DB 连接赋值给 持有者ConnectionHolder holderToUse = conHolder;if (holderToUse == null) {holderToUse = new ConnectionHolder(con);}else {holderToUse.setConnection(con);}// 记录数据库连接: 引用次数加1holderToUse.requested();// 设置事务和持有者同步TransactionSynchronizationManager.registerSynchronization(new ConnectionSynchronization(holderToUse, dataSource));holderToUse.setSynchronizedWithTransaction(true);if (holderToUse != conHolder) {TransactionSynchronizationManager.bindResource(dataSource, holderToUse);}}catch (RuntimeException ex) {// Unexpected exception from external delegation call -> close Connection and rethrow.releaseConnection(con, dataSource);throw ex;}}return con;}

由于一个事务中存在多个sql 执行,每个sql 执行前都会获取一次DB 连接,所以这里使用 holderToUse.requested(); 来记录当前事务中的数据库连接引用的次数。执行完毕后将会将引用次数减一。在最后的sql 执行结束后会将引用次数减一。

2:应用用户设定的数据参数
	protected void applyStatementSettings(Statement stmt) throws SQLException {int fetchSize = getFetchSize();// 设置 fetchSize 属性if (fetchSize != -1) {stmt.setFetchSize(fetchSize);}// 设置 maxRows 属性int maxRows = getMaxRows();if (maxRows != -1) {stmt.setMaxRows(maxRows);}DataSourceUtils.applyTimeout(stmt, getDataSource(), getQueryTimeout());}

FetchSize :该参数的目的是为了减少网络交互次数设计的。在访问 ResultSet时,如果它每次只从服务器上读取一行数据,会产生大量开销。 FetchSize 参数的作用是 在调用 rs.next时, ResultSet会一次性从服务器上取多少行数据回来。这样在下次 rs.next 时,他可以直接从内存中获取数据而不需要网络交互,提高了效率。但是这个设置可能会被某些jdbc驱动忽略。设置过大也会造成内存上升。
MaxRow :其作用是将此 Statement 对象生成的所有 ResultSet 对象可以包含的最大行数限制设置为给定值。

3:警告处理
	protected void handleWarnings(Statement stmt) throws SQLException {// 当设置为忽略警告时尝试只打印日志。if (isIgnoreWarnings()) {if (logger.isDebugEnabled()) {// 如果日志开启的情况下打印日志SQLWarning warningToLog = stmt.getWarnings();while (warningToLog != null) {logger.debug("SQLWarning ignored: SQL state '" + warningToLog.getSQLState() + "', error code '" +warningToLog.getErrorCode() + "', message [" + warningToLog.getMessage() + "]");warningToLog = warningToLog.getNextWarning();}}}else {handleWarnings(stmt.getWarnings());}}

这里用到了一个类 SQWarning ,SQWarning 提供关于数据库访问警告信息的异常。这些警告直接链接到导致报告警告的方法所在的对象。警告可以从Connection、Statement 和 ResultSet 对象中获取。视图在已经关闭的连接上获取警告将导致抛出异常。注意,关闭语句时还会关闭它可能生成得到结果集。

对于警告的处理方式并不是直接抛出异常,出现警告很可能会出现数据错误,但是并不一定会影响程序执行,所以这里用户可以自己设置处理警告的方式,如果默认是忽略警告,当出现警告时仅打印警告日志,不抛出异常。

4:资源释放

数据库的连接释放并不是直接调用了 Connection 的API 中的close 方法。考虑到存在事务的情况,如果当前线程存在事务,那么说明在当前线程中存在共用数据库连接(存在事务则说明不止一个sql 语句被执行,则会共用同一个数据库连接, 所以如果当前Sql执行完毕,不能立即关闭数据库连接,而是将引用次数减一),这种情况下直接使用 ConnectionHolder 中的released 方法进行连接数减一,而不是真正的释放连接。

	public static void doReleaseConnection(@Nullable Connection con, @Nullable DataSource dataSource) throws SQLException {if (con == null) {return;}if (dataSource != null) {// 当前线程存在事务的情况下说明存在共用数据库连接直接使用 ConnectionHolder 中的 released 方法进行连接数减一而不是真正的释放连接。ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);if (conHolder != null && connectionEquals(conHolder, con)) {// It's the transactional Connection: Don't close it.conHolder.released();return;}}doCloseConnection(con, dataSource);}...public static void doCloseConnection(Connection con, @Nullable DataSource dataSource) throws SQLException {if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) {con.close();}}

四:execute 的回调

上面说了 execute 方式是整个JdbcTemplate 的核心,其他个性化定制都是在其基础上,通过StatementCallback 回调完成的。下面我们简单看一看。

1:Update中的回调函数

我们挑一个最简单的Update 方法: JdbcTemplate#update(java.lang.String)

	public int update(final String sql) throws DataAccessException {Assert.notNull(sql, "SQL must not be null");if (logger.isDebugEnabled()) {logger.debug("Executing SQL update [" + sql + "]");}/*** Callback to execute the update statement.*/// 该种形式的回调方法。不同形式的回调实现类并不相同。class UpdateStatementCallback implements StatementCallback<Integer>, SqlProvider {@Overridepublic Integer doInStatement(Statement stmt) throws SQLException {// 执行sql 语句int rows = stmt.executeUpdate(sql);if (logger.isTraceEnabled()) {logger.trace("SQL update affected " + rows + " rows");}return rows;}@Overridepublic String getSql() {return sql;}}// 通过 execute 将 Statement  回调给 UpdateStatementCallback。return updateCount(execute(new UpdateStatementCallback()));}

除此之外,还有另一种形式的更新,其思路都相同。调用的也是另一种 execute 。

	protected int update(final PreparedStatementCreator psc, @Nullable final PreparedStatementSetter pss)throws DataAccessException {logger.debug("Executing prepared SQL update");return updateCount(execute(psc, ps -> {try {if (pss != null) {pss.setValues(ps);}int rows = ps.executeUpdate();if (logger.isTraceEnabled()) {logger.trace("SQL update affected " + rows + " rows");}return rows;}finally {if (pss instanceof ParameterDisposer) {((ParameterDisposer) pss).cleanupParameters();}}}));}
2:query 功能的实现

可以看到,思路基本相同,这里不再赘述。

	public <T> T query(final String sql, final ResultSetExtractor<T> rse) throws DataAccessException {Assert.notNull(sql, "SQL must not be null");Assert.notNull(rse, "ResultSetExtractor must not be null");if (logger.isDebugEnabled()) {logger.debug("Executing SQL query [" + sql + "]");}/*** Callback to execute the query.*/class QueryStatementCallback implements StatementCallback<T>, SqlProvider {@Override@Nullablepublic T doInStatement(Statement stmt) throws SQLException {ResultSet rs = null;try {rs = stmt.executeQuery(sql);return rse.extractData(rs);}finally {JdbcUtils.closeResultSet(rs);}}@Overridepublic String getSql() {return sql;}}return execute(new QueryStatementCallback());}

本文是关于spring相关JDBCTemplate 的源码分析,希望可以帮到初学spring的小伙伴哦!

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

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

相关文章

前端性能优化:Vue项目打包后app.xxx.js 和 chunk-vendors.xxx.js 文件太大,导致页面加载时间太长

问题场景&#xff0c;如下图&#xff0c;环境上的 app.js 和chunk-vendors.js 两个文件大小&#xff0c;高达3.4M 和 2M &#xff0c;加载所耗费的时间也很长。 下面说一下如何解决&#xff1a; 1、首先需要安装插件 compression-webpack-plugin&#xff0c;我这里用的是6.1.1…

情人节送什么礼给男朋友合适?适合送男友的礼物合集

情人节即将来临&#xff0c;作为贴心的女友&#xff0c;你是否已经开始为男友精心挑选礼物了呢&#xff1f;为了让这个特殊的日子充满温馨与甜蜜&#xff0c;选择一份既实用又充满心意的礼物是至关重要的&#xff0c;下面为大家推荐一些适合在情人节送给男友的好物&#xff0c;…

探索自然语言处理在改善搜索引擎、语音助手和机器翻译中的应用

文章目录 每日一句正能量前言文本分析语音识别机器翻译语义分析自然语言生成情感分析后记 每日一句正能量 努力学习&#xff0c;勤奋工作&#xff0c;让青春更加光彩。 前言 自然语言处理&#xff08;NLP&#xff09;是人工智能领域中与人类语言相关的重要研究方向&#xff0c…

腾讯主导制定全球首个车载小程序国际标准,助力车载应用生态发展

2024年1月&#xff0c;国际电信联盟标准部门&#xff08;ITU-T&#xff09;正式发布了由腾讯主导制定的《F.749.8 In-vehicle multimedia applets: Framework and functional requirements》(车载多媒体小程序框架和技术需求)国际标准。 这是全球首个由中国企业主导制定的车载小…

LNMP环境搭建动态网站

一、环境准备 服务器&#xff1a;openEuler 22.03 Linux IPV4 &#xff1a;192.168.110.144/24 网页服务器&#xff1a;Nginx1.21.0 数据库&#xff1a;MySQL 8.0.36 PHP&#xff1a;8.0.30 1.安装软件 [rootnode3 ~]# yum install php-mysqlnd php php-gd php-fpm php-xml -y…

在ESXi中部署时出现the host does not support intel vt-x

在VCenter中新建了一台ESXi用于部署VCSA进行实验 在部署VCSA的第二阶段&#xff0c;出现the host does not support intel vt-x&#xff0c;部署失败。 解决办法&#xff1a;点进ESXi虚拟机的设置界面&#xff08;要先关机&#xff09;&#xff0c;将硬件虚拟化打开&#xff0c…

【Vue3+Vite】路由机制router 快速学习 第四期

文章目录 路由简介路由是什么路由的作用 一、路由入门案例1. 创建项目 导入路由依赖2. 准备页面和组件3. 准备路由配置4. main.js引入router配置 二、路由重定向三、编程式路由(useRouter)四、路由传参(useRoute)五、路由守卫总结 路由简介 路由是什么 路由就是根据不同的 URL…

正点原子--STM32中断系统学习笔记(2)

引言 上篇帖子STM32中断系统学习笔记(1)是理论&#xff0c;这篇帖子开始实战&#xff0c;目标是通过按键实现LED的控制。 1.工程建立 以正点原子HAL库 实验1 跑马灯实验为基础&#xff0c;复制工程&#xff0c;在“Drivers--BSP”目录下建立EXTI文件夹&#xff0c;并创建ext…

Windows - 防火墙 - 如何开启单个端口以供Web应用访问(以82端口为例) - 开启端口后还是访问失败了?

Windows - 防火墙 - 如何开启单个端口以供Web应用访问(以82端口为例) - 开启端口后还是访问失败了&#xff1f; 前言 在网上搜“防火墙开启某个端口”供其他机器访问&#xff0c;都是只讲到了“如何允许某个端口被访问”&#xff0c;而没有后续了。 我之前就遇到过这个问题&…

数据据库八之 视图、触发器、事务

【零】准备数据 【1】创建表 &#xff08;1&#xff09;部门表 d_id是部门的编号d_name是部门的名字 # 确保表不存在 drop table if exists department; # 创建表 create table department( d_id int auto_increment primary key, d_name varchar(6) )auto_increment 501 …

2024-01-06-AI 大模型全栈工程师 - 机器学习基础

摘要 2024-01-06 阴 杭州 晴 本节简介: a. 数学模型&算法名词相关概念; b. 学会数学建模相关知识&#xff1b; c. 学会自我思考&#xff0c;提升认知&#xff0c;不要只会模仿&#xff1b; 课程内容 1. Fine-Tuning 有什么作用&#xff1f; a. 什么是模型训练&#xff…

Linux(一)

目录结构 【在 Linux 世界里&#xff0c;一切皆文件】 linux 的文件系统是采用级层式的树状目录结构&#xff1b; 序号名称介绍备注1/&#xff1a;根目录一般根目录下只存放目录&#xff0c;在 linux 下有且只有一个根目录&#xff0c;所有的东西都是从这里开始&#xff1b; 当…

Open CASCADE学习|曲面上一点的曲率及切平面

曲率&#xff08;Curvature&#xff09;是一个几何学的概念&#xff0c;用于描述一个物体的形状在某一点上的弯曲程度。在我们日常生活中&#xff0c;曲率与我们的生活息息相关&#xff0c;如道路的弯道、建筑物的拱形结构、自然界的山脉等等。了解曲率的概念和计算方法&#x…

Unity中开发程序打包发布

添加ESC脚本 使用Unity打包发布的过程中&#xff0c;考虑到打开的程序会处于全屏界面&#xff0c;而此时我们又会有退出全屏的需求&#xff0c;因此需要添加ESC脚本&#xff0c;当我们单击ESC脚本的过程中&#xff0c;退出全屏模式。 在Assets/Scenes下&#xff0c;创建esc.cs…

Python之PySpark简单应用

文章目录 一、介绍1.准备工作2. 创建SparkSession对象&#xff1a;3. 读取数据&#xff1a;4. 数据处理与分析&#xff1a;5. 停止SparkSession&#xff1a; 二、示例1.读取解析csv数据2.解析计算序列数据map\flatmap 三、问题总结1.代码问题2.配置问题 一、介绍 PySpark是Apa…

Linux离线安装Telnet

前言&#xff1a;由于服务器部署在内网环境&#xff0c;不能yum安装 1.先从网站下载好我们所需要到的三个rpm包http://www.rpmfind.net/linux/rpm2html/search.php?queryxinetd&submitSearch...&system&arch image.png 三个依赖包分别是&#xff1a; -rw-r--r-- 1…

Invicti Professional v24.1.0.43434

新的安全检查 添加了对 dotCMS 的检查添加了对 Ultimate Member WordPress 插件的检查添加了新的 mXSS 模式添加了新签名来检测 JWK 改进 改进了针对 Weak Ciphers Enabled 漏洞的建议改进了对 swagger.json 漏洞的检测添加了对 AWS WAFv2 规则的支持改进了更多错误和警告消…

探索Gin框架:Golang使用Gin完成文件上传

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站https://www.captainbed.cn/kitie。 前言 在之前的文章中&#xff0c;我们讲解了Gin框架的快速入门使用&#xff0c;今天我们来聊聊如何使用…

element-ui button 仿写 demo

基于上篇 button 源码分享写了一个简单 demo&#xff0c;在写 demo 的过程中&#xff0c;又发现了一个小细节&#xff0c;分享一下&#xff1a; 1、组件部分&#xff1a; <template><buttonclass"yss-button"click"handleClick":class"[ty…

STM32目录结构

之前一直头疼的32目录&#xff0c;比51复杂&#xff0c;又没有C规律&#xff0c;也不像python脚本文件关联不强&#xff0c;也不像工整的FPGA工程&#xff0c;编的时候到处放&#xff0c;爆出的错千奇百怪。短暂整理了一个&#xff0c;还是没有理得很轻。 startup_stm32f10x_m…