Spring:JDBCTemplate 的源码分析

news2025/2/4 2:58:46

一: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.coloradmin.cn/o/1427374.html

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

相关文章

智能末世战争之机器人的反击

在遥远的未来&#xff0c;地球陷入了一场空前的战争。这场战争不同于以往的任何战争&#xff0c;因为这是由人工智能和机器人主导的战争。在战争爆发之前&#xff0c;人类一直依赖AI和机器人来提高生产效率和生活质量。然而&#xff0c;随着AI技术的飞速发展&#xff0c;机器人…

H5 简约四色新科技风引导页源码

H5 简约四色新科技风引导页源码 源码介绍&#xff1a;一款四色切换自适应现代科技风动态背景的引导页源码&#xff0c;源码有主站按钮&#xff0c;分站按钮2个&#xff0c;QQ联系站长按钮一个。 下载地址&#xff1a; https://www.changyouzuhao.cn/11990.html

flinkcdc 3.0 尝鲜

本文会将从环境搭建到demo来全流程体验flinkcdc 3.0 包含了如下内容 flink1.18 standalone搭建doris 1fe1be 搭建整库数据同步测试各同步场景从检查点重启同步任务 环境搭建 flink环境(Standalone模式) 下载flink 1.18.0 链接 : https://archive.apache.org/dist/flink/flink…

【大数据】专业融合型人才迎来发展良机-国家数据局正式揭牌

⭐简单说两句⭐ 作者&#xff1a;后端小知识 CSDN个人主页&#xff1a;后端小知识 &#x1f50e;GZH&#xff1a;后端小知识 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; 摘要&#xff1a; 新华社北京10月26日电 《中国证券报》26日刊发文章…

shell - 正则表达式和grep命令和sed命令

一.正则表达式概述 1.正则表达式定义 1.1 定义 使用字符串描述、匹配一系列符合某个规则的字符串 1.2 了解 普通字符&#xff1a; 大小写字母、数字、标点符号及一些其它符号元字符&#xff1a; 在正则表达式中具有特殊意义的专用字符 1.3 层次分类 基础正则表达式扩展正…

git修改密码后mac使用sourceTree出现Authentication failed错误

1、退出sourceTree 2、在钥匙串中删除git对应站点Access Key 3、执行命令&#xff1a;git config --system --unset credential.helper 4、重新启动sourceTree&#xff0c;这时会弹出输入密码框&#xff0c;重新输入密码即可

react 之 UseMemo

useMemo 看个场景 下面我们的本来的用意是想基于count的变化计算斐波那契数列之和&#xff0c;但是当我们修改num状态的时候&#xff0c;斐波那契求和函数也会被执行&#xff0c;显然是一种浪费 // useMemo // 作用&#xff1a;在组件渲染时缓存计算的结果import { useState …

树——二叉搜索树

二叉搜索树 概述 随着计算机算力的提升和对数据结构的深入研究&#xff0c;二叉搜索树也不断被优化和扩展&#xff0c;例如AVL树、红黑树等。 特性 二叉搜索树&#xff08;也称二叉排序树&#xff09;是符合下面特征的二叉树&#xff1a; 树节点增加 key 属性&#xff0c;用来…

Git介绍与常用命令总结

Git介绍与其常用命令总结 1、Git介绍2、Git的使用3、Git常用命令3.1 初始化仓库3.2 克隆仓库3.3 配置用户信息3.4 提交代码(Commit)3.5 推送代码(Push)3.6 拉取代码(Pull)3.7 分支(Branch)3.8 远程仓库(Remote)3.9 撤销回退本地改动3.10 更新本地仓库与远程仓库 1、Git介绍 Gi…

编程流程图

对于复杂流程&#xff0c;我做开发之前一般会 先画一下流程图。特别是多个部门有交叉的情况下&#xff1a; processOn&#xff1a; 这个是我之前 一直的选择&#xff0c;他可以画上面的这些&#xff0c;流程图&#xff0c;网页操作&#xff0c;但是他不是免费的&#xff0c;查过…

【LVGL环境搭建】

LVGL环境搭建 win模拟器环境搭建一.二.三.四.五. Ubuntu模拟器环境搭建一. 前置准备二. 下载LVGL Source code&#xff1a;三. 安装sdl2&#xff1a;四. 开启VScode执行五. 安装扩展套件六. 按F5执行七. 执行结果 win模拟器环境搭建 一. 二. 三. 四. 五. Ubuntu模拟器环境…

同城上门预约软件开发:改变生活服务模式

随着互联网技术的飞速发展&#xff0c;人们的生活方式也在发生着深刻的变化。特别是在生活服务领域&#xff0c;新的需求和模式不断涌现。其中&#xff0c;同城上门预约服务正逐渐成为一种新的趋势。本文将探讨开发同城上门预约软件的意义、市场需求、功能设计以及面临的挑战。…

C++类和对象入门(一)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、面相过程和面向对象的初步认识 C语言是面向过程的&#xff0c;关注的是过程&#xff0c;分析出求解问题的步骤&#…

大语言模型之LlaMA系列-LlaMA 2及LlaMA_chat(下)

多转一致性的系统消息 - System Message for Multi-Turn Consistency 在对话设置中&#xff0c;某些指示应适用于所有对话轮次。 例如&#xff0c;简洁地响应&#xff0c;或"充当"某个公众人物。当我们向Llama 2-Chat提供此类指示时&#xff0c;后续应响应始终遵守约…

专业139总分400+南昌大学811信号与系统考研经验电子信息与通信工程集成电路

今年专业课811信号与系统139分&#xff0c;总分400&#xff0c;顺利上岸南昌大学&#xff0c;回首这一年的复习&#xff0c;有很多经验想和大家分享&#xff0c;希望对大家复习会有一些帮助。专业课&#xff1a;139分&#xff0c;811信号与系统 主要参考书&#xff1a;《信号与…

2024年人工智能应用与先进制造科学国际学术会议(ICAIAAMS 2024)

2024年人工智能应用与先进制造科学国际学术会议(ICAIAAMS 2024) 2024 International Conference on Artificial Intelligence Applications and Advanced Manufacturing Science (ICAIAAMS 2024) 会议简介&#xff1a; 2024年人工智能应用与先进制造科学国际学术会议&#xff…

C语言基础:头歌练习数组练习

&#xff08;字符串插入&#xff09; 任务描述 题目描述:输入两个字符串a和b&#xff0c;将b串中的最大字符插入到a串中最小字符后面。 样例输入&#xff1a; MynameisAmy MynameisJane 样例输出&#xff1a; MynameisAymy 题目分析&#xff1a;a字符串中最小的字符是A…

黑马程序员——html css基础——day06——Flex布局

目录&#xff1a; 小米登录 第一步搭建大盒子logo设置标题和input设置密码框和登录按钮完整写法&#xff1a;爱宠案例 大盒子dog搭建h2标题的做法ul布局修改li和a链接的样式给li添加背景图片完整的写法&#xff1a;标准流浮动 基本使用产品区域布局 左右布局区域小li布局清除浮…

天拓四方:边缘计算网关功能、特点与应用举例

传统的数据处理方式面临网络延迟、带宽限制和安全风险等问题。为了解决这些问题&#xff0c;边缘计算技术应运而生&#xff0c;而边缘计算网关作为其核心组件&#xff0c;正发挥着越来越重要的作用。边缘计算网关位于数据源和云数据中心之间。它具备数据采集、协议转换、数据处…

极限挑战:使用 Go 打造百亿级文件系统的实践之旅

JuiceFS 企业版是一款为云环境设计的分布式文件系统&#xff0c;单命名空间内可稳定管理高达百亿级数量的文件。 构建这个大规模、高性能的文件系统面临众多复杂性挑战&#xff0c;其中最为关键的环节之一就是元数据引擎的设计。JuiceFS 企业版于 2017 年上线&#xff0c;经过…