一、引入
我们刚刚完成了JDBC第一个版本和第二个版本的封装,但是这两个版本只是优化了JDBC操作中的某几步(下图中的1、2、8步)。
下面会使用BaseDao将3、4、5、6、7步优化。
我们后期,基本上每一个数据表都应该有一个对应的 DAO 接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些 DAO 的实现类可以抽取一个公共的父类,我们称为 BaseDao
。

二、非DQL方法封装
package com.atguigu.api.utils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.Objects;
/**
* 封装Dao数据库重复代码!
* TODO:
* 封装两个方法 一个简化非DQL
* 一个简化DQL
*
* List<Object> params
*/
public class BaseDao {
/**
* 封装简化非SQL 语句
* @param sql 带占位符的SQL语句
* @param params 占位符的值 PS:传入占位符的值,必须等于SQL语句?位置!
* @return 执行影响的行数
*/
public int executeUpdate(String sql, Objects... params) throws Exception {//可变参数必须存在于形参列表的最后一位
//获取连接
Connection connection = JdbcUtilsV2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
//5.占位符赋值
//可变参数可以当数组使用
for (int i = 1; i <= params.length; i++) {
preparedStatement.setObject(i, params[i - 1]);
}
int rows = preparedStatement.executeUpdate();
preparedStatement.close();
//是否回收连接,需要考虑是不是事务
if (connection.getAutoCommit()) {
//没有开启事务
//没有开启事务 正常回收连接!
JdbcUtilsV2.freeConnection();
}
// connection.setAutoCommit(false);//开启事务了 不要管连接即可! 由业务层来处理
return rows;
}
}
使用代码:
package com.atguigu.api.utils;
import org.junit.Test;
import java.sql.*;
import java.util.*;
public class PSCURDPart extends BaseDao{
@Test
public void testInsert() throws ClassNotFoundException, SQLException {
String sql = "insert into employee(id, salary) values(?, ?);";
int rows = executeUpdate(sql, 1, 111);
}
}
三、DQL查询方法封装
DQL的封装要比非DQL封装更难一些
1)思路
非DQL语句封装方法 ->返回值固定为int
DQL语句封装方法 -> 返回值是什么类型呢?
DQL -> 返回结果是List<Map> -> 也就是说我将一行数据装到Map对象,因为有多行数据,所以最终要返回 List<Map>
but,我们的返回值并不是List<Map>, 不是说这个类型不能用,是它不好用,map里面的数据有先天的优势,key 和 value都是自定义的,不用先设定好
但是map是没有数据校验机制的,例如我往Map中传年龄,传的是-1,那么Map中存的就是-1
而且Map也不支持反射操作
实际上: 数据库中的数据 都会对应到java的实体类
数据库中的表中的一行数据等于 java类中的一个对象,多行数据可以等于Java实体类中的集合,这样的做法其实就是面向对象的一种思维了
所以DQL语句返回值应该为某一个类型的实体类集合 List<T> list
<T> 声明一个方法泛型,不确定类型,此时我们就可以让对方传一个class对象,这个方法的好处有两点
1.确定泛型 如果你传入的是User.class,那么 T = User
2.后期要使用反射技术为属性赋值
public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params);
2)代码示例
/**
* 将查询结果封装到一个实体类集合!
*
* @param clazz 要接值的实体类集合的模板对象
* @param sql 查询语句,要求列名或者别名等于实体类的属性名!如果不等于,那就起别名
* @param params 占位符的值,要和?位置对象传递
* @param <T> 声明的结果的泛型
* @return 查询的实体类集合
* @throws Exception
*/
public <T> List<T> executeQuery(Class<T> clazz, String sql, Object... params) throws Exception {
//获取连接
Connection connection = JdbcUtilsV2.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement(sql);
if (params != null && params.length != 0) {
for (int i = 1; i <= params.length; i++) {
preparedStatement.setObject(i, params[i - 1]);
}
}
ResultSet resultSet = preparedStatement.executeQuery();
List<T> list = new ArrayList<>();
//TODO:metaData 装的当前 结果集 列的信息对象!(他可以获取列的名称 根据下角标,可以获取列的数量)
ResultSetMetaData metaData = resultSet.getMetaData();
//有了它以后,我们可以水平遍历列!
int columnCount = metaData.getColumnCount();
//一行数据对应一个 T 类型的对象
while (resultSet.next()) {
T t = clazz.newInstance();//调用类的无参构造函数实例化对象!
for (int i = 1; i <= columnCount; i++) {
//对象的属性值
Object value = resultSet.getObject(i);
//对象的属性名
String propertyName = metaData.getColumnLabel(i);
//反射给对象的属性值
Field field = clazz.getDeclaredField(propertyName);
field.setAccessible(true);//属性可以设置,打破private的修饰限制
/**
* 参数1:要赋值的对象 如果属性是静态属性,第一个参数可以为null!
* 参数2:具体的属性值
*/
field.set(t, value);
}
// 一行数据的所有列全部存到了Map中,然后将Map存到集合中即可
list.add(t);
}
//关闭资源
resultSet.close();
preparedStatement.close();
if (connection.getAutoCommit()) {
//没有事务,可以关闭
JdbcUtilsV2.freeConnection();
}
return list;
}