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; } }
作者:sixleaves
链接:https://www.jianshu.com/p/d52fcb6de298