4. 自定义DBUtils
前言
在上一章节,我们使用 Apache-DBUtils 实现了数据库的增删查改,的确使用起来很方便。但是除了方便之余,我们还要思考一下这个 Apache-DBUtils 是如何实现的。
例如在查询的时候,提供的是可变参数的,那么如何将这些可变参数进行参数的设置,进行查询的呢?
queryRunner.update(sql, "参数1", "参数2", "参数3", "参数4");
还有如何在查询的时候获取字段名,字段个数的呢?
下面我们来自己写一个简单的自定义 DBUtils,看看实现的基本原理。
元数据
1. 存在的问题
在上面我们描述中,可以发现我们想要实现一个自定义的 DBUtils 工具的话,首先要可以知道如何知道查询的 字段名、参数个数等。
例如:假设我查询的SQL语句是 select * from user
,那么我怎么去知道这个表有哪些字段,参数到底有多少个?
2.问题的解决
我们想要知道查询的 字段名, 参数个数 等, 这些可以通过数据库的元数据库进行获得.
元数据在建立框架和架构方面是特别重要的知识,下面可以使用数据库的元数据来创建自定义JDBC工具包, 模仿DBUtils.
下面先来介绍一下什么是元数据。
3. 什么是元数据
元数据(MetaData),即定义数据的数据。
打个比方,就好像我们要想搜索一首歌(歌本身是数据),而我们可以通过歌名,作者,专辑等信息来搜索,那么这些歌名,作者,专辑等等就是这首歌的元数据。因此数据库的元数据就是一些注明数据库信息的数据。
歌曲:凉凉
作词:刘畅
演唱: 杨宗纬 张碧晨
时长: 3分28秒
...
简单来说: 数据库的元数据就是 数据库、表、列的定义信息。
主要我们会使用到的元数据有以下的两种:
① 由PreparedStatement对象的getParameterMetaData ()方法获取的是ParameterMetaData对象。
② 由ResultSet对象的getMetaData()方法获取的是ResultSetMetaData对象。
3.1 ParameterMetaData
3.1.1概述
ParameterMetaData是由preparedStatement对象通过getParameterMetaData方法获取而来,ParameterMetaData
可用于获取有关PreparedStatement
对象和其预编译sql语句
中的一些信息. eg:参数个数,获取指定位置占位符的SQL类型
获得ParameterMetaData:
ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData()
3.1.2ParameterMetaData相关的API
- int getParameterCount(); 获得参数个数
- int getParameterType(int param) 获取指定参数的SQL类型。 (注:MySQL不支持获取参数类型)
所以如果使用 Mysql 数据库,我们只能使用如下:
//4. 获得参数的元数据
ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
// 获得sql语句里面参数的个数
int parameterCount = parameterMetaData.getParameterCount();
System.out.println("parameterCount = "+parameterCount);
3.1.3实例代码
/**
* ParameterMetaData相关的API
* @throws Exception
*/
@Test
public void test03() throws Exception {
//0 根据druid.properties创建配置文件对象
Properties properties = new Properties();
// 关联druid.properties文件
InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(is);
//1. 创建DataSource
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//2. 从数据源(连接池)获得连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 3. 设置SQL语句
// 使用 prepareStatement 来解决SQL注入
String sql = "select * from user where id = ?"; // 设置SQL, 使用问号?设置查询的条件参数
PreparedStatement preparedStatement = connection.prepareStatement(sql); // 获取preparedStatement
//4. 获得参数的元数据
ParameterMetaData parameterMetaData = preparedStatement.getParameterMetaData();
// 获得sql语句里面参数的个数
int parameterCount = parameterMetaData.getParameterCount();
System.out.println("parameterCount = "+parameterCount);
}
3.2 ResultSetMetaData
3.2.1概述
ResultSetMetaData 是由 ResultSet 对象通过 getMetaData 方法获取而来,ResultSetMetaData
可用于获取有关ResultSet
对象中列的类型和属性的信息。
获得 ResultSetMetaData 的两种方式:
// 方式一:这种方式最干脆
ResultSetMetaData resultSetMetaData = preparedStatement.getMetaData(); // 通过 preparedStatement 获取
// 方式二:
// 1. 设置SQL语句
// 使用 prepareStatement 来解决SQL注入
String sql = "select * from user where id = ?"; // 设置SQL, 使用问号?设置查询的条件参数
PreparedStatement preparedStatement = connection.prepareStatement(sql); // 获取preparedStatement
//设置参数
preparedStatement.setObject(1, 2);
// 2. 获取结果集元数据
ResultSet resultSet = preparedStatement.executeQuery();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData(); // 通过结果集 resultSet 获取
3.2.2 resultSetMetaData 相关的API
- getColumnCount(); 获取结果集中列项目的个数
- getColumnName(int column); 获得数据指定列的列名
- getColumnTypeName();获取指定列的SQL类型
- getColumnClassName();获取指定列SQL类型对应于Java的类型
3.2.3 实例代码
/**
* resultSetMetaData 相关的API
* @throws Exception
*/
@Test
public void test04() throws Exception {
//0 根据druid.properties创建配置文件对象
Properties properties = new Properties();
// 关联druid.properties文件
InputStream is = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(is);
//1. 创建DataSource
DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
//2. 从数据源(连接池)获得连接对象
Connection connection = dataSource.getConnection();
System.out.println(connection);
// 3. 设置SQL语句
// 使用 prepareStatement 来解决SQL注入
String sql = "select * from user where id = ?"; // 设置SQL, 使用问号?设置查询的条件参数
PreparedStatement preparedStatement = connection.prepareStatement(sql); // 获取preparedStatement
//4. 获取结果集元数据
ResultSetMetaData resultSetMetaData = preparedStatement.getMetaData();
int columnCount = resultSetMetaData.getColumnCount();
System.out.println("获取结果集中列项目的个数 columnCount = " + columnCount); // 获取结果集中列项目的个数
for (int i = 1; i <= columnCount; i++) {
//获取每列的列名
String columnName = resultSetMetaData.getColumnName(i);
System.out.println("获取每列的列名: " + columnName);
}
}
4.小结
- 元数据: 描述数据的数据. mysql元数据: 用来定义数据库, 表 ,列信息的 eg: 参数的个数, 列的个数, 列的类型...
- mysql元数据:
- ParameterMetaData
- ResultSetMetaData
自定义DBUtils增删改
1.需求
模仿DBUtils, 完成增删改的功能
2.分析
- 创建MyQueryRunner类, 定义dataSource, 提供有参构造方法
- 定义int update(String sql, Object...params)方法
//1.从dataSource里面获得connection
//2.根据sql语句创建预编译sql语句对象
//3.获得参数元数据对象, 获得参数的个数
//4.遍历, 从params取出值, 依次给参数? 赋值
//5.执行
//6.释放资源
3.实现
3.1 创建 MyQueryRunner 类
/**
* @author Aron.li
* @date 2021/1/26 23:36
*/
public class MyQueryRunner {
//定义连接池
private DataSource dataSource;
//构造器: 传入一个连接池
public MyQueryRunner(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 执行增删改的SQL语句的方法
* @param sql
* @param params
* @return
* @throws SQLException
*/
public int update(String sql,Object... params) throws SQLException {
//1. 获取连接对象
Connection connection = dataSource.getConnection();
//2. 预编译SQL语句
PreparedStatement pstm = connection.prepareStatement(sql);
//3. 设置参数
//3.1 获取参数的个数
ParameterMetaData parameterMetaData = pstm.getParameterMetaData(); // 获取参数元数据
int parameterCount = parameterMetaData.getParameterCount(); // 获取参数的个数
//3.2 设置参数
for (int i = 1; i <= parameterCount; i++) {
pstm.setObject(i,params[i-1]);
}
//4. 执行SQL语句
int num = pstm.executeUpdate(); // 返回受影响的行数
//5. 关闭资源
pstm.close();
connection.close();
return num;
}
}
3.2 使用 MyQueryRunner 类新增一条数据
/**
* 使用 MyQueryRunner 类新增一条数据
*/
@Test
public void test09() throws SQLException {
//1. 获取Druid连接池
DataSource dataSource = DruidUtil.getDataSource();
//2. 创建自定义 MyQueryRunner 类
MyQueryRunner queryRunner = new MyQueryRunner(dataSource);
//3. 设置插入数据的SQL
String sql = "insert into user values (null,?,?,?,?)";
//4. 使用QueryRunner对象调用update(sql,...)执行增删改的SQL语句
int i = queryRunner.update(sql, "特朗普", "tlbsb", "USA", "123456");
System.out.println(i);
}
3.3 使用 MyQueryRunner 类 更新一条数据
/**
* 使用 MyQueryRunner 类更新一条数据
*/
@Test
public void test10() throws SQLException {
//1. 获取Druid连接池
DataSource dataSource = DruidUtil.getDataSource();
//2. 创建自定义 MyQueryRunner 类
MyQueryRunner queryRunner = new MyQueryRunner(dataSource);
//3. 设置插入数据的SQL
String sql = "update user set name=? where id = ?";
//4. 使用QueryRunner对象调用update(sql,...)执行增删改的SQL语句
int i = queryRunner.update(sql, "奥巴马", 8);
System.out.println(i);
}
3.4 使用 MyQueryRunner 类 删除一条数据
/**
* 使用 MyQueryRunner 类删除一条数据
*/
@Test
public void test11() throws SQLException {
//1. 获取Druid连接池
DataSource dataSource = DruidUtil.getDataSource();
//2. 创建自定义 MyQueryRunner 类
MyQueryRunner queryRunner = new MyQueryRunner(dataSource);
//3. 设置插入数据的SQL
String sql = "delete from user where id = ?";
//4. 使用QueryRunner对象调用update(sql,...)执行增删改的SQL语句
int i = queryRunner.update(sql, 8);
System.out.println(i);
}
4.小结
- 先模拟DBUtils写出架子
- update()
- 封装了PrepareStatement
- 用到了参数元数据
自定义DBUtils查询多条数据封装到List<POJO>
在上面我自定义 MyQueryRunner 类中,我们已经实现了 增删改,那么再来实现一个 查询多条数据的 方法。
1.在 MyQueryRunner 类中,增加查询多条数据的方法 queryList
/**
* @author Aron.li
* @date 2021/1/26 23:36
*/
public class MyQueryRunner {
//定义连接池
private DataSource dataSource;
//构造器: 传入一个连接池
public MyQueryRunner(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 执行增删改的SQL语句的方法
* @param sql
* @param params
* @return
* @throws SQLException
*/
public int update(String sql,Object... params) throws SQLException {
//1. 获取连接对象
Connection connection = dataSource.getConnection();
//2. 预编译SQL语句
PreparedStatement pstm = connection.prepareStatement(sql);
//3. 设置参数
//3.1 获取参数的个数
ParameterMetaData parameterMetaData = pstm.getParameterMetaData(); // 获取参数元数据
int parameterCount = parameterMetaData.getParameterCount(); // 获取参数的个数
//3.2 设置参数
for (int i = 1; i <= parameterCount; i++) {
pstm.setObject(i,params[i-1]);
}
//4. 执行SQL语句
int num = pstm.executeUpdate(); // 返回受影响的行数
//5. 关闭资源
pstm.close();
connection.close();
return num;
}
/**
* 查询到多条数据封装到List<T>
* @param sql
* @param clazz
* @param params
* @param <T>
* @return
* @throws Exception
*/
public <T> List<T> queryList(String sql, Class<T> clazz, Object... params) throws Exception {
//1. 获取连接对象
Connection connection = dataSource.getConnection();
//2. 预编译SQL语句
PreparedStatement pstm = connection.prepareStatement(sql);
//3. 设置参数
//3.1 获取参数的个数
ParameterMetaData parameterMetaData = pstm.getParameterMetaData();
int parameterCount = parameterMetaData.getParameterCount();
for (int i = 1; i <= parameterCount; i++) {
pstm.setObject(i,params[i-1]);
}
//4. 执行SQL语句
ResultSet rst = pstm.executeQuery();
//获取结果集元数据
ResultSetMetaData rstMetaData = rst.getMetaData();
//获取结果集的列数
int columnCount = rstMetaData.getColumnCount();
//1. 获取POJO中的所有方法
Method[] methods = clazz.getMethods();
//声明一个集合存储t
List<T> tList = new ArrayList<>();
while (rst.next()) {
//创建出封装数据的POJO对象
T t = clazz.newInstance();
for (int i = 1; i <= columnCount; i++) {
//获取每列的名字(字段名)
String columnName = rstMetaData.getColumnName(i);
//1. 获取每一行数据的各个字段的数据
Object columnValue = rst.getObject(columnName);
//2. 调用对象的对应的set方法,往对象中设置数据
//遍历出POJO的每一个方法
for (Method method : methods) {
//获取方法名
String methodName = method.getName();
//匹配方法名
if (methodName.equalsIgnoreCase("set"+columnName)) {
//说明匹配到了对应的set方法,那么就调用这个set方法,设置columnValue
method.invoke(t,columnValue);
}
}
}
tList.add(t);
}
//关闭资源
rst.close();
pstm.close();
connection.close();
return tList;
}
}
2. 测试查询多条数据
/**
* 使用 MyQueryRunner 类查询多条数据
*/
@Test
public void test12() throws Exception {
//1. 获取Druid连接池
DataSource dataSource = DruidUtil.getDataSource();
//2. 创建自定义 MyQueryRunner 类
MyQueryRunner queryRunner = new MyQueryRunner(dataSource);
//3. 设置查询多条数据的SQL
String sql = "select * from user where id > ?";
//4. 使用QueryRunner对象调用queryList查询多个数据
List<User2> userList = queryRunner.queryList(sql, User2.class, 2);
//5. 遍历结果
for (User2 user : userList) {
System.out.println(user);
}
}