先写一个测试代码

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源码解析的更多相关文章

  1. Spring源码解析-JdbcTemplate

    JdbcTemplate类图 从类继承关系上来看,JdbcTemplate继承了基类JdbcAccessor和接口类JdbcOperation,在基类JdbcAccessor的设计中,对DataSou ...

  2. spring事务源码解析

    前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...

  3. Spring系列(六):Spring事务源码解析

    一.事务概述 1.1 什么是事务 事务是一组原子性的SQL查询,或者说是一个独立的工作单元.要么全部执行,要么全部不执行. 1.2 事务的特性(ACID) ①原子性(atomicity) 一个事务必须 ...

  4. Spring源码解析系列汇总

    相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...

  5. SpringJDBC源码解析

    读完本篇文章需要很长很长时间.... 传统JDBC 相信大家对传统的jdbc已经很熟悉了,无非就是下面这个流程 1234567891011 //1.加载驱动程序Class.forName(" ...

  6. 【原】Android热更新开源项目Tinker源码解析系列之三:so热更新

    本系列将从以下三个方面对Tinker进行源码解析: Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Android热更新开源项目Tinker源码解析系列之二:资源文件热更新 A ...

  7. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  8. 【原】Android热更新开源项目Tinker源码解析系列之二:资源文件热更新

    上一篇文章介绍了Dex文件的热更新流程,本文将会分析Tinker中对资源文件的热更新流程. 同Dex,资源文件的热更新同样包括三个部分:资源补丁生成,资源补丁合成及资源补丁加载. 本系列将从以下三个方 ...

  9. 多线程爬坑之路-Thread和Runable源码解析之基本方法的运用实例

    前面的文章:多线程爬坑之路-学习多线程需要来了解哪些东西?(concurrent并发包的数据结构和线程池,Locks锁,Atomic原子类) 多线程爬坑之路-Thread和Runable源码解析 前面 ...

随机推荐

  1. Windows下的DOM操作

    前言:这学期学Linux之后,发现用命令行的方式来操作电脑还是很好玩的~写这篇随笔来总结下Windows环境下通过DOS来执行命令的操作,希望能帮到您~自己忘记的时候能回来复习emmm 启动DOS环境 ...

  2. jQuery 遍历 – 后代

    后代是子.孙.曾孙等等. 通过 jQuery,您能够向下遍历 DOM 树,以查找元素的后代. 向下遍历 DOM 树 下面是两个用于向下遍历 DOM 树的 jQuery 方法: children() f ...

  3. Python3 日期和时间

    Python 程序能用很多方式处理日期和时间,转换日期格式是一个常见的功能. Python 提供了一个 time 和 calendar 模块可以用于格式化日期和时间. 时间间隔是以秒为单位的浮点小数. ...

  4. ubuntu初始化python3+postgresql+uwsgi+nginx+django

    一. postgresql 数据库 安装 apt-get update apt-get install postgresql 进入psql客户端 sudo -u postgres psql 创建数据库 ...

  5. ViewPager滑动后,可移动的Imageview会回到初始化的位置

    知乎看到的原文http://www.zhihu.com/question/37398770?sort=created ViewPager滑动后,可移动的Imageview会回到初始化的位置? < ...

  6. Shell脚本了解

    一.什么是Shell Shell 是一个用C语言编写的程序,它是用户使用Linux的桥梁.Shell既是一种命令语言,又是一种程序设计语言. Shell 是指一种应用程序,这个应用程序提供了一个界面, ...

  7. 使用redis构建文章投票系统

    首先,我得说明这篇博客基本上就是<<redis in action>>第一章内容的读书笔记. 需求 首先,说明一下,我们的需求 用户可以发表文章,发表时,自己就默认的给自己的文 ...

  8. 使用Dialog实现全局Loading加载框

    Dialog实现全局Loading加载框 很多人在实现Loading加载框的时候,都是在当前的页面隐藏一个Loading布局,需要加载的时候,显示出来,加载完再隐藏 使用Dialog实现Loading ...

  9. 带你深入理解STL之RBTree

    最近一直忙于校招的笔试,STL的深入理解系列也耽搁了好几天,再加上!红黑树真的是超级超级难理解,超级超级复杂,参考了好多博客上的大神的理解才稍微明白一点,勉强入个门,下面请以一个菜鸟的角度跟着我一起学 ...

  10. [ExtJS5学习笔记]第三十节 sencha extjs 5表格gridpanel分组汇总

    本文地址:http://blog.csdn.net/sushengmiyan/article/details/42240531 本文作者:sushengmiyan ------------------ ...