为了理解DBUtils框架实现原理,我们先手动仿写一个 JDBC框架

元数据指的是"数据库"、"表"、"列"的定义信息。

Connection.getDatabaseMetaData()获得代表DatabaseMetaData元数据的DatabaseMetaData对象。DataBaseMetaData对象的常用方法:

getURL():返回一个String类对象,代表数据库的URL。

getUserName():返回连接当前数据库管理系统的用户名。

getDatabaseProductName():返回数据库的产品名称。

getDatabaseProductVersion():返回数据库的版本号。

getDriverName():返回驱动驱动程序的名称。

getDriverVersion():返回驱动程序的版本号。

isReadOnly():返回一个boolean值,指示数据库是否只允许读操作。

* @Method: testDataBaseMetaData

* @Description: 获取数据库的元信息

* @Anthor:rkhd

* @throws SQLException

public void testDataBaseMetaData() throws SQLException {

Connection conn = JdbcUtils.getConnection();

DatabaseMetaData metadata = conn.getMetaData();

//getURL():返回一个String类对象,代表数据库的URL

System.out.println(metadata.getURL());

//getUserName():返回连接当前数据库管理系统的用户名

System.out.println(metadata.getUserName());

//getDatabaseProductName():返回数据库的产品名称

System.out.println(metadata.getDatabaseProductName());

//getDatabaseProductVersion():返回数据库的版本号

System.out.println(metadata.getDatabaseProductVersion());

//getDriverName():返回驱动驱动程序的名称

System.out.println(metadata.getDriverName());

//getDriverVersion():返回驱动程序的版本号

System.out.println(metadata.getDriverVersion());

//isReadOnly():返回一个boolean值,指示数据库是否只允许读操作

System.out.println(metadata.isReadOnly());

JdbcUtils.release(conn, null, null);

运行结果如下:

PreparedStatement.getParameterMetaData() 获得代表PreparedStatement元数据的ParameterMetaData对象。

Select * from user where name=? And password=?

ParameterMetaData对象的常用方法:

getParameterCount(): 获得指定参数的个数

getParameterType(int param):获得指定参数的sql类型,MySQL数据库驱动不支持

* @Method: testParameterMetaData

* @Description: 获取参数元信息

* @Anthor:rkhd

* @throws SQLException

public void testParameterMetaData() throws SQLException {

Connection conn = JdbcUtils.getConnection();

String sql = "select * from user wherer name=? and password=?";

//将SQL预编译一下

PreparedStatement st = conn.prepareStatement(sql);

ParameterMetaData pm = st.getParameterMetaData();

//getParameterCount() 获得指定参数的个数

System.out.println(pm.getParameterCount());

//getParameterType(int param):获得指定参数的sql类型,MySQL数据库驱动不支持

System.out.println(pm.getParameterType(1));

JdbcUtils.release(conn, null, null);

ResultSet. getMetaData() 获得代表ResultSet对象元数据的ResultSetMetaData对象。ResultSetMetaData对象的常用方法:

getColumnCount() 返回resultset对象的列数

getColumnName(int column) 获得指定列的名称

getColumnTypeName(int column)获得指定列的类型

* @Method: testResultSetMetaData

* @Description: 结果集的元数据

* @Anthor:rkhd

* @throws Exception

public void testResultSetMetaData() throws Exception {

Connection conn = JdbcUtils.getConnection();

String sql = "select * from account";

PreparedStatement st  = conn.prepareStatement(sql);

ResultSet rs = st.executeQuery();

//ResultSet.getMetaData()获得代表ResultSet对象元数据的ResultSetMetaData对象

ResultSetMetaData metadata = rs.getMetaData();

//getColumnCount() 返回resultset对象的列数

System.out.println(metadata.getColumnCount());

//getColumnName(int column) 获得指定列的名称

System.out.println(metadata.getColumnName(1));

//getColumnTypeName(int column)获得指定列的类型

System.out.println(metadata.getColumnTypeName(1));

JdbcUtils.release(conn, st, rs);

系统中所有实体对象都涉及到基本的CRUD操作

所有实体的CUD操作代码基本相同,仅仅发送给数据库的SQL语句不同而已,因此可以把CUD操作的所有相同代码抽取到工具类的一个update方法中,并定义参数接收变化的SQL语句。

实体的R操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet的映射也各不相同,因此可义一个query方法,除以参数形式接收变化的SQL语句外,可以使用策略模式由qurey方法的调用者决定如何把ResultSet中的数据映射到实体对象中。

定义一个JdbcUtils工具类,工具类负责获取数据库连接,释放资源,执行SQL的update和query操作,代码如下:

package me.rkhd.util;

import java.io.InputStream;

import java.sql.Connection;

import java.sql.DriverManager;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.sql.Statement;

import java.util.Properties;

public class JdbcUtils {

private static String driver = null;

private static String url = null;

private static String username = null;

private static String password = null;

//读取db.properties文件中的数据库连接信息

InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");

Properties prop = new Properties();

prop.load(in);

//获取数据库连接驱动

driver = prop.getProperty("driver");

//获取数据库连接URL地址

url = prop.getProperty("url");

//获取数据库连接用户名

username = prop.getProperty("username");

//获取数据库连接密码

password = prop.getProperty("password");

//加载数据库驱动

Class.forName(driver);

}catch (Exception e) {

throw new ExceptionInInitializerError(e);

* @Method: getConnection

* @Description: 获取数据库连接对象

* @Anthor:rkhd

* @return Connection数据库连接对象

* @throws SQLException

public static Connection getConnection() throws SQLException{

return DriverManager.getConnection(url, username,password);

* @Method: release

* @Description: 释放资源,

*     要释放的资源包括Connection数据库连接对象,负责执行SQL命令的Statement对象,存储查询结果的ResultSet对象

* @Anthor:rkhd

* @param conn

* @param st

* @param rs

public static void release(Connection conn,Statement st,ResultSet rs){

if(rs!=null){

//关闭存储查询结果的ResultSet对象

rs.close();

}catch (Exception e) {

e.printStackTrace();

if(st!=null){

//关闭负责执行SQL命令的Statement对象

st.close();

}catch (Exception e) {

e.printStackTrace();

if(conn!=null){

//关闭Connection数据库连接对象

conn.close();

}catch (Exception e) {

e.printStackTrace();

* @Method: update

* @Description: 万能更新

* 所有实体的CUD操作代码基本相同,仅仅发送给数据库的SQL语句不同而已,

* 因此可以把CUD操作的所有相同代码抽取到工具类的一个update方法中,并定义参数接收变化的SQL语句

* @Anthor:rkhd

* @param sql 要执行的SQL

* @param params 执行SQL时使用的参数

* @throws SQLException

public static void update(String sql,Object params[]) throws SQLException{

Connection conn = null;

PreparedStatement st = null;

ResultSet rs = null;

conn = getConnection();

st = conn.prepareStatement(sql);

for(int i=0;i<params.length;i++){

st.setObject(i+1, params[i]);

st.executeUpdate();

release(conn, st, rs);

* @Method: query

* @Description:万能查询

* 实体的R操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet的映射也各不相同,

* 因此可义一个query方法,除以参数形式接收变化的SQL语句外,可以使用策略模式由qurey方法的调用者决定如何把ResultSet中的数据映射到实体对象中。

* @Anthor:rkhd

* @param sql 要执行的SQL

* @param params 执行SQL时使用的参数

* @param rsh 查询返回的结果集处理器

* @throws SQLException

public static Object query(String sql,Object params[],ResultSetHandler rsh) throws SQLException{

Connection conn = null;

PreparedStatement st = null;

ResultSet rs = null;

conn = getConnection();

st = conn.prepareStatement(sql);

for(int i=0;i<params.length;i++){

st.setObject(i+1, params[i]);

rs = st.executeQuery();

* 对于查询返回的结果集处理使用到了策略模式,

* 在设计query方法时,query方法事先是无法知道用户对返回的查询结果集如何进行处理的,即不知道结果集的处理策略,

* 那么这个结果集的处理策略就让用户自己提供,query方法内部就调用用户提交的结果集处理策略进行处理

* 为了能够让用户提供结果集的处理策略,需要对用户暴露出一个结果集处理接口ResultSetHandler

* 用户只要实现了ResultSetHandler接口,那么query方法内部就知道用户要如何处理结果集了

return rsh.handler(rs);

release(conn, st, rs);

在设计query方法时,对于查询返回的结果集处理使用到了策略模式,query方法事先是无法知道用户对返回的查询结果集如何进行处理的,即不知道结果集的处理策略, 那么这个结果集的处理策略就让用户自己提供,query方法内部就调用用户提交的结果集处理策略进行处理, 为了能够让用户提供结果集的处理策略,需要对用户暴露出一个结果集处理接口ResultSetHandler, 结果集处理器接口ResultSetHandler的定义如下:

package me.rkhd.util;

import java.sql.ResultSet;

* @ClassName: ResultSetHandler

* @Description:结果集处理器接口

* @author: rkhd

public interface ResultSetHandler {

* @Method: handler

* @Description: 结果集处理方法

* @Anthor:rkhd

* @param rs 查询结果集

public Object handler(ResultSet rs);

用户只要实现了ResultSetHandler接口,那么就是针对查询结果集写了一个处理器,在query方法内部就调用用户自己写的处理器处理结果集。

为了提高框架的易用性,我们可以事先就针对结果集写好一些常用的处理器,比如将结果集转换成bean对象的处理器,将结果集转换成bean对象的list集合的处理器。

package me.rkhd.util;

import java.lang.reflect.Field;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

* @ClassName: BeanHandler

* @Description: 将结果集转换成bean对象的处理器

* @author: rkhd

public class BeanHandler implements ResultSetHandler {

private Class<?> clazz;

public BeanHandler(Class<?> clazz){

this.clazz = clazz;

public Object handler(ResultSet rs) {

if(!rs.next()){

return null;

Object bean = clazz.newInstance();

//得到结果集元数据

ResultSetMetaData metadata = rs.getMetaData();

int columnCount = metadata.getColumnCount();//得到结果集中有几列数据

for(int i=0;i<columnCount;i++){

String coulmnName = metadata.getColumnName(i+1);//得到每列的列名

Object coulmnData = rs.getObject(i+1);

Field f = clazz.getDeclaredField(coulmnName);//反射出类上列名对应的属性

f.setAccessible(true);

f.set(bean, coulmnData);

return bean;

}catch (Exception e) {

throw new RuntimeException(e);

package me.rkhd.util;

import java.lang.reflect.Field;

import java.sql.ResultSet;

import java.sql.ResultSetMetaData;

import java.util.ArrayList;

import java.util.List;

* @ClassName: BeanListHandler

* @Description: 将结果集转换成bean对象的list集合的处理器

* @author: rkhd

public class BeanListHandler implements ResultSetHandler {

private Class<?> clazz;

public BeanListHandler(Class<?> clazz){

this.clazz = clazz;

public Object handler(ResultSet rs) {

List<Object> list = new ArrayList<Object>();

while(rs.next()){

Object bean = clazz.newInstance();

ResultSetMetaData  metadata = rs.getMetaData();

int count = metadata.getColumnCount();

for(int i=0;i<count;i++){

String name = metadata.getColumnName(i+1);

Object value = rs.getObject(name);

Field f = bean.getClass().getDeclaredField(name);

f.setAccessible(true);

f.set(bean, value);

list.add(bean);

return list.size()>0?list:null;

}catch (Exception e) {

throw new RuntimeException(e);

当框架自身提供的结果集处理器不满足用户的要求时,那么用户就可以自己去实现ResultSetHandler接口,编写满足自己业务要求的结果集处理器。

有了上述的JdbcUtils框架之后,针对单个实体对象CRUD操作就非常方便了,如下所示: