用最基本的JDBC进行数据连接的时候,效率低且违背面向对象,引入c3p0连接池进行优化处理,使用DbUtils进一步封装jDBC的过程、——自己手动实现一个简单的DbUtils类,对JDBC过程进行封装。
在对JDBC的执行过程进行分析中,我们会发现两个主要的问题:1.PreparedStatement执行sql语句,如何设置参数 2.ResultSet结果集中如何取出参数并封装到到一个对象中。经过对问题的分析,可以看出sql语句,结果集的处理以及要传递的参数是变量,而且我们不确定要传递参数的个数,数据库查询语句和数据库的更改语句的处理过程是不一样的,所以我们定义了两个方法都使用了可变参数,一个是Query数据库的查询语句,根据返回需要处理结果的不同,设计了ResultSetHandler接口,实现ResultSetHandler中的handler方法对结果集Result进行不同的处理,并返回相应的结果。BeanHandler与BeanListHandler都实现了ResultSetHandler接口,内部handler方法的实现逻辑也大体相同的、回头看需要解决的那个问题,ResultSetResultSet结果集中如何取出参数并封装到一个对象中,首先对象的类型我们不能确定下来,不过要处理的对象都符合JavaBean规范,所以使用了泛型提高了通用性,rs.getObject(columnLabel)可以解决数据库中数据映射到对象中类型不同的问题,但我们还是不能确定代表每列的字段名,但我们约定数据库中的列名与User实体类中属性名是一致的,所以我们可以获得User对象的属性名集合。而反射是将类中的各种信息映射到一个类中,这样我们需要一个传递Class<T>对象的参数,来获得类中的信息,为更方面的操作类中信息,引入java.beans包下的一些类,通过Introspector.getBeanInfo(Clz).getPeropertyDescriptors()来获取描述类中所有属性的数组,这样迭代这个数组就可以获得每个属性的名字,我们还要将获取的数据封装到对象中,我们的对象都是符合javaBean规范的,所以可以通过属性获得相应的set方法,然后通过反射调用相关方法对对象进行封装、当我们PropertyDescriptor数组进行迭代式,属性中可能可能会有父类的属性,这样会抛出异常,捕获异常,并利用continue关键字继续迭代。
DbUtils的主要实现类:
/**jdbc查询的方法(可以查单个对象,也可以查多个)
* @param sql:sql语句
* @param rsh:ResultSetHandler接口的实现类的对象
* @param params:查询条件对应的参数值
* @return 对应的javabean对象或对应List<javabean>对象
*/
public static <T> T query(String sql, ResultSetHandler<T> rsh,
Object... params){
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
//获取数据库连接
conn = getConn();
//预编译sql语句并返回PreparedStatement对象
ps = conn.prepareStatement(sql);
//为占位符赋值
for(int i = 0;i<params.length;i++){//占位符设置下标从1开始
ps.setObject(i+1, params[i]);
}
//执行查询操作
rs = ps.executeQuery();
return rsh.handler(rs);
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}finally{
//关闭数据连接释放资源
close(rs, ps, conn);
}
}
/**添加、修改、删除
* @param sql:sql语句
* @param params:用于为占位符赋值
* @return 影响的行数
*/
public static int update(String sql, Object... params){
Connection conn = null;
PreparedStatement ps = null;
try {
//获取数据库连接
conn = getConn();
//预编译sql语句并返回PreparedStatement对象
ps = conn.prepareStatement(sql);
//为占位符赋值
for(int i = 0;i<params.length;i++){
ps.setObject(i+1, params[i]);
}
//执行操作,并返回影响的行数
return ps.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
throw new RuntimeException(e);
}finally{
close(ps, conn);
}
}
对结果集的处理,ResultSetHandler是一个接口,内部只有一个Handler方法,BeanHandler与BeanListHandler都实现了ResultSetHandler接口:
BeanHandler的内部实现,BeanListHandler的内部实现原理也是大体一致的,只不过将封装的对象迭代进集合中:
public class BeanHandler<T> implements ResultSetHandler<T> {
private Class<T> clz;
public BeanHandler(Class<T> clz){
this.clz = clz;
}
/**
* 从结果集rs对象获取信息,并进行封装:
* 封装一个JavaBean对象
*/
public T handler(ResultSet rs) throws Exception {
if(rs.next()){//存在记录
//创建JavaBean对象
T t = clz.newInstance();
//将Class对象clz转化成BeanInfo对象,可以属性信息逐一进行封装
BeanInfo bi = Introspector.getBeanInfo(clz);
PropertyDescriptor[] pds = bi.getPropertyDescriptors();
//t setXxx rs.getObject(xxx);
//t.setUsername(rs.getString("username"));
for (PropertyDescriptor pd:pds){
//获取属性的名称
String name = pd.getName();
//获取setXxx方法
Method mtd = pd.getWriteMethod();
//t.setXxx(rs.getObject(name)
try{
mtd.invoke(t, rs.getObject(name));
}catch (SQLException e) {
continue;
}
}
return t;
}
return null;
}
}
这样通过DbUtils工具类查询数据库只需两行代码:
String sql = "select * from user where username = ?";
return DbUtils.query(sql, new BeanHandler<User>(User.class), username);
更改操作也是大体相同的用法:
String sql = "insert into user(username,nickname,password,email)" +
" values(?, ?, ?, ?)";
return DbUtils.update(sql, user.getUsername(),user.getNickname(),user.getPassword(),user.getEmail());