【Spring】利⽤spring的JdbcTemplate查询返回结果映射到⾃
定义类型
// org.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.String("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 = Log(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 = PropertyDescriptors(mappedClass);//org.springframework.beans.BeanUtils
for (PropertyDescriptor pd : pds) {
if (pd.getWriteMethod() != null) {
this.mappedFields.Name().toLowerCase(), pd); // key:全⼩写
String underscoredName = Name()); // ex:bookName --> book_name
if (!pd.getName().toLowerCase().equals(underscoredName)) {
//set与其属性命名的不⼀致;⽅法是setBookName ⽽变量是book_name; ⼤致是这意思
this.mappedFields.put(underscoredName, pd);
}
this.mappedProperties.Name()); //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);
}
}
String();
}
注意:
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⾃带的吧。
虽然不清楚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)
*/write的返回值
public RowMapperResultSetExtractor(RowMapper<T> rowMapper, int rowsExpected) {
}
@Override
public List<T> extractData(ResultSet rs) throws SQLException {
List<T> results = (wsExpected > 0 ? new ArrayList<T>(wsExpected) : new ArrayList<T>());
int rowNum = 0;
while (rs.next()) {
results.wMapper.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); //实例化⼀个新对象;就是wInstance();
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(mappedObject); //这也是spring⾃⼰的,有兴趣可以看。在这主要就是类似method.invoke(…)
initBeanWrapper(bw); //这是个空⽅法,⽤于⼦类扩展
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = ColumnCount(); // 与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 = (placeAll(" ", "").toLowerCase()); // 从缓存中得到⽅法信息if (pd != null) {
try {
Object value = getColumnValue(rs, index, pd); // 得到每列的值。为什么要pd:因为要根据类型获取相应的值。
if (logger.isDebugEnabled() && rowNumber == 0) {
logger.debug("Mapping column '" + column + "' to property '" +
}
try {
bw.Name(), 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.Name());
}
}
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 {
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
wInstance();
}
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);
}
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论