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);
				}
				// 记录数据库连接: 引用次数加1
				holderToUse.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 {
			@Override
			public Integer doInStatement(Statement stmt) throws SQLException {
				// 执行sql 语句
				int rows = stmt.executeUpdate(sql);
				if (logger.isTraceEnabled()) {
					logger.trace("SQL update affected " + rows + " rows");
				}
				return rows;
			}
			@Override
			public 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
			@Nullable
			public T doInStatement(Statement stmt) throws SQLException {
				ResultSet rs = null;
				try {
					rs = stmt.executeQuery(sql);
					return rse.extractData(rs);
				}
				finally {
					JdbcUtils.closeResultSet(rs);
				}
			}
			@Override
			public String getSql() {
				return sql;
			}
		}

		return execute(new QueryStatementCallback());
	}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

提升编程效率的利器: 解析Google Guava库之集合篇RangeSet范围集合(五)

在编程中&#xff0c;我们经常需要处理各种范围集合&#xff0c;例如时间范围、数字范围等。传统的集合类库往往只能处理离散的元素集合&#xff0c;对于范围集合的处理则显得力不从心。为了解决这个问题&#xff0c;Google的Guava库提供了一种强大的数据结构——RangeSet&…

一文掌握 Golang 加密:crypto/cipher 标准库全面指南

一文掌握 Golang 加密&#xff1a;crypto/cipher 标准库全面指南 引言Golang 和加密简介crypto/cipher 库概览使用 crypto/cipher 实现加密高级功能和技巧最佳实践和性能优化总结资源推荐 引言 在现代软件开发领域&#xff0c;安全性是一个不容忽视的重要议题。随着信息技术的…

黑盒测试用例的具体设计方法(7种)

7种常见的黑盒测设用例设计方法&#xff0c;分别是等价类、边界值、错误猜测法、场景设计法、因果图、判定表、正交排列。 &#xff08;一&#xff09;等价类 1.概念 依据需求将输入&#xff08;特殊情况下会考虑输出&#xff09;划分为若干个等价类&#xff0c;从等价类中选…

备战蓝桥杯---数据结构与STL应用(基础实战篇1)

话不多说&#xff0c;直接上题&#xff1a; 当然我们可以用队列&#xff0c;但是其插入复杂度为N,总的复杂度为n^2,肯定会超时&#xff0c;于是我们可以用链表来写&#xff0c;同时把其存在数组中&#xff0c;这样节点的访问复杂度也为o(1).下面是AC代码&#xff1a; 下面我们来…

Vim实战:使用Vim实现图像分类任务(一)

文章目录 摘要安装包安装timm 数据增强Cutout和MixupEMA项目结构编译安装Vim环境环境安装过程安装库文件 计算mean和std生成数据集 摘要 论文&#xff1a;https://arxiv.org/pdf/2401.09417v1.pdf 翻译&#xff1a; 近年来&#xff0c;随着深度学习的发展&#xff0c;视觉模型…

项目解决方案:高清视频监控联网设计方案

目 录 一、客户需求 二、网络拓扑图 三、方案描述 四、服务器配置 五、方案优势 1. 多级控制 2. 平台可堆叠使用 3. 支持主流接入协议 4. 多种终端显示 5. 视频质量诊断 6. 客户端功能强大 7. 一机一档 一、客户需求 客户现场存在两个网络环境&#xff0c…

25考研北大软微该怎么做?

25考研想准备北大软微&#xff0c;那肯定要认真准备了 考软微需要多少实力 现在的软微已经不是以前的软微了&#xff0c;基本上所有考计算机的同学都知道&#xff0c;已经没有什么信息优势了&#xff0c;只有实打实的有实力的选手才建议报考。 因为软微的专业课也是11408&am…

HarmonyOS4.0系统性深入开发31创建列表(List)

创建列表&#xff08;List&#xff09; 概述 列表是一种复杂的容器&#xff0c;当列表项达到一定数量&#xff0c;内容超过屏幕大小时&#xff0c;可以自动提供滚动功能。它适合用于呈现同类数据类型或数据类型集&#xff0c;例如图片和文本。在列表中显示数据集合是许多应用…

[DotNetGuide]C#/.NET/.NET Core优秀项目和框架精选

前言 注意&#xff1a;排名不分先后&#xff0c;都是十分优秀的开源项目和框架&#xff0c;每周定期更新分享&#xff08;欢迎关注公众号&#xff1a;追逐时光者&#xff0c;第一时间获取每周精选分享资讯&#x1f514;&#xff09;。 帮助开发者发现功能强大、性能优越、创新前…

R语言学习case7:ggplot基础画图(核密度图)

step1: 导入ggplot2库文件 library(ggplot2)step2&#xff1a;带入自带的iris数据集 iris <- datasets::irisstep3&#xff1a;查看数据信息 dim(iris)维度为 [150,5] head(iris)查看数据前6行的信息 step4&#xff1a;画图展示 plot2 <- ggplot(iris,aes(Sepal.W…

基于C#制作一个连连看小游戏

基于C#制作一个连连看小游戏,实现:难易度选择、关卡选择、倒计时进度条、得分计算、音效播放等功能。 目录 引言游戏规则开发环境准备游戏界面设计游戏逻辑实现图片加载与显示鼠标事件处理游戏优化与扩展添加关卡与难度选择说明</

wpf 数据转换(Bytes 转 KB MB GB)

效果 后端 using ProCleanTool.Model; using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Data;namespace P…

Python(19)Excel表格操作Ⅰ

目录 导包 读取EXCEL文件 1、获取worksheet名称 2、设定当前工作表 3、输出目标单元格数据 4、工作表.rows&#xff08;行&#xff09; 5、工作表.columns&#xff08;列&#xff09; 小结 导包 要想使用 python 操作 Excel 文件&#xff0c;应当导入 openpyxl 包。在…

【Docker】WSL(Windows Subsystem for Linux)常见命令解释说明以及简单使用

欢迎来到《小5讲堂》&#xff0c;大家好&#xff0c;我是全栈小5。 这是《Docker容器》序列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对…

Flask 入门2

1. 在上一节中&#xff0c;我们使用到了静态路由&#xff0c;即一个路由规则对应一个 URL。而在实际应用中&#xff0c;更多使用的则是动态路由&#xff0c;它的 URL是可变的。 2. 定义一个很常见的路由地址 app.route(/user/<username>) def user(username):return U…

鸿蒙首批原生应用!无感验证已完美适配鸿蒙系统

顶象无感验证已成功适配鸿蒙系统&#xff0c;成为首批鸿蒙原生应用&#xff0c;助力鸿蒙生态的快速发展。 作为全场景分布式操作系统&#xff0c;鸿蒙系统旨在打破不同设备之间的界限&#xff0c;实现极速发现、极速连接、硬件互助、资源共享。迄今生态设备数已突破8亿台&…

【环境配置】安装了pytorch但是报错torch.cuda.is_availabel()=Flase

解决思路&#xff1a;import torch正常&#xff0c;说明torch包安装正常&#xff0c;但是不能和gpu正常互动&#xff0c;猜测还是pytroch和cuda的配合问题 1.查看torch包所需的cuda版本 我的torch是2.0.1&#xff0c;在现在是比较新的包&#xff0c;需要12以上的cuda支持&…

【flutter项目类型】project type如何区分

通过项目中.metadata内容区分 如 # Used by Flutter tool to assess capabilities and perform upgrades etc. # # This file should be version controlled and should not be manually edited.version:revision: 85684f9300908116a78138ea4c6036c35c9a1236channel: stablep…

vs 撤销本地 commit 并保留更改

没想到特别好的办法&#xff0c;我想的是用 vs 打开 git 命令行工具 然后通过 git 命令来撤销提交&#xff0c;尝试之前建议先建个分支实验&#xff0c;以免丢失代码&#xff0c; git 操作见 git 合并多个 commit / 修改上一次 commit

PaddleNLP的简单使用

1 介绍 PaddleNLP是一个基于PaddlePaddle深度学习平台的自然语言处理&#xff08;NLP&#xff09;工具库。 它提供了一系列用于文本处理、文本分类、情感分析、文本生成等任务的预训练模型、模型组件和工具函数。 PaddleNLP有统一的应用范式&#xff1a;通过 paddlenlp.Task…
最新文章