JdbcTemplate源码解析

网友投稿 229 2022-10-30


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> list = new ArrayList>(); // 一定要用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 u = new HashMap(); u.put("id", rs.getString("id")); u.put("description", rs.getString("description")); list.add(u); } }); for ( Map u :list) { System.out.println(u.get("description")); } //HERE IS A SIMPLE EXAMPLE //EXAMPLE EXAMPLE }}

前期构造方法的调用

我们能追踪到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. *

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 { 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 query(String sql, PreparedStatementSetter pss, ResultSetExtractor 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 query( PreparedStatementCreator psc, final PreparedStatementSetter pss, final ResultSetExtractor 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() { 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 execute(PreparedStatementCreator psc, PreparedStatementCallback 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() { 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> list = new ArrayList>(); // 一定要用final定义 Object[] params = new Object[] { 1 }; jt.query(sql, params, new RowCallbackHandler() { @Override public void processRow(ResultSet rs) throws SQLException { Map u = new HashMap(); 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 extractData(ResultSet rs) throws SQLException { List results = (this.rowsExpected > 0 ? new ArrayList(this.rowsExpected) : new ArrayList()); int rowNum = 0; while (rs.next()) { results.add(this.rowMapper.mapRow(rs, rowNum++)); } return results; }

另外 我得说明一下

queryForObject有两个,我们刚才使用的是第二个,只能查一行记录的一个字段

但是第一个queryForObject可以查到一条数据里的所有字段,并返还一个map。

public T queryForObject(String sql, RowMapper rowMapper) throws DataAccessException { List results = query(sql, rowMapper); return DataAccessUtils.requiredSingleResult(results); } public T queryForObject(String sql, Class requiredType) throws DataAccessException { return queryForObject(sql, getSingleColumnRowMapper(requiredType)); }

至于别的queryForList,跳到第一个queryForObject里。queryForObject(String sql, RowMapper rowMapper)

queryForInt会跳到第二个queryForObject。 queryForObject(String sql, Class requiredType)

我们看第二个queryForObject,它传递的是SingleColumnRowMapper

queryForList最后传递的是ColumnMapRowMapper,它与SingleColumnRowMapper是兄弟,都实现而了RowMapper接口。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:how tomcat works 读书笔记九 Session管理
下一篇:java实现动态图片效果
相关文章

 发表评论

暂时没有评论,来抢沙发吧~