利用反射实现 BaseDao

在 web 开发中,我们需要用 dao 从数据库中查询数据,此时我们可以定义一个 BaseDao, 就是用于做一些增删改查的基础 DAO, 其后的其他的具体 DAO, 只需要继承这个 DAO, 然后再根据具体的业务逻辑去写具体方法就行,实现代码重用.

这是增删改查的基础代码,利用 DBUtils 写的.

下面是具体的代码,我一一对其解释.

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
package com.xxx.dao;

import com.xxx.utils.JDBCUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;

/**
* Created on 11/30/2016 11:24 PM.
* 定义对数据库的基础操作, 用于被其他DAO继承.
*
*/
public class BaseDao<T> {

private QueryRunner runner = new QueryRunner();
private Class<T> type;

public BaseDao () {
Class<? extends BaseDao> clz = this.getClass();
System.out.println("BaseDao构造器被执行, clz为: " + clz);

Type genericSuperclass = clz.getGenericSuperclass();
System.out.println("带泛型的父类类型实际类型为: " + genericSuperclass.getClass());

ParameterizedType p = (ParameterizedType) genericSuperclass;
Type[] types = p.getActualTypeArguments();
type = (Class<T>) types[0];
}

/**
* 查询一个对象.
*
* @param sql
* @param params
* @return
*/
public T getBean (String sql, Object... params) {
T t = null;
Connection conn = JDBCUtils.getConnection();
try {
t = runner.query(conn, sql, new BeanHandler<T>(type), params);
} catch ( SQLException e ) {
e.printStackTrace();
} finally {
JDBCUtils.releaseConnection(conn);
}
return t;
}

/**
* 查询一组对象列表
*
* @param sql
* @param params
* @return
*/
public List<T> getBeanList (String sql, Object... params) {
List<T> list = null;
Connection connection = JDBCUtils.getConnection();
try {
list = runner.query(connection, sql, new BeanListHandler<T>(type),
params);
} catch ( SQLException e ) {
e.printStackTrace();
} finally {
JDBCUtils.releaseConnection(connection);
}
return list;
}

/**
* 更新数据库操作的方法, 可以实现增删改.
*
* @param sql
* @param params
* @return update count!
*/
public int update (String sql, Object... params) {
int count = 0;
Connection conn = JDBCUtils.getConnection();
try {
count = runner.update(conn, sql, params);
} catch ( SQLException e ) {
e.printStackTrace();
} finally {
JDBCUtils.releaseConnection(conn);
}
return count;
}
}


其中 udpate 方法,可以实现增删改.
查询方法,单独查询一个对象和查询一组对象.
要说的部分在查询这里。在 BeanListHandler 和 BeanHandler 这里后面都有一个 type 参数传到了构造器中,这个参数就是具体要查询类的 Bean 对象。由于我们这里是在 BaseDao 里无法写具体的子类 Class, 所以我们先 private Class<T> type; 声明了一个 type 给他传递了一个引用.

那么我们这个 BaseDao 被子类继承后,如何传递这个具体的 bean 对象过来呢.
就是说在这个 Dao 的查询方法里,他怎么知道查询后的数据赋给哪个 bean 对象?

本文重点要说的就是这里了.
我们将 BaseDao 传递一个泛型参数,写成: public class BaseDao<T>
然后我们将具体的子类对象通过泛型传递过来,接着在构造器中获取具体的泛型类型.
解释一下这个构造器的作用.
首先来说一下,这个 BaseDao 的构造器会在何时执行.

在 JAVA 基础那里我们知道,在初始化子类之前会初始化父类的构造器,在子类中,我们也是把 super () 语句写在子类构造器的第一行,且 java 规定必须是第一行,这样的目的就是让子类在初始化之前确保父类被初始化,
而这个 super () 就是调用父类的构造器,所以在BaseDao这个构造器中的this, 它的指针是指向的具体实现类的子类引用.
那么 this.getClass () 获取到的就是子类的类型.

其次,我们再通过反射获取这个子类的父类类型,毫无疑问它的父类肯定就是这个 BaseDao.
由于这个 BaseDao 是带泛型的,所以我们应该调用 clz.getGenericSuperclass(); 这个方法,而不是调用 clz.superClass();

获取到了带泛型的父类之后,由于这个泛型真实类型是参数化泛型,所以我们还需要强转成 ParameterizedType p = (ParameterizedType) genericSuperclass;.

接下来,Type[] types = p.getActualTypeArguments(); 这个方法开始真正的获取泛型列表了,因为我们知道泛型可以写多个,所以这里返回值是一个数组。但我们这里只有一个泛型,
所以 type = (Class<T>) types[0]; 直接获取数组下标为 0 那个泛型就可以.

经过这一些列的获取,我们就可以拿到传递过来的具体子类 Bean 对象.
然后之前通过 private Class<T> type; 声明的 type 就被赋值为子类的类型,就可以用于 BeanListHandler 和 BeanHandler 使用了.

1
2
3
4
5
Class<? extends BaseDao> clz = this.getClass();
System.out.println("BaseDao构造器被执行, clz为: " + clz);

Type genericSuperclass = clz.getGenericSuperclass();
System.out.println("带泛型的父类类型实际类型为: " + genericSuperclass.getClass());

上面 2 条语句的输出为:

BaseDao 构造器被执行,clz 为: class com.xxx.dao.impl.UserDaoImpl

带泛型的父类类型实际类型为: class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl