编写自己的JDBC框架
Posted 李阿昀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编写自己的JDBC框架相关的知识,希望对你有一定的参考价值。
元数据介绍
元数据指的是”数据库”、”表”、”列”的定义信息。
DatabaseMetaData元数据
Connection.getMetaData()获得代表DatabaseMetaData元数据的DatabaseMetaData对象。DatabaseMetaData对象的常用方法有:
例,
package cn.liayun.demo;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.SQLException;
import org.junit.Test;
import cn.liayun.utils.JdbcUtils_C3P0;
public class Demo5 {
/**
* 获取数据库的元数据
* @param args
* @throws SQLException
*/
@Test
public void test1() throws SQLException {
Connection conn = JdbcUtils_C3P0.getConnection();
DatabaseMetaData meta = conn.getMetaData();
System.out.println(meta.getDatabaseProductName());//mysql
System.out.println(meta.getDatabaseMajorVersion());//5
System.out.println(meta.getDatabaseMinorVersion());//7
}
}
JdbcUtils_C3P0这个类的代码怎么写,可以参考我的笔记《Java Web基础入门第五十八讲 开源数据库连接池》。test1方法运行结果如下:
ParameterMetaData元数据——参数的元数据
PreparedStatement.getParameterMetaData()获得代表PreparedStatement元数据的ParameterMetaData对象。例如,有这样的一条SQL语句:
String sql = "insert into user(name,password) values(?,?)";
我们要想知道参数的个数和每一个参数的类型,就得用到ParameterMetaData元数据。ParameterMetaData对象的常用方法有:
例,
package cn.liayun.demo;
import java.sql.Connection;
import java.sql.ParameterMetaData;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import org.junit.Test;
import cn.liayun.utils.JdbcUtils_C3P0;
public class Demo5 {
/**
* 获取参数的元数据
* @param args
* @throws SQLException
*/
@Test
public void test2() throws SQLException {
Connection conn = JdbcUtils_C3P0.getConnection();
String sql = "insert into user(id,name) values(?,?)";
PreparedStatement st = conn.prepareStatement(sql);
ParameterMetaData meta = st.getParameterMetaData();
System.out.println(meta.getParameterCount());//sql语句需要几个参数?
System.out.println(meta.getParameterType(1));//想要知道sql语句的第1个参数的类型,但MySQL驱动不支持获取参数的类型
}
}
ResultSetMetaData元数据——结果集的元数据
ResultSet.getMetaData()获得代表ResultSet对象元数据的ResultSetMetaData对象。ResultSetMetaData对象的常用方法有:
例,
package cn.liayun.demo;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import org.junit.Test;
import cn.liayun.utils.JdbcUtils_C3P0;
public class Demo5 {
/**
* 获取结果集的元数据
* @param args
* @throws SQLException
*/
@Test
public void test3() throws SQLException {
Connection conn = JdbcUtils_C3P0.getConnection();
String sql = "select * from account";
PreparedStatement st = conn.prepareStatement(sql);
ResultSet rs = st.executeQuery();
ResultSetMetaData meta = rs.getMetaData();
System.out.println(meta.getColumnCount());//结果集里面有几列?
System.out.println(meta.getColumnName(1));//结果集里面每一列是什么?
System.out.println(meta.getColumnName(2));
System.out.println(meta.getColumnName(3));
}
}
使用元数据封装简单的JDBC框架
业务背景:系统中所有实体对象都涉及到基本的CRUD操作。所有实体的CUD操作代码基本相同,仅仅是发送给数据库的SQL语句不同而已,因此可以把CUD操作的所有相同代码抽取到工具类的一个update方法中,并定义参数接收变化的SQL语句。实体的R操作,除SQL语句不同之外,根据操作的实体不同,对ResultSet的映射也各不相同,因此可定义一个query方法,除以参数形式接收变化的SQL语句外,还可以使用策略模式由qurey方法的调用者决定如何把ResultSet中的数据映射到实体对象中。
在设计使用元数据封装简单的JDBC框架之前,我们先编写测试用的SQL脚本:
/* 创建数据库 */
create database day16;
use day16;
/* 创建账户表 */
create table account
(
id int primary key auto_increment,
name varchar(40),
money float
) character set utf8 collate utf8_general_ci;
/* 插入测试数据 */
insert into account(name,money) values('aaa',1000);
insert into account(name,money) values('bbb',1000);
insert into account(name,money) values('ccc',1000);
以前我们与MySQL数据库打交道的代码都是这样写的:
package cn.liayun.utils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import cn.liayun.domain.Account;
// 模拟Dao,公共的代码都应该提取出来
public class TestJdbcFramework {
public void add(Account a) throws SQLException {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils_DBCP.getConnection();
String sql = "insert into account(id,name,money) values(?,?,?)";
st = conn.prepareStatement(sql);
st.setInt(1, a.getId());
st.setString(2, a.getName());
st.setDouble(3, a.getMoney());
st.executeUpdate();
} finally {
JdbcUtils_DBCP.release(conn, st, rs);
}
}
public void delete(int id) throws SQLException {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils_DBCP.getConnection();
String sql = "delete from account where id=?";
st = conn.prepareStatement(sql);
st.setInt(1, id);
st.executeUpdate();
} finally {
JdbcUtils_DBCP.release(conn, st, rs);
}
}
public void update(Account a) throws SQLException {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils_DBCP.getConnection();
String sql = "update account set name=? where id=?";
st = conn.prepareStatement(sql);
st.setString(1, a.getName());
st.setInt(2, a.getId());
st.executeUpdate();
} finally {
JdbcUtils_DBCP.release(conn, st, rs);
}
}
public Account find(int id) throws SQLException {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
String sql = "select * from account where id=?";
st = conn.prepareStatement(sql);
st.setInt(1, id);
rs = st.executeQuery();
if (rs.next()) {
Account a = new Account();
// blablabla......
return a;
}
return null;
} finally {
JdbcUtils.release(conn, st, rs);
}
}
public List getAll() throws SQLException {
// blablabla......
return null;
}
}
JdbcUtils_DBCP这个类的代码怎么写,也可以参考我的笔记《Java Web基础入门第五十八讲 开源数据库连接池》。这样写代码你是不是要疯了啊!我们可以明显看到:所有实体的增删改操作代码基本相同,仅仅是发送给数据库的SQL语句不同而已,因此可以把增删改操作的所有相同代码抽取到工具类的一个update方法中,并定义参数接收变化的SQL语句。
封装通用的update方法和query方法
现在我们先来抽取增删改的公共代码。定义一个JdbcUtils工具类,工具类负责获取数据库连接,释放资源,执行SQL的update操作,代码如下:
package cn.liayun.utils;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;
import org.apache.commons.dbcp.BasicDataSourceFactory;
public class JdbcUtils {
private static DataSource ds = null;
//在静态代码块里面初始化DBCP链接池
static {
try {
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");
Properties prop = new Properties();
prop.load(in);
BasicDataSourceFactory factory = new BasicDataSourceFactory();
ds = factory.createDataSource(prop);
} catch (Exception e) {
throw new ExceptionInInitializerError(e);
}
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();// 不会将真正的MySQL驱动返回的Connection返回给你
}
public static void release(Connection conn, Statement st, ResultSet rs) {
if (rs != null) {
try {
rs.close();
} catch (Exception e) {
e.printStackTrace();
}
rs = null;
}
if (st != null) {
try {
st.close();
} catch (Exception e) {
e.printStackTrace();
}
st = null;
}
if (conn != null) {
try {
conn.close();
} catch (Exception e) {
e.printStackTrace();
}
conn = null;
}
}
//以下方法用来抽取增删改的公共代码
/*
* 别人调用以下方法时,会传进来如下参数:
* String sql = "insert into account(id,name,money) values(?,?,?)";
* Object[]{4,"liayun",1000}
*/
public static void update(String sql, Object[] params) throws SQLException {
Connection conn = null;
PreparedStatement st = null;
ResultSet rs = null;
try {
conn = getConnection();
st = conn.prepareStatement(sql);
for (int i = 0; i < params.length; i++) {
st.setObject(i + 1, params[i]);
}
st.executeUpdate();
} finally {
release(conn, st, rs);
}
}
}
从上面代码可以看到抽取增删改的公共代码还是比较容易的,但最麻烦的就是抽取查询的公共代码,现在来攻克它。
下面,我们来抽取查询的公共代码,优化查询,替换掉所有的查询。在JdbcUtils工具类中编写执行SQL语句的通用的query操作。萌新们初次编写时,代码一般都会是这样:
框架的设计者是不知道要执行的sql语句的,执行该sql语句,拿到结果集,如何对结果集进行处理,框架的设计者也是不知道的,那该怎么办呢?框架的设计者不知道没关系,但使用该框架的人是知道怎么对结果集进行处理的,那就让他去做这种事情。所以,代码该这样写:框架的设计者(我)对外暴露一个接口,使用该框架的人去实现该接口做这种事情,框架的设计者(我)针对接口进行调用。这即是一种设计模式,叫策略模式。
首先,框架的设计者(我)对外暴露一个接口(该接口存放在JdbcUtils工具类中):
然后,框架的设计者(我)针对接口进行调用,那么在JdbcUtils工具类中的query方法就变为:
使用该框架的人去实现ResultSetHandler接口,并对结果集进行处理。那么别人写的TestJdbcFramework类中的find方法就可以简化为:
明显发现这样写与以前写毫无区别,也要对结果集进行遍历啊……等等操作!设计出来的框架就是废物!
编写常用的结果集处理器
为了提高框架的易用性,我们可以事先就针对结果集写好一些常用的处理器,比如将结果集转换成bean对象的处理器,将结果集转换成bean对象的List集合的处理器。
BeanHandler——将结果集转换成bean对象的处理器
框架的设计者(我)针对结果集写一个将结果集转换成bean对象的处理器(该类实现了ResultSetHandler接口,同样在JdbcUtils工具类内中)。
/*
* 框架设计者在编写这个处理器的时候,并不知道把结果集处理到哪个对象里面去,
* 框架的设计者不知道没关系,但使用该框架的人总该知道吧,到时候他在使用这个结果集处理器时传递给框架设计者即可。
*/
class BeanHandler implements ResultSetHandler {
private Class clazz;
public BeanHandler(Class clazz) {
this.clazz = clazz;
}
@Override
public Object handler(ResultSet rs) {
try {
//看结果集里面有没有数据?
if (!rs.next()) {
return null;
}
//创建出封装结果集的bean
Object bean = clazz.newInstance();
//得到结果集的元数据,以获知结果集中的信息
ResultSetMetaData meta = rs.getMetaData();
int count = meta.getColumnCount();
for (int i = 0; i < count; i++) {
String name = meta.getColumnName(i + 1);//获取到结果集每列的列名
Object value = rs.getObject(name);//获取到结果集每列的值(id-1)
//反射出bean上与列名相应的属性
Field f = bean.getClass().getDeclaredField(name);
f.setAccessible(true);
f.set(bean, value);//取到每列的数据,并把每列的数据全部整到bean上与列名相应的属性上面去了
}
return bean;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
BeanListHandler——将结果集转换成bean对象的List集合的处理器
框架的设计者(我)针对结果集写一个将结果集转换成bean对象的List集合的处理器(该类实现了ResultSetHandler接口,同样在JdbcUtils工具类内中)。
class BeanListHandler implements ResultSetHandler {
private Class clazz;
public BeanListHandler(Class clazz) {
this.clazz = clazz;
}
@Override
public Object handler(ResultSet rs) {
List list = new ArrayList();
try {
while (rs.next()) {
Object bean = clazz.newInstance();
ResultSetMetaData meta = rs.getMetaData();
int count = meta.getColumnCount();
for (int i = 0; i < count; i++) {
String name = meta.getColumnName(i + 1);
Object value = rs.getObject(name);
Field f = bean.getClass().getDeclaredField(name);
f.setAccessible(true);
f.set(bean, value);
}
list.add(bean);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
return list;
}
}
至此,我设计出来的简单的JDBC框架的完整代码为:
package cn.liayun.utils;
import java.io.InputStream;
以上是关于编写自己的JDBC框架的主要内容,如果未能解决你的问题,请参考以下文章