JdbcTemplate源码解析
先写一个测试代码
package jdbc; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.core.io.ClassPathResource; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.RowCallbackHandler; public class JDBCTest { public static void main(String[] args) { //加载spring ClassPathResource resource = new ClassPathResource( "applicationContext-common2.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader bdf = new XmlBeanDefinitionReader(factory); bdf.loadBeanDefinitions(resource); //获得jdbctemplate JdbcTemplate jt = (JdbcTemplate) factory.getBean("jdbcTemplate"); String sql = "select * from dream where personid=?"; final List<Map<String, String>> list = new ArrayList<Map<String, String>>(); // 一定要用final定义 Object[] params = new Object[] { 1 }; //调用query方法 //注意 query有三个参数 sql params还有一个RowCallbackHandler jt.query(sql, params, new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { Map<String, String> u = new HashMap<String, String>(); u.put("id", rs.getString("id")); u.put("description", rs.getString("description")); list.add(u); } }); for ( Map<String, String> u :list) { System.out.println(u.get("description")); } //HERE IS A SIMPLE EXAMPLE //EXAMPLE EXAMPLE } }<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"> </span>
前期构造方法的调用
我们能追踪到JDBCTemplate的query方法,如下:
//JDBCTemplate.java public void query(String sql, Object[] args, RowCallbackHandler rch) throws DataAccessException { query(sql, newArgPreparedStatementSetter(args), rch); }
关于statement与preparedstatement的区别,我默认大家都明白。
算了还是写出来吧
String name="董磊峰2";
int id=18;
String sql="insert into admininfo (Aname,Aid) values(?,?)";
PreparedStatement st=conn.prepareStatement(sql);
st.setString(1, name); //给第一个栏位注入String型的数值name 这个不是从0开始
st.setInt(2, id);
st.execute();
我们使用sql生成preparedstatement,再把参数传递进去。
newArgPreparedStatementSetter方法返回了一个ArgPreparedStatementSetter类,而ArgPreparedStatementSetter方法实现了PreparedStatementSetter
public interface PreparedStatementSetter { /** * Set parameter values on the given PreparedStatement. * @param ps the PreparedStatement to invoke setter methods on * @throws SQLException if a SQLException is encountered * (i.e. there is no need to catch SQLException) */ void setValues(PreparedStatement ps) throws SQLException; }
很清楚,PreparedStatementSetter就是写参数的嘛
干的事情就类似于上面的PreparedStatement的setString,setInt
//JDBCTemplate.java
public void query(String sql, PreparedStatementSetter pss, RowCallbackHandler rch) throws DataAccessException {
query(sql, pss, new RowCallbackHandlerResultSetExtractor(rch));
}
这个RowCallbackHandlerResultSetExtractor是个干什么的?大家看看它的构造方法包含我们在前面的RowCallbackHandler。
猜也能猜出来,这个是对最后处理逻辑的调用。
举个最简单的例子,我们自己写的RowCallbackHandler的参数是一个ResultSet,等到循环处理多条数据的时候,就得循环的调用processRow,那么在哪里调用呢?
在RowCallbackHandlerResultSetExtractor里
/** * Adapter to enable use of a RowCallbackHandler inside a ResultSetExtractor. * <p>Uses a regular ResultSet, so we have to be careful when using it: * We don't use it for navigating since this could lead to unpredictable consequences. */ private static class RowCallbackHandlerResultSetExtractor implements ResultSetExtractor<Object> { private final RowCallbackHandler rch; public RowCallbackHandlerResultSetExtractor(RowCallbackHandler rch) { this.rch = rch; } public Object extractData(ResultSet rs) throws SQLException { while (rs.next()) { this.rch.processRow(rs); } return null; } }
继续往下看
public <T> T query(String sql, PreparedStatementSetter pss, ResultSetExtractor<T> rse) throws DataAccessException { return query(new SimplePreparedStatementCreator(sql), pss, rse); }
SimplePreparedStatementCreator又是什么鬼?
SimplePreparedStatementCreator实现了PreparedStatementCreator接口
public interface PreparedStatementCreator { /** * Create a statement in this connection. Allows implementations to use * PreparedStatements. The JdbcTemplate will close the created statement. * @param con Connection to use to create statement * @return a prepared statement * @throws SQLException there is no need to catch SQLExceptions * that may be thrown in the implementation of this method. * The JdbcTemplate class will handle them. */ PreparedStatement createPreparedStatement(Connection con) throws SQLException; }
现在明白了吧,SimplePreparedStatementCreator就是生成PreparedStatement的呗
说到这,我有几个不是问题的问题
正常的使用PreparedStatement的顺序是
先生成PreparedStatement,然后注入参数,最后处理结果
怎么JDBCTemplate几个query的调用顺序是先生成ArgPreparedStatementSetter然后再生成SimplePreparedStatementCreator?
反过来不是更符合大家的思考顺序吗?
最终的处理
下面就是最终的大boss了:
public <T> T query( PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor<T> rse) throws DataAccessException { Assert.notNull(rse, "ResultSetExtractor must not be null"); logger.debug("Executing prepared SQL query"); //注意匿名类 //execute一共两个参数 一个是PreparedStatementCreator 一个是PreparedStatementCallback //***************3 return execute(psc, new PreparedStatementCallback<T>() { public T doInPreparedStatement(PreparedStatement ps) throws SQLException { ResultSet rs = null; try { if (pss != null) { pss.setValues(ps); } rs = ps.executeQuery(); //最最核心的一步 调用jdk的接口 ResultSet rsToUse = rs; if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } return rse.extractData(rsToUse); } finally { JdbcUtils.closeResultSet(rs); if (pss instanceof ParameterDisposer) { ((ParameterDisposer) pss).cleanupParameters(); } } } }); }
下面看execute呗:
public <T> T execute(PreparedStatementCreator psc, PreparedStatementCallback<T> action) throws DataAccessException { //检测参数不为null Connection con = DataSourceUtils.getConnection(getDataSource()); PreparedStatement ps = null; try { Connection conToUse = con; //说实话 下面的代码 我也不知道是干什么的 //不过我在测试的时候 //nativeJdbcExtractor为null //我们暂且不管这块 if (this.nativeJdbcExtractor != null && this.nativeJdbcExtractor.isNativeConnectionNecessaryForNativePreparedStatements()) { conToUse = this.nativeJdbcExtractor.getNativeConnection(con); } //*******1 获得PreparedStatement ps = psc.createPreparedStatement(conToUse); //这里是给PreparedStatement设置一些参数 基本不怎么用 不用深究 applyStatementSettings(ps); PreparedStatement psToUse = ps; //跟上面的conToUse一样 暂且不管 if (this.nativeJdbcExtractor != null) { psToUse = this.nativeJdbcExtractor.getNativePreparedStatement(ps); } //************2 回调方法 我们得回到代码3处 T result = action.doInPreparedStatement(psToUse); handleWarnings(ps); return result; } catch (SQLException ex) { // } finally { // } }
那代码1处具体是怎么获得PreparedStatement的呢?
先别看源码,我们自己使用PreparedStatement的时候是什么生成的呢?
PreparedStatement st=conn.prepareStatement(sql);
所以猜一下都能猜出来的,代码1处的psc的默认实现是SimplePreparedStatementCreator
/** * Simple adapter for PreparedStatementCreator, allowing to use a plain SQL statement. */ private static class SimplePreparedStatementCreator implements PreparedStatementCreator, SqlProvider { private final String sql; public SimplePreparedStatementCreator(String sql) { Assert.notNull(sql, "SQL must not be null"); this.sql = sql; } public PreparedStatement createPreparedStatement(Connection con) throws SQLException { return con.prepareStatement(this.sql); } public String getSql() { return this.sql; } }
OK,代码1结束后,我们已经有了不带参数的PreparedStatementCreator
代码2处的action.doInPreparedStatement(psToUse);
这就是回调,我们得去前面看
new PreparedStatementCallback<T>() { public T doInPreparedStatement(PreparedStatement ps) throws SQLException { ResultSet rs = null; try { if (pss != null) { pss.setValues(ps); //给PreparedStatement 插入值 } rs = ps.executeQuery(); //进行最和谐的查询 ResultSet rsToUse = rs; if (nativeJdbcExtractor != null) { rsToUse = nativeJdbcExtractor.getNativeResultSet(rs); } return rse.extractData(rsToUse); //数据的最后整理 } finally { JdbcUtils.closeResultSet(rs); if (pss instanceof ParameterDisposer) { ((ParameterDisposer) pss).cleanupParameters(); } } } }
总结
String sql = "select * from dream where personid=?"; final List<Map<String, String>> list = new ArrayList<Map<String, String>>(); // 一定要用final定义 Object[] params = new Object[] { 1 }; jt.query(sql, params, new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { Map<String, String> u = new HashMap<String, String>(); u.put("id", rs.getString("id")); u.put("description", rs.getString("description")); list.add(u); } });
类似上面的调用方式,在Spring的JDBCTemplate中处理过程是这样的
1 生成给PreparedStatement注入参数的类---ArgPreparedStatementSetter
2 生成最后处理查询结果的RowCallbackHandlerResultSetExtractor,它内部循环调用了上面RowCallbackHandler的processRow方法
3 new一个生成PreparedStatement的类SimplePreparedStatementCreator
4 获得connection并通过SimplePreparedStatementCreator生成PreparedStatement
5 使用回调,处理PreparedStatement
6 向PreparedStatement中插入值
7 rs = ps.executeQuery();
8 调用RowCallbackHandlerResultSetExtractor的extractData方法,里面循环调用上面RowCallbackHandler的processRow方法
那么如果是下面的调用方式呢
sql = "select description from dream where personid=?"; String description= jt.queryForObject(sql, new Object[] { 8 },String.class); System.out.println(description);
上面的查询方式,只能查询出表中的一条记录的一个字段
1 会把String.class包装成一个SingleColumnRowMapper
2 根据SingleColumnRowMapper生成RowMapperResultSetExtractor,这个RowMapperResultSetExtractor与上面的RowCallbackHandlerResultSetExtractor是兄弟,他们都实现了ResultSetExtractor接口。
3 在上面的第8步,这里是调用RowMapperResultSetExtractor的extractData方法
public List<T> extractData(ResultSet rs) throws SQLException { List<T> results = (this.rowsExpected > 0 ? new ArrayList<T>(this.rowsExpected) : new ArrayList<T>()); int rowNum = 0; while (rs.next()) { results.add(this.rowMapper.mapRow(rs, rowNum++)); } return results; }
另外 我得说明一下
queryForObject有两个,我们刚才使用的是第二个,只能查一行记录的一个字段
但是第一个queryForObject可以查到一条数据里的所有字段,并返还一个map。
public <T> T queryForObject(String sql, RowMapper<T> rowMapper) throws DataAccessException { List<T> results = query(sql, rowMapper); return DataAccessUtils.requiredSingleResult(results); } public <T> T queryForObject(String sql, Class<T> requiredType) throws DataAccessException { return queryForObject(sql, getSingleColumnRowMapper(requiredType)); }
至于别的queryForList,跳到第一个queryForObject里。queryForObject(String sql, RowMapper<T> rowMapper)
queryForInt会跳到第二个queryForObject。 queryForObject(String sql, Class<T> requiredType)
我们看第二个queryForObject,它传递的是SingleColumnRowMapper
queryForList最后传递的是ColumnMapRowMapper,它与SingleColumnRowMapper是兄弟,都实现而了RowMapper接口。
JdbcTemplate源码解析的更多相关文章
- Spring源码解析-JdbcTemplate
JdbcTemplate类图 从类继承关系上来看,JdbcTemplate继承了基类JdbcAccessor和接口类JdbcOperation,在基类JdbcAccessor的设计中,对DataSou ...
- spring事务源码解析
前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...
- Spring系列(六):Spring事务源码解析
一.事务概述 1.1 什么是事务 事务是一组原子性的SQL查询,或者说是一个独立的工作单元.要么全部执行,要么全部不执行. 1.2 事务的特性(ACID) ①原子性(atomicity) 一个事务必须 ...
- Spring源码解析系列汇总
相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...
- SpringJDBC源码解析
读完本篇文章需要很长很长时间.... 传统JDBC 相信大家对传统的jdbc已经很熟悉了,无非就是下面这个流程 1234567891011 //1.加载驱动程序Class.forName(" ...
- 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新
本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...
- 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新
[原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...
- 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新
上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...
- 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例
前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...
随机推荐
- Android简易实战教程--第三十五话《音乐播放》
已经好几天不更新博客了,今天轻松一点模拟个简单的"音乐播放器".1分钟看完~ 整个简单布局,加几个控制按钮: <LinearLayout xmlns:android=&quo ...
- Spark核心类:SQLContext和DataFrame
http://blog.csdn.net/pipisorry/article/details/53320669 pyspark.sql.SQLContext Main entry point for ...
- Maven仓库概述
什么是Maven仓库 在Maven世界中,任何一个依赖.插件或项目构建的输出,都可以称为构建.由于Maven引入了坐标机制,任何一个构建都可以由其坐标唯一标识.坐标是一个构建在Maven世界中的逻辑表 ...
- Oracle EBS各个模块日志收集的方法
MSCA(Mobile Supply Chain Application)日志的收集 Reference Note:338291.1 - Howto Enable WMS / MSCA Logging ...
- 19 Handler 子线程向主线程发送信息
案例一 Message创建三种方法: package com.example.day19_handler_demo1; import android.os.Bundle; import android ...
- EBS总账(GL)模块常用表
select * from gl_sets_of_books 总帐 select * from gl_code_combinations gcc wheregcc.summary_flag='Y ...
- iOS常见控件的基本使用
UI相关类继承关系 UIView 常见属性和方法 UIView属性 UIView方法 UIControl 常用控件 UIImageView 图片显示控件android ImageView UISlid ...
- Matlab:如何查找给定目录下的文件
我们有很多目录,每个目录下都有些有用的文件,比如图像文件,如何自动的扫描这些文件呢? 可以使用dir函数来完成这个任务. 比如假设给定目录 baseDir,它是一个字符串,包含的是某个目录,例如'./ ...
- JDBC的java驱动安装
首先登陆mysql.com官方网站,download-->选中下面的community–>mysql connentor-->然后选中下面与平台无关的zip包,一般是第二个,完成下载 ...
- 插件占坑,四大组件动态注册前奏(三) 系统BroadCast的注册发送流程
转载请注明出处:http://blog.csdn.net/hejjunlin/article/details/52204143 前言:为什么要了解系统Activity,Service,BroadCas ...