1. 封装DDL\DML\DQL工具类
-
上一篇博客做了什么
。在上一篇博客中我分享了如何抽取JDBC工具类。该工具类提供数据库的会话创建
、关闭资源
等我们操作数据库过程中用到的方法。 -
本章博客做什么
。在这一篇博客中我们分享下关于抽取操作数据库的DDL
、DML
、DQL
的工具类。
1.1 为什么要自己抽取工具类,不用现成的
-
有助于了解底层的实现
。Hibernate和MyBatis底层很多都是基于反射来做ORM的 -
锻炼自己的编码能力
。 -
尝试思考如何设计框架
。
1.2 封装DML和DDL工具方法
我们首先要思考如何封装DML和DDL工具方法。DML指常用的数据库的增删改
、DDL主要指建表
和改表
语句.
这两个类操作可以封装成一个方法,我们定义这个方法叫update
- 它们这两类语句的目的都不是
获取数据
. - 他们本质都是
sql
。
update方法的设计
-
方法参数
。所以在设计方法的时候,我们可以很明确,需要传递字符串sql语句
.并且因为我们用的是预编译执行体
, 所以我们还需要传递要设置到预编译语句中的值。 -
返回值
。本质上我们可以返回执行是否成功给调用者,但是对于DML语句,我们可以返回受到影响的行数
.如果执行失败我们就返回-1.对于DDL, 如果执行成功返回0, 执行失败返回-1.总之-1表示失败,非负数表示sql执行成功,并且受影响的行数有几行. -
方法体
。考虑好参数、返回值。接着就是方法体如何写, 其实就是加载注册驱动, 建立会话连接, 创建预编译执行体, 执行sql.
public class CommonUtil {
public static int update(String sql, Object...values) throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
int rows = -1;
try {
conn = JDBCUtil.getConn();
ps = conn.prepareStatement(sql);
int len = values.length;
for (int i = 0; i <len; i++) {
ps.setObject(i+1, values[i]);
}
rows = ps.executeUpdate();
} finally {
JDBCUtil.close(conn, ps);
return rows;
}
}
}
1.3 封装通用查询方法
DML和DDL的方法封装很简单。较为复杂的就是DQL方法的封装.因为对于DQL我们需要处理查询后返回的数据, 而这些数据如何处理对于不同的实体对象又不一样。我会分享如何一步步的抽取通用的查询方法。
什么是ORM
ORM
是Object Relation Mapping
的缩写.在Web开发中其意思就是将数据库查询数据, 转换成程序中的对象的过程.做ORM的框架有很多, 比如Hibernate
, Mybatis
.
query方法的设计
先给方法取给通俗移动的方法名, 既然是查询,就叫query.
-
参数设计
. 和update道理一样, 我们肯定需要sql
和参数
. -
返回值设计
.既然是ORM,返回的当然一个相应的对象数组或者一个对象。因为查询可能是多条数据,可能是只要获取一条数据。
1.3.1 ORM版本一,根据列索
引获取数据
先使用列索引, 在关于数据库的操作中, 索引都是从1开始计数.
@Test
public void testResultTest1() {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "select id, name, age, gender, birthday from t_customer";
List<Customer> customers = new ArrayList<>();
try {
conn = JDBCUtil.getConn();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
int id = rs.getInt(1);
String name = rs.getString(2);
int age = rs.getInt(3);
String gender = rs.getString(4);
Date birthday = rs.getDate(5);
customers.add(new Customer(id, name, age, gender,birthday));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtil.close(conn, ps, rs);
}
for (Customer c : customers) {
System.out.println(c);
}
}
1.3.2 ORM版本二,根据标签
获取虚表数据
由于使用列索引的方式, 是一种硬编码.只要当前查询语句的列一变,那么代码也得改.所以基于这种考虑。我们进行重构,改成基于虚表中的列名
.结果集对象的getXxx(String)
这个接口接受的就是虚表的列名而不是, 对于有别名的使用的是别名.
@Test
public void testResultTest2() {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "select id, name, age, gender, birthday birth from t_customer";
List<Customer> customers = new ArrayList<>();
try {
conn = JDBCUtil.getConn();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while (rs.next()) {
// 使用虚表的列名
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
String gender = rs.getString("gender");
Date birthday = rs.getDate("birth");
customers.add(new Customer(id, name, age, gender,birthday));
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtil.close(conn, ps, rs);
}
for (Customer c : customers) {
System.out.println(c);
}
}
1.3.3 ORM版本三,根据表元数据
和属性对象
创建.
在1.3.2 的这个版本中, 我们需要需要手动的指定列的名称.但是有一部分信息我们没有利用。
1.3.2 存在的问题
-
浪费了JavaBean类的信息
。JavaBean
对象的信息, 或者称为模型对象
、domain对象
.我们定义JavaBean对象的时候, 是按照查询的时候虚表的列名
进行定义的.也就是JavaBean的属性名
和虚表的列名
是一模一样的。我们可以通过反射的方式获取对象的属性对象
.在设置属性对象的值,从而实现更加通用的代码。
下面的代码用到了反射技术.主要步骤如下
- 获取
结果集元数据对象
(即描述结果集的对象,结果集其实就是虚表) - 根据
结果集元数据对象
获取结果集中的行数
. - 根据
结果集元数据对象
获取每一列的列名.
@Test
public void testResultTest3_metadata() {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
String sql = "select id, name, age, gender, birthday birth from t_customer";
List<Customer> customers = new ArrayList<>();
try {
conn = JDBCUtil.getConn();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
ResultSetMetaData rsmd = ps.getMetaData();
// 获取列的总个数
int rolCount = rsmd.getColumnCount();
// 获取原始列表的列名,这个列名不是虚表名
for (int i = 0; i < rolCount; i++) {
// System.out.print(rsmd.getColumnName(i+1) + "\t");
}
// System.out.println();
// 获取虚表的列名, 使用列标签
ArrayList<String> colLabels = new ArrayList<>();
for (int i = 0; i < rolCount; i++) {
colLabels.add(rsmd.getColumnLabel(i+1));
}
while (rs.next()) {
// 使用虚表的列名
// 元数据 ---- 描述数据的数据称为元数据.获取表的描述数据
for (int i = 0; i < rolCount; i++) {
Object value = rs.getObject(colLabels.get(i));
System.out.print(value + "\t");
}
System.out.println();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtil.close(conn, ps, rs);
}
for (Customer c : customers) {
System.out.println(c);
}
}
1.3.4 ORM版本四,根据反射
和泛型
封装通用查询版本
1.3.3 版本的问题
-
每种类型都要再写一套query方法
。1.3.3的代码只使用于Customer类, 我们可以使用泛型技术, 进一步抽取代码,将类的类型定义成一种类型
.在这里我们将要返回的JavaBean类型定义为一种泛型类型。
public static <T> List<T> query(Class<T> clazz, String sql, Object...values)
throws IllegalAccessException, SQLException, NoSuchFieldException,
InstantiationException, ClassNotFoundException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<T> beans = new ArrayList<T>();
try {
conn = JDBCUtil.getConn();
ps = conn.prepareStatement(sql);
int valuesCount = values.length;
for (int i = 0; i < valuesCount; i++) {
ps.setObject(i+1, values[i]);
}
rs = ps.executeQuery();
ResultSetMetaData rsmd = ps.getMetaData();
// 获取列的总个数
int rolCount = rsmd.getColumnCount();
while (rs.next()) {
T bean = clazz.newInstance();
// 使用虚表的列名
// 元数据 ---- 描述数据的数据称为元数据.获取表的描述数据
for (int i = 0; i < rolCount; i++) {
String label = rsmd.getColumnLabel(i+1);
Field field = Customer.class.getDeclaredField(label);
field.setAccessible(true);
Object value = rs.getObject(label);
field.set(bean, value);
}
beans.add(bean);
}
} finally {
JDBCUtil.close(conn, ps, rs);
}
return beans;
}
最终封装好的CommonUtils.
public class CommonUtil {
public static int update(String sql, Object...values) throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
int rows = -1;
try {
conn = JDBCUtil.getConn();
ps = conn.prepareStatement(sql);
int len = values.length;
for (int i = 0; i <len; i++) {
ps.setObject(i+1, values[i]);
}
rows = ps.executeUpdate();
} finally {
JDBCUtil.close(conn, ps);
return rows;
}
}
public static <T> List<T> query(Class<T> clazz, String sql, Object...values)
throws IllegalAccessException, SQLException, NoSuchFieldException,
InstantiationException, ClassNotFoundException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List<T> beans = new ArrayList<T>();
try {
conn = JDBCUtil.getConn();
ps = conn.prepareStatement(sql);
int valuesCount = values.length;
for (int i = 0; i < valuesCount; i++) {
ps.setObject(i+1, values[i]);
}
rs = ps.executeQuery();
ResultSetMetaData rsmd = ps.getMetaData();
// 获取列的总个数
int rolCount = rsmd.getColumnCount();
while (rs.next()) {
T bean = clazz.newInstance();
// 使用虚表的列名
// 元数据 ---- 描述数据的数据称为元数据.获取表的描述数据
for (int i = 0; i < rolCount; i++) {
String label = rsmd.getColumnLabel(i+1);
Field field = Customer.class.getDeclaredField(label);
field.setAccessible(true);
Object value = rs.getObject(label);
field.set(bean, value);
}
beans.add(bean);
}
} finally {
JDBCUtil.close(conn, ps, rs);
}
return beans;
}
}
后续
请继续关注JDBC基础(三).下篇我将分享的是
在JDBC中如何操作事务
开发中应用数据库连接池
网友评论