当前位置:首页 > 开发 > 编程语言 > Spring > 正文

Spring源码学习-JdbcTemplate queryForObject

发表于: 2014-05-09   作者:bylijinnan   来源:转载   浏览:
摘要: JdbcTemplate中有两个可能会混淆的queryForObject方法: 1. Object queryForObject(String sql, Object[] args, Class requiredType) 2. Object queryForObject(String sql, Object[] args, RowMapper rowMapper) 第1个方法是只查
JdbcTemplate中有两个可能会混淆的queryForObject方法:
1.
Object queryForObject(String sql, Object[] args, Class requiredType)
2.
Object queryForObject(String sql, Object[] args, RowMapper rowMapper)

第1个方法是只查一列的,参数“requiredType”不可以是自定义的类
如果要把查询结果封装为自定义的类,需要采用第2个方法

例如:
	//只查询一列:name
	String sql = "SELECT NAME FROM CUSTOMER WHERE CUST_ID = ?";
 
	String name = (String)getJdbcTemplate().queryForObject(
			sql, new Object[] { custId }, String.class);
 
	return name;
	
	//查询返回自定义的类
	String sql = "SELECT * FROM CUSTOMER WHERE CUST_ID = ?";
 
	Customer customer = (Customer)getJdbcTemplate().queryForObject(
			sql, new Object[] { custId }, 
			new BeanPropertyRowMapper(Customer.class));
 
	return customer;


分析一下第2个方法的源码

先看看BeanPropertyRowMapper
其实不用看源码都知道思路了:
new BeanPropertyRowMapper(Customer.class)
mapRow(ResultSet rs, int rowNumber)
遍历ResultSet,每一行对应一个Customer对象
遍历每一行时,顺序遍历各列,取得该列的值通过反射调用对应的setter方法,赋值给到Customer对象
注意到无论是取列名还是取列的值,都是通过index(1~columnCount)来取的

public T mapRow(ResultSet rs, int rowNumber) throws SQLException {
		T mappedObject = BeanUtils.instantiate(this.mappedClass);
		BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);

		ResultSetMetaData rsmd = rs.getMetaData();
		int columnCount = rsmd.getColumnCount();
		Set<String> populatedProperties = (isCheckFullyPopulated() ? new HashSet<String>() : null);

		for (int index = 1; index <= columnCount; index++) {
			String column = JdbcUtils.lookupColumnName(rsmd, index);
			PropertyDescriptor pd = this.mappedFields.get(column.replaceAll(" ", "").toLowerCase());
			if (pd != null) {
				try {
					Object value = getColumnValue(rs, index, pd);
				    bw.setPropertyValue(pd.getName(), value);
					if (populatedProperties != null) {
						populatedProperties.add(pd.getName());
					}
				}
			}
		}

		if (populatedProperties != null && !populatedProperties.equals(this.mappedProperties)) {
			throw new InvalidDataAccessApiUsageException("Given ResultSet does not contain all fields " +
					"necessary to populate object of class [" + this.mappedClass + "]: " + this.mappedProperties);
		}

		return mappedObject;
	}

注意到mapRow方法里面,populatedProperties变量是用来检查是否所有的property都正确地实现了赋值。这个功能在2.5.5的版本是没有的。在3.0.0的版本有,且可以通过函数来配置是否作这一项检查:
public BeanPropertyRowMapper(Class<T> mappedClass, boolean checkFullyPopulated)

再看看BeanPropertyRowMapper的构造函数:
	public BeanPropertyRowMapper(Class<T> mappedClass) {
		initialize(mappedClass);
	}
	
	protected void initialize(Class<T> mappedClass) {
		this.mappedClass = mappedClass;
		
		/*保存field
		注意到对于驼峰式命名的field:
		例如,对于gameId, mappedFields 会同时保存"gameid"和"game_id"——注意全部变为小写了
		因此在sql语句中,
		select  id as game_id, name from game和
		select  id as gameId, name from game的效果是一样的
		*/
		this.mappedFields = new HashMap<String, PropertyDescriptor>();
		
		//保存property
		this.mappedProperties = new HashSet<String>();

		PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);
		for (PropertyDescriptor pd : pds) {
			if (pd.getWriteMethod() != null) {
				this.mappedFields.put(pd.getName().toLowerCase(), pd);
				String underscoredName = underscoreName(pd.getName());
				if (!pd.getName().toLowerCase().equals(underscoredName)) {
					this.mappedFields.put(underscoredName, pd);
				}
				this.mappedProperties.add(pd.getName());
			}
		}
	}
	
	//1. "game", return "game", unchanged
	//2. "gameId", return "game_id"
	private String underscoreName(String name) {
		//......
	}
	


