今天搭建服务器,使用DBUtils处理数据库的时候,出了个小问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public Device findById(String sn) throws SQLException {
String sql = "SELECT * FROM device WHERE sn=?";
QueryRunner runner = DBUtils.getQuerryRunner();
return runner.query(sql, new BeanHandler<>(Device.class), sn);
}
//定义业务Model定义
public class Device {
public String sn;
public String uuid;
public String name;
public String chargeAddr;
public String addr1;
public String addr2;
public String firmware;
public Float compensation;
public Long lastUpdateTime;
}

最终,runner.query(返回的结果,是一个空的Device对象,即其内部成员都是null值。 而当我把Device定义成标准的JavaBean对象的时候,才能够正常的返回结果。

那么,这是为什么呢? 源码是最好的老师…

查看DBUtils的源码

QueryRunner的源码位于:/commons-dbutils-1.7-sources.jar!/org/apache/commons/dbutils/QueryRunner.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public <T> T query(String sql, ResultSetHandler<T> rsh, Object... params) throws SQLException {
Connection conn = this.prepareConnection();
return this.<T>query(conn, true, sql, rsh, params);
}
private <T> T query(Connection conn, boolean closeConn, String sql, ResultSetHandler<T> rsh, Object... params)
throws SQLException {
......
PreparedStatement stmt = null;
ResultSet rs = null;
T result = null;
try {
stmt = this.prepareStatement(conn, sql);
this.fillStatement(stmt, params);
//执行SQL语句,得到ResultSet结果集
rs = this.wrap(stmt.executeQuery());
//使用传入的rsh来处理结果集
result = rsh.handle(rs);
} catch (SQLException e) {
this.rethrow(e, sql, params);
} finally {
try {
close(rs);
} finally {
close(stmt);
if (closeConn) {
close(conn);
}
}
}
return result;
}

我们在调用的时候,传入了BeanHandler,所以使用了BeanHandler来处理结果集,其源码位于:/commons-dbutils-1.7-sources.jar!/org/apache/commons/dbutils/handlers/BeanHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static final RowProcessor ROW_PROCESSOR = new BasicRowProcessor();
public BeanHandler(Class<? extends T> type) {
this(type, ArrayHandler.ROW_PROCESSOR);
}
...
public BeanHandler(Class<? extends T> type, RowProcessor convert) {
this.type = type;
this.convert = convert;
}
public T handle(ResultSet rs) throws SQLException {
return rs.next() ? this.convert.toBean(rs, this.type) : null;
}

可见,在得到结果集之后,使用ArrayHandler.ROW_PROCESSOR.toBean()的方法来处理结果集

BasicRowProcessor源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static final BeanProcessor defaultConvert = new BeanProcessor();
public BasicRowProcessor() {
this(defaultConvert);
}
public BasicRowProcessor(BeanProcessor convert) {
super();
this.convert = convert;
}
public <T> T toBean(ResultSet rs, Class<? extends T> type) throws SQLException {
return this.convert.toBean(rs, type);
}

最终,调用到 /commons-dbutils-1.7-sources.jar!/org/apache/commons/dbutils/BeanProcessor.java 来处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public <T> T toBean(ResultSet rs, Class<? extends T> type) throws SQLException {
//首先初始化一个type实例,在本例中,为Device实例
T bean = this.newInstance(type);
//开始填实例内容
return this.populateBean(rs, bean);
}
...
public <T> T populateBean(ResultSet rs, T bean) throws SQLException {
//首先获得bean的成员属性列表
PropertyDescriptor[] props = this.propertyDescriptors(bean.getClass());
/拿到结果集中包含的meta data数据,也就是我们的数据库列
ResultSetMetaData rsmd = rs.getMetaData();
int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
return populateBean(rs, bean, props, columnToProperty);
}
......

我们先来看bean属性成员列表的获取,入参为Class对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private PropertyDescriptor[] propertyDescriptors(Class<?> c)
throws SQLException {
// Introspector caches BeanInfo classes for better performance
BeanInfo beanInfo = null;
try {
//通过Introspector.sgetBeanInfo获得对应Class的BeanInfo
beanInfo = Introspector.getBeanInfo(c);
} catch (IntrospectionException e) {
throw new SQLException(
"Bean introspection failed: " + e.getMessage());
}
return beanInfo.getPropertyDescriptors();
}

我们来看下src.zip!/java/beans/Introspector.java的getBeanInfo()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public static BeanInfo getBeanInfo(Class<?> beanClass)
throws IntrospectionException
{
//显然isPackageAccessible()返回true,因此不走这里
if (!ReflectUtil.isPackageAccessible(beanClass)) {
return (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
}
ThreadGroupContext context = ThreadGroupContext.getContext();
BeanInfo beanInfo;
synchronized (declaredMethodCache) {
//一开始没有掉用过putBeanInfo,因此这里获取得到的为null
beanInfo = context.getBeanInfo(beanClass);
}
if (beanInfo == null) {
//最终走到这一步,构造beanClass的内省器,然后从该内省器中获取BeanInfo
beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();
synchronized (declaredMethodCache) {
context.putBeanInfo(beanClass, beanInfo);
}
}
return beanInfo;
}
......
private BeanInfo getBeanInfo() throws IntrospectionException {
// the evaluation order here is import, as we evaluate the
// event sets and locate PropertyChangeListeners before we
// look for properties.
BeanDescriptor bd = getTargetBeanDescriptor();
MethodDescriptor mds[] = getTargetMethodInfo();
EventSetDescriptor esds[] = getTargetEventInfo();
//这便是我们要找的PropertyDescriptor[]数组了,这个数组是什么时候写进来的呢?
PropertyDescriptor pds[] = getTargetPropertyInfo();
int defaultEvent = getTargetDefaultEventIndex();
int defaultProperty = getTargetDefaultPropertyIndex();
return new GenericBeanInfo(bd, esds, defaultEvent, pds,
defaultProperty, mds, explicitBeanInfo);
}

我们来深入看getTargetPropertyInfo方法,这个方法比较长,需要细心的看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
private PropertyDescriptor[] getTargetPropertyInfo() {
// Check if the bean has its own BeanInfo that will provide
// explicit information.
PropertyDescriptor[] explicitProperties = null;
if (explicitBeanInfo != null) {
explicitProperties = getPropertyDescriptors(this.explicitBeanInfo);
}
if (explicitProperties == null && superBeanInfo != null) {
// We have no explicit BeanInfo properties. Check with our parent.
addPropertyDescriptors(getPropertyDescriptors(this.superBeanInfo));
}
for (int i = 0; i < additionalBeanInfo.length; i++) {
addPropertyDescriptors(additionalBeanInfo[i].getPropertyDescriptors());
}
if (explicitProperties != null) {
// Add the explicit BeanInfo data to our results.
addPropertyDescriptors(explicitProperties);
} else {
//对于前面的Device.class,explicitBeanInfo、explicitProperties、additionalBeanInfo都为null,所以直接走到这里
// Apply some reflection to the current class.
// First get an array of all the public methods at this level
Method methodList[] = getPublicDeclaredMethods(beanClass);
//这里是重点了:获得BeanClass的所有public方法,然后依次分析
// Now analyze each method.
for (int i = 0; i < methodList.length; i++) {
Method method = methodList[i];
if (method == null) {
continue;
}
// skip static methods.
int mods = method.getModifiers();
if (Modifier.isStatic(mods)) {
continue;
}
String name = method.getName();
Class<?>[] argTypes = method.getParameterTypes();
Class<?> resultType = method.getReturnType();
int argCount = argTypes.length;
//这便是我们PropertyDescriptor[]的单个元素 pd
PropertyDescriptor pd = null;
if (name.length() <= 3 && !name.startsWith(IS_PREFIX)) {
// Optimization. Don't bother with invalid propertyNames.
continue;
}
try {
if (argCount == 0) {
//如果无参方法,则看下是否以get开头
if (name.startsWith(GET_PREFIX)) {
// Simple getter
pd = new PropertyDescriptor(this.beanClass, name.substring(3), method, null);
//看下方法返回类型是否为boolean、并且方法以is开头
} else if (resultType == boolean.class && name.startsWith(IS_PREFIX)) {
// Boolean getter
pd = new PropertyDescriptor(this.beanClass, name.substring(2), method, null);
}
} else if (argCount == 1) {
//如果有个参数,则看下是否以get开头,并且参数类型为int类型
if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) {
pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null);
//看下方法释放以set开头,并且返回类型为void空类型
} else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) {
// Simple setter
pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method);
if (throwsException(method, PropertyVetoException.class)) {
pd.setConstrained(true);
}
}
} else if (argCount == 2) {
//如果有两个参数,则检查1、返回类型释放为空, 第一个参数是否为int类型,然后方法开头是否以set开头
if (void.class.equals(resultType) && int.class.equals(argTypes[0]) && name.startsWith(SET_PREFIX)) {
pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, null, method);
if (throwsException(method, PropertyVetoException.class)) {
pd.setConstrained(true);
}
}
}
} catch (IntrospectionException ex) {
// This happens if a PropertyDescriptor or IndexedPropertyDescriptor
// constructor fins that the method violates details of the deisgn
// pattern, e.g. by having an empty name, or a getter returning
// void , or whatever.
pd = null;
}
//如果pd不为空,BeanClass的方法符合前面的条件,因此,将它通过addPropertyDescriptor方法添加到PropertyDescriptor列表中
if (pd != null) {
// If this class or one of its base classes is a PropertyChange
// source, then we assume that any properties we discover are "bound".
if (propertyChangeSource) {
pd.setBound(true);
}
addPropertyDescriptor(pd);
}
}
}
//将PropertyDescriptor从pdStore中取出来并处理,填充properties这个Map集合
processPropertyDescriptors();
//最终,将处理过后的properties返回,这就是我们的结果
// Allocate and populate the result array.
PropertyDescriptor result[] =
properties.values().toArray(new PropertyDescriptor[properties.size()]);
// Set the default index.
if (defaultPropertyName != null) {
for (int i = 0; i < result.length; i++) {
if (defaultPropertyName.equals(result[i].getName())) {
defaultPropertyIndex = i;
}
}
}
return result;
}

这个方法很长,但是最终我们找到了关键的地方getTargetPropertyInfo,因此,由于我们的Device类,所有元素都是public,并没有set/get/is/add之类的标准JavaBean所具有的方法,因此这里返回的PropertyDescriptor[]中只有一个Class元素,并不包含任何方法属性pd

从而导致 int[] columnToProperty = this.mapColumnsToProperties(rsmd, props); 这里,数组内容都是-1,即找不到属性

1
2
3
4
5
6
7
public <T> T populateBean(ResultSet rs, T bean) throws SQLException {
PropertyDescriptor[] props = this.propertyDescriptors(bean.getClass());
ResultSetMetaData rsmd = rs.getMetaData();
int[] columnToProperty = this.mapColumnsToProperties(rsmd, props);
return populateBean(rs, bean, props, columnToProperty);
}

也就无法填充这个BeanClass的成员内容了,所以最终数据库QueryRunner查询出来的实例内容都是空的

参考链接

https://zh.wikipedia.org/wiki/JavaBeans