Spring源码学习-JdbcTemplate queryForObject

7 篇文章 0 订阅
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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值