看完了BeanPropertyRowMapper,回到queryForObject
调用过程:
queryForObject(String sql, Object[] args, RowMapper<T> rowMapper)
query(sql, args, new RowMapperResultSetExtractor<T>(rowMapper, 1))
query(sql, newArgPreparedStatementSetter(args), resultSetExtractor)
query(new SimplePreparedStatementCreator(sql), argPreparedStatementSetter, resultSetExtractor)
query(PreparedStatementCreator psc, final PreparedStatementSetter argPreparedStatementSetter, final ResultSetExtractor<T>  resultSetExtractor)
整个过程跟JdbcTemplate的batchUpdate方法是类似的
1.
对于参数'sql',它最终的作用是生成一个PreparedStatement:
connection.prepareStatement(sql);
2.
对sql语句中“?”的赋值,是通过PreparedStatementSetter
需要两个数据:
一是“?”所对应的值,也就是Object[] args
二是PreparedStatement
对第一个数据:
args是在ArgPreparedStatementSetter的构造函数中传入
对第二个数据:
在最后的query方法中,把创建好的PreparedStatement作为匿名内部类PreparedStatementCallback.doInPreparedStatement方法的参数
最后得以传给ArgPreparedStatementSetter:
public <T> T query(
			PreparedStatementCreator psc, final PreparedStatementSetter argPreparedStatementSetter, final ResultSetExtractor<T> rse) {
					return execute(psc, 
										new PreparedStatementCallback<T>() {
											public T doInPreparedStatement(PreparedStatement ps) {
												//...
												argPreparedStatementSetter.setValues(ps);
												ResultSet rsToUse = ps.executeQuery();
												return rse.extractData(rsToUse);
												//...
											}
										}
					);
}


ArgPreparedStatementSetter的setValues方法:
public void setValues(PreparedStatement ps) throws SQLException {
		if (this.args != null) {
			for (int i = 0; i < this.args.length; i++) {
				Object arg = this.args[i];
				
				/*
					doSetValue方法会根据arg的实际类型去调用ps.setSpecificType(index, arg)方法,例如:
					if (isStringValue(arg.getClass())) {
						ps.setString(paramIndex, arg.toString());
					}
				*/
				doSetValue(ps, i + 1, arg);
			}
		}
	}


最后看一下RowMapperResultSetExtractor
它有两个字段:
private final RowMapper<T> rowMapper;
private final int rowsExpected;
因为是queryForObject,因此rowsExpected=1
extractData方法很直观,是在上面的doInPreparedStatement调用的:
	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;
		
		//rs.next()迭代每一行
		while (rs.next()) {
			results.add(this.rowMapper.mapRow(rs, rowNum++));
		}
		return results;
	}

从代码可看出,如果ResultSet为空,返回的List也不会是null,而是empty list
这也符合《Effective Java》的提议:返回类型是集合类型时,返回空集合而不是null







Spring源码学习-JdbcTemplate queryForObject

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

编辑推荐
闲着没事,看看源码也是一种乐趣! java操作数据库的基本步骤都是类似的: 1. 建立数据库连接 2. 创
JdbcTemplate将我们使用的JDBC的流程封装起来,包括了异常的捕捉、SQL的执行、查询结果的转换等等。
JdbcTemplate将我们使用的JDBC的流程封装起来,包括了异常的捕捉、SQL的执行、查询结果的转换等等。
一、spring jdbctemplate 使用java的jdbc我们需要的做的步骤有: 1、指定数据库连接参数; (配置)
a) Spring 初始化的时候首先要运行的类为: org.springframework.web.context. ContextLoaderListen
最近在看spring的源码,担心忘掉了,打个记号,也请大家一起指正其中的错误,防止走歪路。 从xml配
这一段我主要去看看spring容器的创建,继续(一)的代码,我们看 if (this.context == null) { this
众所周知,Spring以其强大而又灵活的IoC管理功能著称。IoC本质上是通过依赖注入DI的方式实现组件之间
转自 http://www.iteye.com/magazines/72 本文主要以 spring ioc 容器基本代码骨架为切入点,理解 i
a) Spring 初始化的时候首先要运行的类为: org.springframework.web.context. ContextLoaderListen
版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号