DAO设计简化思路
首先初始化数据库连接池(使用Alibaba的Druid连接池,需先下载druid-1.x.x.jar包)
public class JDBCUtil {
private static DataSource ds = null;
//初始化数据库连接池
static {
try {
Properties p = new Properties();
InputStream input = new FileInputStream("sources/db.properties");
p.load(input);
ds = DruidDataSourceFactory.createDataSource(p);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据库连接对象
* @return 数据库连接对象
*/
public static Connection getConnection() {
Connection con = null;
try {
con = ds.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
return con;
}
/**
* 释放资源
* @param con
* @param st
* @param re
*/
public static void release(Connection con,Statement st,ResultSet re) {
try {
if(con!=null) {
con.close();
}
if(st!=null) {
st.close();
}
if(re!=null) {
re.close();
}
}catch (SQLException e) {
e.printStackTrace();
}
}
}
随后建立一个AbstractDAO抽象类作为DAO实现类的父类,并设置泛型表示要操作的VO类
public abstract class AbstractDAO<T>{
}
抽象出execute方法,接受sql语句和可变参数,返回sql的执行结果,原理如下图:
此时子类DAO进行增加、修改、删除时就可以直接调用父类的execute方法,传入自己编写的SQL语句,并通过可变参数的形式为预处理语句设置值,示例代码如下。
public abstract class AbstractDAO<T> {
private Connection con;
private PreparedStatement pstmt;
private ResultSet re;
public AbstractDAO() {
}
/**
* 通过可变参数的形式设置数据库语句并执行,适用于新增、修改和删除
* @param sql 预处理语句
* @param params 预处理语句的预留参数
* @return 执行成功返回true,否则返回false
* @throws SQLException
*/
public boolean execute(String sql,Object...params) throws SQLException{
try {
//取得JDBC连接对象
this.con = JDBCUtil.getConnection();
this.pstmt = this.con.prepareStatement(sql);
//循环遍历设置预处理语句的预留参数
for(int x=0;x<params.length;x++) {
this.pstmt.setObject(x+1,params[x]);
}
return this.pstmt.executeUpdate()>0;
}catch(SQLException e) {
throw e;
}finally {
//释放资源
JDBCUtil.release(this.con,this.pstmt,this.re);
}
}
}
查询方法的抽象要复杂一点,因为我们查询出的数据要以简单Java类的形式返回,而父类不知道要将数据库返回的数据封装为什么类型,这时我们可以通过泛型来实现。将查询的数据封装为子类传入的类型,方法如下,调用此方法就可以得到传入泛型的实例化对象。
/**
* 使用泛型反射实例化对象
* @return
*/
@SuppressWarnings("unchecked")
private T getInstanceOfT(){
ParameterizedType superClass = (ParameterizedType) getClass().getGenericSuperclass();
Class<T> type = (Class<T>) superClass.getActualTypeArguments()[0];
try{
return type.newInstance();
}
catch (Exception e){
throw new RuntimeException(e);
}
}
即使得到了要封装的对象,我们怎么为对象的每一个属性都赋值呢(AbstractDAO为工具类,每一个DAO实现类都可以继承它来简化开发,因此传入的泛型一定会不同,要封装对象里面的属性更不同)这就要用到“内省”。
内省的本质是通过反射来操作JavaBean中的属性,得到了要封装类的属性,查询的操作就简单了。与增加、修改、删除不同,JDBC的查询操作会返回一个ResultSet数据集用来存储查询得到的数据。数据的保存就是在ResultSet数据集中读取数据然后设置到返回VO对象的属性值中(JavaBean中的属性名称应与数据库存储字段名称相同,数据类型也应该相同。否则取不到值或产生异常)。
利用Introspector类中的getBeanInfo(A.class,B.class)方法可以取得字节码中的属性信息,返回为BeanInfo类型,保存A类及A类的父类中的所有属性信息,但不包括B类(包括B类的父类)中的属性信息。(假设A类继承B类,B类继承C类,我们使用Introspector.getBeanInfo(A.class,B.class)
只能够得到A类中的所有属性,而使用Introspector.getBeanInfo(A.class,C.class)
则可以得到A类和B类中的所有属性)。
利用BeanInfo中的getPropertyDescriptors()
方法可以得到具体属性描述器的集合,返回为PropertyDescriptor类型,其可以获得属性名、set方法、get方法等。取得属性名称后,我们直接利用属性名称作为索引值在返回的ResultSet数据集中取值。再通过set方法.invoke(对象名称,参数)赋值给对象。具体实现代码如下:
/**
* 查询单条数据
* @param sql 预处理语句
* @param params 预处理语句的预留参数
* @return 将查询结果封装在VO类中返回
* @throws Exception
*/
public T query(String sql,Object...params) throws Exception{
T t = null;
try {
this.con = JDBCUtil.getConnection();
this.pstmt = this.con.prepareStatement(sql);
//循环遍历设置预处理语句的预留参数
for(int x=0;x<params.length;x++) {
this.pstmt.setObject(x+1,params[x]);
}
//查询结果集
this.re = this.pstmt.executeQuery();
if(this.re.next()) {
//反射实例化VO类对象
t = getInstanceOfT();
//取得指定字节码的属性信息
BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass(),Object.class);
//取得所有的属性描述器
PropertyDescriptor[] dps = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor dp:dps) {
//以属性名称为索引,再ResultSet结果集中取值
Object value = this.re.getObject(dp.getName());
//通过set方法将取出的值设置在t的同名属性中
dp.getWriteMethod().invoke(t,value);
}
}
return t;
}catch(Exception e) {
throw e;
}finally {
JDBCUtil.release(this.con,this.pstmt,this.re);
}
}
该方法实现后,子类就可以通过调用此方法进行查询操作,避免了查询操作的重复代码,我们只需自己写好SQL语句然后调用super.query传入参数即可取得查询结果,调用示例如下:
public class UserDAOImpl extends AbstractDAO<User> implements IUserDAO {
@Override
public User findById(String id) throws Exception {
String sql = "SELECT bank_customer_id,bank_customer_pwd,bank_customer_name,"
+ "bank_customer_email,bank_customer_phone,bank_customer_type"
+ " FROM bank_customer_auth WHERE bank_customer_id=?";
return super.query(sql,id);
}
}
虽然以上方法确实可以完成查询功能,但只能查询单条数据,如果想要查询多条数据,则应返回List集合,具体代码如下:
/**
* 查询多条数据
* @param sql 预处理语句
* @param params 预处理语句的预留参数
* @return 将查询结果封装在List集合中返回
* @throws Exception
*/
public List<T> queryList(String sql,Object... params) throws Exception {
List<T> all = new ArrayList<T>();
T t = null;
try {
this.con = JDBCUtil.getConnection();
this.pstmt = this.con.prepareStatement(sql);
//循环遍历设置预处理语句的预留参数
for(int x=0;x<params.length;x++) {
this.pstmt.setObject(x+1,params[x]);
}
//查询结果集
this.re = this.pstmt.executeQuery();
while(this.re.next()) {
//反射实例化VO类对象
t = getInstanceOfT();
BeanInfo beanInfo = Introspector.getBeanInfo(t.getClass(),Object.class);
PropertyDescriptor[] dps = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor dp:dps) {
Object value = this.re.getObject(dp.getName());
dp.getWriteMethod().invoke(t,value);
}
all.add(t);
}
}catch(Exception e) {
throw e;
}finally {
JDBCUtil.release(this.con,this.pstmt,this.re);
}
return all;
}
同单次查询类似,子类就可以通过调用此方法进行多组数据的查询操作:
@Override
public List<User> findAll() throws Exception {
String sql = "SELECT bank_customer_id,bank_customer_pwd,bank_customer_name,"
+ "bank_customer_email,bank_customer_phone,bank_customer_type"
+ " FROM bank_customer_auth";
return super.queryList(sql);
}
总结:DAO代码的重构在于提取出重复的代码然后建立一种模板,使其适用于任意的CRUD。
以上代码汇总
使用Apache公司的DBUtils工具可以更好实现上述所有功能。DBUtils官网下载
DBUtils的使用非常简单,主要是依靠数据库连接池提供的数据源来进行操作。我们改写数据库连接工具类,加入getDs()方法取得DataSource对象。
public class JDBCUtil {
private static DataSource ds = null;
//初始化数据库连接池
static {
try {
Properties p = new Properties();
InputStream input = new FileInputStream("sources/db.properties");
p.load(input);
ds = DruidDataSourceFactory.createDataSource(p);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 获取数据源
* @return
*/
public static DataSource getDs() {
return ds;
}
}
在具体的DAO实现类中通过QueryRunner对象的update()方法进行数据库的增加、修改、删除操作,通过query()方法进行查询操作。实例如下:
新增操作
@Override
public boolean doCreate(User vo) throws SQLException {
String sql = "INSERT INTO bank_customer_auth (bank_customer_id,"
+ "bank_customer_pwd,bank_customer_name,bank_customer_email,"
+ "bank_customer_phone,bank_customer_type) VALUES (?,?,?,?,?,?)";
if(vo!=null) {
QueryRunner qr = new QueryRunner(JDBCUtil.getDs());
return qr.update(sql,vo.getBank_customer_id(),vo.getBank_customer_pwd(),
vo.getBank_customer_name(),vo.getBank_customer_email(),
vo.getBank_customer_phone(),vo.getBank_customer_type())>0;
}
return false;
}
查看单条记录
@Override
public User findById(String id) throws SQLException {
String sql = "SELECT bank_customer_id,bank_customer_pwd,bank_customer_name,"
+ "bank_customer_email,bank_customer_phone,bank_customer_type"
+ " FROM bank_customer_auth WHERE bank_customer_id=?";
QueryRunner qr = new QueryRunner(JDBCUtil.getDs());
return qr.query(sql,new BeanHandler<User>(User.class),id);
}
查看多条记录
@Override
public List<User> findAll() throws SQLException {
String sql = "SELECT bank_customer_id,bank_customer_pwd,bank_customer_name,"
+ "bank_customer_email,bank_customer_phone,bank_customer_type"
+ " FROM bank_customer_auth";
QueryRunner qr = new QueryRunner(JDBCUtil.getDs());
return qr.query(sql,new BeanListHandler<User>(User.class));
}