// org.springframework.jdbc.core.JdbcTemplate 中的查询方法基本都有支持参数RowMapper<T> rowMapper的重载方法。下面只是随便举例2个,还有很多

public <T> List<T> query(String sql, Object[] args, RowMapper<T> rowMapper) throws DataAccessException {
...
}; public <T> T queryForObject(String sql, Object[] args, RowMapper<T> rowMapper) throws DataAccessException {
...
}; //demo01
List<Person> person = jdbcTemplate.query(sql, new RowMapper<Person>() {
@Override
public Person mapRow(ResultSet rs, int i) throws SQLException {
Person p = new Person(); //特别需要new,不然如果结果集是list就只有1个对象
p.setId(rs.getString("id"));
return p;
}}); //特别 如果如demo写,很麻烦要set很多。此时spring提供了一个RowMapper的实现类BeanPropertyRowMapper
//demo02
List<Person> person = jdbcTemplate.query(sql, new BeanPropertyRowMapper(Person.class));

这篇博客的主要目的是分析BeanPropertyRowMapper的实现是怎么样。

先,之前也在用jdbcTemplate来查询,但都是用demo01的方式。上周末本来想写一个BaseRowMapper(其实就是BeanPropertyRowMapper),但冬(lan)眠(si)去了。

在今天(2016-11-07)上班的时候又用到了,于是就打算写。但看了下RowMapper的结构(ctrl+t)发现了BeanPropertyRowMapper。这不就是我想要的吗,于是决定去看下和自己的想法有什么差别。

现在,我还没看过源码,先说我周末大致想到的:

1、肯定要用反射,根据sql的列名/别名去找到对应的set;

以sql返回结果集的列为准,sql有的必须有set,有set不一定sql有返回。

2、反射效率低,如果我sql返回的是list,不应该每行都要根据反射去找set。而是应该在第一次找的时候,把列名/别名对应的set缓存起来,以后直接取;

第一次(第一行结果)用列名/别名(不区分大小写),找到set,并缓存;之后直接用别名/列名去找set,节约反射查找set消耗的时间。

注:以下都是BeanPropertyRowMapper源码分析

一、缓存自定义类型的set方法

// BeanPropertyRowMapper的成员变量

/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass()); /** The class we are mapping to ;要映射的class*/
private Class<T> mappedClass; /** Whether we're strictly validating; 是否严格映射bean和sql结果 */
private boolean checkFullyPopulated = false; /** Whether we're defaulting primitives when mapping a null value */
private boolean primitivesDefaultedForNullValue = false; /** Map of the fields we provide mapping for;映射字段的set方法 */
private Map<String, PropertyDescriptor> mappedFields; /** Set of bean properties we provide mapping for ;需要映射的字段*/
private Set<String> mappedProperties;
/**
* Create a new BeanPropertyRowMapper, accepting unpopulated properties in the target bean.
* <p>Consider using the {@link #newInstance} factory method instead,which allows for specifying the mapped type once only.
* @param mappedClass the class that each row should be mapped to
*/
public BeanPropertyRowMapper(Class<T> mappedClass) {
initialize(mappedClass);
} /**
* Create a new BeanPropertyRowMapper.
* @param mappedClass the class that each row should be mapped to
* @param checkFullyPopulated whether we're strictly validating that all bean properties have been mapped from corresponding database fields
*/
public BeanPropertyRowMapper(Class<T> mappedClass, boolean checkFullyPopulated) {
initialize(mappedClass);
this.checkFullyPopulated = checkFullyPopulated; //是否严格验证,所有bean属性已经从对应的数据库字段映射。
}

在BeanPropertyRowMapper提供的2中构造函数中,区别只在于是否严格映射bean和sql结果(默认是false,不严格映射)。

/**
* Initialize the mapping metadata for the given class.
* @param mappedClass the mapped class.
*/
protected void initialize(Class<T> mappedClass) {
this.mappedClass = mappedClass;
this.mappedFields = new HashMap<String, PropertyDescriptor>();
this.mappedProperties = new HashSet<String>();
//以上都是设置/初始化成员变量 PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(mappedClass);//org.springframework.beans.BeanUtils
   for (PropertyDescriptor pd : pds) {
if (pd.getWriteMethod() != null) {
this.mappedFields.put(pd.getName().toLowerCase(), pd); // key:全小写
String underscoredName = underscoreName(pd.getName()); // ex:bookName --> book_name
if (!pd.getName().toLowerCase().equals(underscoredName)) {
//set与其属性命名的不一致;方法是setBookName 而变量是book_name; 大致是这意思
this.mappedFields.put(underscoredName, pd);
}
this.mappedProperties.add(pd.getName()); //key:与mappedFields不一样
}
}
} /**
* Convert a name in camelCase to an underscored name in lower case.
* Any upper case letters are converted to lower case with a preceding underscore.
* @param name the string containing original name
* @return the converted name
*/
private String underscoreName(String name) { //ex: bookName --> book_name
if (!StringUtils.hasLength(name)) {
return "";
}
StringBuilder result = new StringBuilder();
result.append(name.substring(0, 1).toLowerCase());
for (int i = 1; i < name.length(); i++) {
String s = name.substring(i, i + 1);
String slc = s.toLowerCase();
if (!s.equals(slc)) { //大写字母转换成 _+小写
result.append("_").append(slc);
}
else {
result.append(s);
}
}
return result.toString();
}

注意:

1、Map<String, PropertyDescriptor> mappedFields的key与Set<String> mappedProperties的value保存的并不一定是一样的:

mappedFields的key是set方法的全小写/带下划线的全小写,而mappedProperties的是set方法名。

ex: private String bookName; public void setBookName(..)

mappedFields:bookname/book_name   mappedProperties:bookName

2、关于underscoreName()的转换,效果就是: 大写 –> _+小写。 初略的认为是转换成员变量与对应set命名不一样的问题。

从BeanPropertyRowMapper.initialize(…)结合自己的设想:

1、先根据class缓存了所有的set方法,并保存在了mappedFields。

即当初我想要的效果,不过我想的可能是在执行第一次的时候(mapRow方法中)。而BeanPropertyRowMapper则是在构造的时候就缓存了。

2、我所没想到的underscoreName(),可能我项目并没存在命名问题。成员变量的set/get都是工具自己生成的,命名也是采取的驼峰式(不管是java还是sql的别名)

3、对于PropertyDescriptor的获取,spring还是用了自己的获取。我的话不确定,毕竟对反射也不是很熟悉。

但看过一篇文章: java反射的性能问题 ,如果我自己写的话,估计还是会用java自带的吧。

虽然不清楚spring和java自带的区别及效率,但我觉得应该spring比较好吧。不然spring直接用jdk的就行了,没必要自己再写。

以上,BeanPropertyRowMapper在构造的时候已经有了反射,接下来就是把每行的值写到对应的属性中。

二、写入sql结果集的值到对应bean属性

// jdbcTemplate调用RowMapper.mapRow(...)
public class RowMapperResultSetExtractor<T> implements ResultSetExtractor<List<T>> { private final RowMapper<T> rowMapper; private final int rowsExpected; /**
* Create a new RowMapperResultSetExtractor.
* @param rowMapper the RowMapper which creates an object for each row
*/
public RowMapperResultSetExtractor(RowMapper<T> rowMapper) {
this(rowMapper, 0);
} /**
* Create a new RowMapperResultSetExtractor.
* @param rowMapper the RowMapper which creates an object for each row
* @param rowsExpected the number of expected rows
* (just used for optimized collection handling)
*/
public RowMapperResultSetExtractor(RowMapper<T> rowMapper, int rowsExpected) {
Assert.notNull(rowMapper, "RowMapper is required");
this.rowMapper = rowMapper;
this.rowsExpected = rowsExpected;
} @Override
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++));//调用核心; 1、每行的rowMapper是同一个对象,所以可以缓存映射关系 2、mapRow为什么是new对象也是因为这个。不然list.add的是同一个return对象。
}
return results;
}
}
// BeanPropertyRowMapper中mapRow的实现
@Override
public T mapRow(ResultSet rs, int rowNumber) throws SQLException {
Assert.state(this.mappedClass != null, "Mapped class was not specified");
T mappedObject = BeanUtils.instantiate(this.mappedClass); //实例化一个新对象;就是class.newInstance();
 
    BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject); //这也是spring自己的,有兴趣可以看。在这主要就是类似method.invoke(…)
initBeanWrapper(bw); //这是个空方法,用于子类扩展
     ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount(); // 与rsmd都是sql结果集的信息
Set<String> populatedProperties = (isCheckFullyPopulated() ? new HashSet<String>() : null);//是否严格映射bean和sql for (int index = 1; index <= columnCount; index++) {
String column = JdbcUtils.lookupColumnName(rsmd, index); // 得到sql的列名/别名
PropertyDescriptor pd = this.mappedFields.get(column.replaceAll(" ", "").toLowerCase()); // 从缓存中得到方法信息
if (pd != null) {
try {
Object value = getColumnValue(rs, index, pd); // 得到每列的值。为什么要pd:因为要根据类型获取相应的值。
if (logger.isDebugEnabled() && rowNumber == 0) {
logger.debug("Mapping column '" + column + "' to property '" +
pd.getName() + "' of type " + pd.getPropertyType());
}
try {
bw.setPropertyValue(pd.getName(), value); // 设置结果
}
catch (TypeMismatchException e) {
if (value == null && primitivesDefaultedForNullValue) {
logger.debug("Intercepted TypeMismatchException for row " + rowNumber +
" and column '" + column + "' with value " + value +
" when setting property '" + pd.getName() + "' of type " + pd.getPropertyType() +
" on object: " + mappedObject);
}
else {
throw e;
}
}
if (populatedProperties != null) { //严格映射的逻辑判断
populatedProperties.add(pd.getName());
}
}
catch (NotWritablePropertyException ex) {
throw new DataRetrievalFailureException(
"Unable to map column " + column + " to property " + pd.getName(), ex);
}
}
} 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;
}
public static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException {
Assert.notNull(clazz, "Class must not be null");
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
return clazz.newInstance();
}
catch (InstantiationException ex) {
throw new BeanInstantiationException(clazz, "Is it an abstract class?", ex);
}
catch (IllegalAccessException ex) {
throw new BeanInstantiationException(clazz, "Is the constructor accessible?", ex);
}
}
/**
* Initialize the given BeanWrapper to be used for row mapping.
* To be called for each row.
* <p>The default implementation is empty. Can be overridden in subclasses.
* @param bw the BeanWrapper to initialize
*/
protected void initBeanWrapper(BeanWrapper bw) {
}
protected Object getColumnValue(ResultSet rs, int index, PropertyDescriptor pd) throws SQLException {
return JdbcUtils.getResultSetValue(rs, index, pd.getPropertyType());
} // JdbcUtils.class 根据set参数的类型,决定sql返回值调用类型
public static Object getResultSetValue(ResultSet rs, int index, Class<?> requiredType) throws SQLException {
if (requiredType == null) {
return getResultSetValue(rs, index);
} Object value; // Explicitly extract typed value, as far as possible.
if (String.class.equals(requiredType)) {
return rs.getString(index);
}
else if (boolean.class.equals(requiredType) || Boolean.class.equals(requiredType)) {
value = rs.getBoolean(index);
}
else if (byte.class.equals(requiredType) || Byte.class.equals(requiredType)) {
value = rs.getByte(index);
}
else if (short.class.equals(requiredType) || Short.class.equals(requiredType)) {
value = rs.getShort(index);
}
else if (int.class.equals(requiredType) || Integer.class.equals(requiredType)) {
value = rs.getInt(index);
}
else if (long.class.equals(requiredType) || Long.class.equals(requiredType)) {
value = rs.getLong(index);
}
else if (float.class.equals(requiredType) || Float.class.equals(requiredType)) {
value = rs.getFloat(index);
}
else if (double.class.equals(requiredType) || Double.class.equals(requiredType) ||
Number.class.equals(requiredType)) {
value = rs.getDouble(index);
}
else if (BigDecimal.class.equals(requiredType)) {
return rs.getBigDecimal(index);
}
else if (java.sql.Date.class.equals(requiredType)) {
return rs.getDate(index);
}
else if (java.sql.Time.class.equals(requiredType)) {
return rs.getTime(index);
}
else if (java.sql.Timestamp.class.equals(requiredType) || java.util.Date.class.equals(requiredType)) {
return rs.getTimestamp(index);
}
else if (byte[].class.equals(requiredType)) {
return rs.getBytes(index);
}
else if (Blob.class.equals(requiredType)) {
return rs.getBlob(index);
}
else if (Clob.class.equals(requiredType)) {
return rs.getClob(index);
}
else {
// Some unknown type desired -> rely on getObject.
if (getObjectWithTypeAvailable) {
try {
return rs.getObject(index, requiredType);
}
catch (AbstractMethodError err) {
logger.debug("JDBC driver does not implement JDBC 4.1 'getObject(int, Class)' method", err);
}
catch (SQLFeatureNotSupportedException ex) {
logger.debug("JDBC driver does not support JDBC 4.1 'getObject(int, Class)' method", ex);
}
catch (SQLException ex) {
logger.debug("JDBC driver has limited support for JDBC 4.1 'getObject(int, Class)' method", ex);
}
}
// Fall back to getObject without type specification...
return getResultSetValue(rs, index);
} // Perform was-null check if necessary (for results that the JDBC driver returns as primitives).
return (rs.wasNull() ? null : value);
}

基本核心的全部源码就在这;但有个核心没看也没写出源码就是:

BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject);
  bw.setPropertyValue(pd.getName(), value);

其目的个人理解就是:method.invoke(…)

感觉总体来说,和我的设想是一样的。但只是实现上效率的区别。就在PropertyDescriptor、Method、及set调用。

个人话应该不会去写PropertyDescriptor的获取,就直接用jdk提供的了。而set的调用也是一样,直接用PropertyDescriptor找到set的Method,然后Method.invoke(…);

(所以个人最初想的时候缓存的其实是Method而不是PropertyDescriptor)

值的获取也没太大区别,都是判断set的参数类型,然后调用ResultSet的对应方法。

总结:

考虑到了优化最明显的查找set方法小号。但其余的优化还不知道:不知道哪些还可以优化、怎么优化,有待学习。

(有待不知是哪年了,周围的都没注重技术的。都只要求业务功能的代码,管你怎么写的,也不愿给你时间、资源去学习,就知道催进度。)

ps:

哎,回到家大概19.50。从8.10来分开始边写边看BeanPropertyRowMapper,感觉也没写什么而且那源码核心的复杂的BeanWrapper还没看。单独是BeanPropertyRowMapper其实几分钟就看完了。结果写完就22.40左右了。

要说学到了什么吗,真的不知道。项目实际接触的都是业务代码,没有任何技术可言(不管是旧技术还是新技术)。

心好累…一直想转行,可惜一无是处,哎!

【Spring】利用spring的JdbcTemplate查询返回结果映射到自定义类型的更多相关文章

  1. 关于Spring Data JPA 多表查询 返回自定义Vo的问题记录

    这两天开了一个新项目,使用SpringBoot+SpringData,  刚做了一个小功能,都是一张表的操作没什么问题,今天设计到了两张表联查,两张表各取了几个字段,组合成了一个vo, 当我用原生sq ...

  2. JdbcTemplate查询返回JavaBean的几种方法

    关于JdbcTemplate的官方描述如下: org.springframework.jdbc.core.JdbcTemplate 大约的讲,将JdbcTemplate返回的list结果集生成Java ...

  3. spring:利用Spring AOP 使日志输入与方法分离

    对方法进行日志输出是一种很常见的功能.传统的做法是把输出语句写在方法体的内部,在调用该方法时,用输入语句输出信息来记录方法的执行! 1.先写一个普通类: package com.importnew; ...

  4. hibernate查询返回一个list ,Date类型追加数据

    public Pagination getLookPage(BeanPatrolScheduling beanPatrolScheduling, int pageNo, int pageSize) { ...

  5. Spring JdbcTemplate 查询结果集Map反向生成Java实体(转)

    原文地址:Spring JdbcTemplate 查询结果集Map反向生成Java实体 以前写过一篇文章吐槽过Spring JdbcTemplate的queryForList方法(参见:http:// ...

  6. spring jdbcTemplate query 返回值为null

    spring jdbcTemplate query 返回值为null 今天使用以下方法从数据库中查询数据,返回列表 public List<BookBean> getBooks(){ St ...

  7. Spring JdbcTemplate 查询出的Map,是如何产生大小写忽略的Key的?(转)

    原文地址:Spring JdbcTemplate 查询出的Map,是如何产生大小写忽略的Key的? 原始讨论组:用Spring JdbcTemplate 查询出的Map,是如何产生大小写忽略的Key的 ...

  8. 【Spring学习笔记-MVC-5】利用spring MVC框架,实现ajax异步请求以及json数据的返回

    作者:ssslinppp      时间:2015年5月26日 15:32:51 1. 摘要 本文讲解如何利用spring MVC框架,实现ajax异步请求以及json数据的返回. Spring MV ...

  9. [转] spring JdbcTemplate 查询,使用BeanPropertyRowMapper

    [From] http://blog.csdn.net/limenghua9112/article/details/45096437 应用: 使用Spring的JdbcTemplate查询数据库,获取 ...

随机推荐

  1. MainActivity中R为红色

    除了在gradle.properties中加入东西外还可能原因是在activity_main.xml 组件的设置有错误

  2. ARTS Week 1

    Oct 28,2019 ~ Nov 3,2019 Algorithm 本周的学习的算法是二分法.二分法可以用作查找即二分查找,也可以用作求解一个非负数的平方根等.下面主要以二分查找为例. 为了后续描述 ...

  3. BZOJ 1087 [SCOI2005]互不侵犯King(状压DP)

    题意:在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案.国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子.n<=9 思路:状压dp,dp[i][ ...

  4. SpringBoot Jpa 自定义查询

    SpringBoot Jpa 自定义查询 持久层Domain public interface BaomingDao extends JpaRepository<BaomingBean,Inte ...

  5. 第3章 JDK并发包(二)

    3.1.2 重入锁的好搭档:Condition条件 它和wait()和notify()方法的作用是大致相同的.但是wait()和notify()方法是和synchronized关键字合作使用的,而Co ...

  6. JavaWeb前置知识 : 动态和静态的区别、两种架构、常见状态码

    JavaWeb程序设计(一) : 前置知识 1.动态网页与静态网页的区别: a.不要和是否有"动感"混为一谈. b.是否随着时间.地点.用户操作的改变而改变 (例如 : 在百度上搜 ...

  7. MySQL :LAST_INSERT_ID()函数总结

    作用:当对table进行insert操作时,返回具有Auto_increment(自动增长)特性的属性列的最新值. 该函数的特点 1.每当断开本次连接之后又重新连接时,该函数的返回值会被重置为0. 2 ...

  8. Window10和Ubuntu 18.04双系统安装的引导问题解决

    作为码农 首先,建议了解下grub2的启动顺序和逻辑.可以参考这篇文章,grub.cfg详解. 从执行顺序倒推,如下如果全部执行成功,则会进入grub的启动菜单:如果最后一步,没有找到grub.cfg ...

  9. python之基础中的基础(二)

    1.字典 创建字典,alien_0={'color':'green','points':5}其中由一个又一个的“键-值”对组成. 访问键-值对相应的值,print(alien_0['color']), ...

  10. 【STM32H7教程】第61章 STM32H7的MDMA基础知识和HAL库API

    完整教程下载地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980 第61章       STM32H7的MDMA基础知识和HAL ...