文章目录
现在学习的是如何在上一章的工具类基础上再次将相同的功能抽象出来。例如,每次执行SQL语句时都需要预编译SQL语句、获得返回集等等,能否将这些功能封装到一起,将不同的SQL语句(增删改)都封装到一起,传入的参数数目不同也可以调用同一个方法进行UPDATE操作;或者是将查询的SELECT语句封装到一个方法中,不管传入需要查询的参数有多少,都可以调用同一个方法。
一、数据库源信息介绍
1.数据库的源信息:DataBaseMetaData
- java.sql.DataBaseMetaData:封装了整个数据库的综合信息
- 例如:
- String getDatabaseProductName():获取数据库产品的名称
- int getDatabaseProductVersion():获取数据库产品的版本号
2.参数的源信息:ParameterMetaData
- java.sql.ParameterMetaData:封装的是预编译执行者对象中每个参数的类型和属性
- 这个对象可以通过预编译执行者对象中的getParameterMetaData()方法来获取
- 核心功能:int getParameterCount():获取sql语句中参数的个数
3.结果集的源信息:ResultSetMetaData
- java.sql.ResultSetMetaData:封装的是结果集对象中列的类型和属性
- 这个对象可以通过结果集对象中的getMetaData()方法来获取
- 核心功能:
- int getColumnCount():获取列的总数
- String getColumnName(int i):获取列名
二、自定义JDBC框架
1.将增删改功能抽象为模板
主要分为以下步骤:
- 定义所需的成员变量,如数据源、数据库连接、执行者、结果集等;
- 定义有参构造函数,为数据源对象赋值;
- 定义update()方法,封装增删改功能,形参为SQL语句和语句中占位参数;
- 定义int变量,接收SQL语句执行后影响的行数;
- 通过数据源获取到一个数据库连接;
- 通过数据库连接对象获取执行者对象并对SQL语句预编译。
- 通过执行者对象获取参数源信息对象
- 拿到参数源信息,获得sql语句中参数的个数
- 判断参数数量是否一致
- 给?占位符赋值
- 执行SQL语句进行增删改
- 返回影响行数
代码如下:
package org.example5;
import javax.sql.DataSource;
import java.sql.*;
public class JDBCTemplate {
//1.定义所需的成员变量,如数据源、数据库连接、执行者、结果集等;
private DataSource dataSource;
private Connection connection;
private PreparedStatement pst;
private ResultSet resultSet;
//2. 定义有参构造函数,为数据源对象赋值;
public JDBCTemplate(DataSource dataSource) {
this.dataSource = dataSource;
}
//4. 定义int变量,接收SQL语句执行后影响的行数;
int res = 0;
//3. 定义update()方法,封装 增删改 功能,形参为SQL语句和语句中占位参数;
public int update(String sql,Object...objs){
try {
//5. 通过数据库连接池对象,获取到一个数据库连接对象;
connection = dataSource.getConnection();
//6. 通过数据库连接对象,获取执行者对象,并对SQL语句预编译。
pst = connection.prepareStatement(sql);
//7.通过执行者对象获取参数源信息对象
ParameterMetaData parameterMetaData = pst.getParameterMetaData();
//8.拿到参数源信息,获得sql语句中参数的个数
int count = parameterMetaData.getParameterCount();
//9.判断参数数量是否一致
if (count != objs.length) throw new RuntimeException("参数个数不匹配!");
//10.给?占位符赋值
for (int i = 0; i < objs.length; i++) {
pst.setObject(i+1, objs[i]);//给任意类型字段赋值
}
//11.执行SQL语句进行增删改
res = pst.executeUpdate();
} catch (Exception throwables) {
throwables.printStackTrace();
}finally {
DataSourceUtils.close(connection, pst);
}
//12.返回影响行数
return res;
}
}
进行增删改的测试类:JDBCTemplateTest
package org.example5;
import org.junit.Test;
public class JDBCTemplateTest {
private JDBCTemplate template = new JDBCTemplate(DataSourceUtils.getDataSource());
@Test
public void insert(){
//新增数据
String sql = "INSERT INTO student VALUES (?,?,?,?)";
Object[] params = {5, "小黄", 12, "1995-02-02"};
int result = template.update(sql, params);
if(result != 0) System.out.println("添加成功!");
else System.out.println("添加失败");
}
@Test
public void update(){
String sql = "UPDATE student SET age=? WHERE name=?";
Object[] params = {22, "小黄"};
if(template.update(sql, params) != 0) System.out.println("修改成功!");
else System.out.println("修改失败!");
}
@Test
public void delete(){
String sql = "DELETE FROM student WHERE name=?";
if(template.update(sql, "小黄") != 0) System.out.println("删除成功!");
else System.out.println("删除失败!");
}
}
2.将查询功能抽象为模板
因为查询功能过于复杂,所以单独将查询抽象为模板框架。
用于执行查询功能的方法有:
- 查询一条记录并封装为对象的方法:
queryForObject()
- 查询多条记录并封装为集合的方法:
queryForList()
- 查询聚合函数并返回单条数据的方法:
queryForScalar()
- 首先,根据表中的列信息,编写实体类,提供成员变量,且成员变量的数据类型须与表中的列的数据类型保持一致。
- 编写用于处理结果集的接口,此接口仅用于为处理不同结果集的方式提供规范,具体实现类再自行实现。
- 定义泛型接口ResultSetHandler;
- 定义用于处理结果集的泛型方法 T handler(ResultSet rs)。
准备
首先定义Student类,包含表中的所有字段信息,创建set和get方法,重写toString()方法,便于打印查看。
2.1 查询表中的一条记录并封装为对象的方法
- 实现类1:查询一条记录,封装为Student对象返回——BeanHandler
- 定义一个类,实现ResultSetHandler接口
- 定义Class对象类型变量
- 通过有参构造飞变量赋值
- 重写handler方法,用于将一条记录封装到自定义对象中
- 声明自定义对象类型
- 创建传递参数的对象,为自定义对象赋值
- 判断结果集中是否有数据
- 通过结果集对象获取结果集源信息的对象
- 通过结果集源信息对象获取列数
- 通过循环遍历列数
- 通过结果集源信息对象获取列名
- 通过列名获取该列数数
- 创建属性描述其对象,将获取到的值通过该对象的set方法进行赋值
- 返回封装好的对象
代码如下:
package org.example5.handler;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
//-------实现类1:查询一条记录,封装为Student对象返回
//1.定义一个类,实现ResultSetHandler接口
public class BeanHandler<T> implements ResultSetHandler<T>{
//2.定义Class对象类型变量
private Class<T> beanClass;
//3.通过有参构造为变量赋值
public BeanHandler(Class<T> beanClass) {
this.beanClass = beanClass;
}
//4.重写handler方法,用于将一条记录封装到自定义对象中
@Override
public <T> T handler(ResultSet rs) {
//5.声明自定义对象类型
T bean = null;
try {
//6.创建传递参数的对象,为自定义对象赋值
bean = (T) beanClass.newInstance();
//7.判断结果集中是否有数据
if (rs.next()){
//8.通过结果集对象获取结果集源信息的对象
ResultSetMetaData metaData = rs.getMetaData();
//9.通过结果集源信息对象获取列数
int count = metaData.getColumnCount();
//10.通过循环遍历列数
for (int i = 1; i <= count; i++) {
//11.通过结果集源信息对象获取列名
String columnName = metaData.getColumnName(i);
//12.通过列名获取该列数据
Object value = rs.getObject(columnName);
//13.创建"属性描述器对象",将获取到的值通过该对象的set方法进行赋值
PropertyDescriptor pd = new PropertyDescriptor(columnName, beanClass);
//获取set方法
Method writeMethod = pd.getWriteMethod();
//执行set方法,给成员变量赋值
writeMethod.invoke(bean, value);
}
//14.返回封装好的对象
return bean;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
在JDBCTemplate类中编写:查询一条记录并封装为对象的方法:
//查询1:将一条记录封装成自定义对象返回
public <T> T queryForObject(String sql, ResultSetHandler<T> rsh, Object...objs){
T obj = null;
try {
//通过数据库连接池对象,获取到一个数据库连接对象;
connection = dataSource.getConnection();
//通过数据库连接对象,获取执行者对象,并对SQL语句预编译。
pst = connection.prepareStatement(sql);
//通过执行者对象获取参数源信息对象
ParameterMetaData parameterMetaData = pst.getParameterMetaData();
//拿到参数源信息,获得sql语句中参数的个数
int count = parameterMetaData.getParameterCount();
//判断参数数量是否一致
if (count != objs.length) throw new RuntimeException("参数个数不匹配!");
//给?占位符赋值
for (int i = 0; i < objs.length; i++) {
pst.setObject(i+1, objs[i]);//给任意类型字段赋值
}
//执行SQL语句进行查询
resultSet = pst.executeQuery();
//通过beanHandler对结果进行处理
obj = rsh.handler(resultSet);
} catch (Exception throwables) {
throwables.printStackTrace();
}finally {
DataSourceUtils.close(connection, pst);
}
//返回对象
return obj;
}
2.2 查询表中的多条记录并封装为对象,加入list的方法
BeanListHandler
package org.example5.handler;
import org.example5.domain.Student;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;
//-------实现类2:查询多条记录,封装为Student对象并添加到集合,返回list
//1.定义一个类,实现ResultSetHandler接口
public class BeanListHandler<T> implements ResultSetHandler<T>{
//2.定义Class对象类型变量
private Class<T> beanClass;
//3.通过有参构造为变量赋值
public BeanListHandler(Class<T> beanClass) {
this.beanClass = beanClass;
}
//4.重写handler方法,用于将多条记录封装到自定义对象中并添加到list中返回
@Override
public List<T> handler(ResultSet rs) {
//5.声明集合对象类型
List<T> list = new ArrayList<>();
try {
//7.判断结果集中是否还有数据
while (rs.next()){
//6.创建传递参数的对象,为自定义对象赋值
T bean = (T) beanClass.newInstance();
//8.通过结果集对象获取结果集源信息的对象
ResultSetMetaData metaData = rs.getMetaData();
//9.通过结果集源信息对象获取列数
int count = metaData.getColumnCount();
//10.通过循环遍历列数
for (int i = 1; i <= count; i++) {
//11.通过结果集源信息对象获取列名
String columnName = metaData.getColumnName(i);
//12.通过列名获取该列数据
Object value = rs.getObject(columnName);
//13.创建"属性描述器对象",将获取到的值通过该对象的set方法进行赋值
PropertyDescriptor pd = new PropertyDescriptor(columnName, beanClass);
//获取set方法
Method writeMethod = pd.getWriteMethod();
//执行set方法,给成员变量赋值
writeMethod.invoke(bean, value);
}
//将对象添加到list
list.add(bean);
}
} catch (Exception e) {
e.printStackTrace();
}
//14.返回封装好的对象
return list;
}
}
在JDBCTemplate类中编写:查询多条记录并封装为对象加入list的方法:
//查询2:将多条记录封装成自定义对象,添加到list再返回
public <T> List<T> queryForList(String sql, ResultSetHandler<T> rsh, Object...objs){
List<T> list = new ArrayList<>();
try {
//通过数据库连接池对象,获取到一个数据库连接对象;
connection = dataSource.getConnection();
//通过数据库连接对象,获取执行者对象,并对SQL语句预编译。
pst = connection.prepareStatement(sql);
//通过执行者对象获取参数源信息对象
ParameterMetaData parameterMetaData = pst.getParameterMetaData();
//拿到参数源信息,获得sql语句中参数的个数
int count = parameterMetaData.getParameterCount();
//判断参数数量是否一致
if (count != objs.length) throw new RuntimeException("参数个数不匹配!");
//给?占位符赋值
for (int i = 0; i < objs.length; i++) {
pst.setObject(i+1, objs[i]);//给任意类型字段赋值
}
//执行SQL语句进行查询
resultSet = pst.executeQuery();
//通过beanListHandler对结果进行处理
list = rsh.handler(resultSet);
} catch (Exception throwables) {
throwables.printStackTrace();
}finally {
DataSourceUtils.close(connection, pst);
}
//返回list
return list;
}
2.3 将聚合函数的查询结果返回的方法
ScalarHandler
package org.example5.handler;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
//1.定义一个类,实现ResultSetHandler接口
//2.重写handler方法
//3.定义一个Long类型变量
//4.判断结果集对象中是否还有数据
//5.获取结果集源信息的对象
//6.获取第一列的列名
//7.根据列名获取该列的值
//8.返回结果
//1.定义一个类,实现ResultSetHandler接口
public class ScalarHandler<T> implements ResultSetHandler {
//2.重写handler方法
@Override
public Long handler(ResultSet rs) {
//3.定义一个Long类型变量
Long value = null;
//4.判断结果集对象中是否还有数据
try {
if (rs.next()){
//5.获取结果集源信息的对象
ResultSetMetaData metaData = rs.getMetaData();
//6.获取第一列的列名
String columnName = metaData.getColumnName(1);
//7.根据列名获取该列的值
value = rs.getLong(columnName);
}
} catch (Exception throwables) {
throwables.printStackTrace();
}
//8.返回结果
return value;
}
}
在JDBCTemplate类中编写:将聚合函数的查询结果返回的方法:
//查询3:将聚合函数的查询结果返回
public Long queryForScalar(String sql, ResultSetHandler<Long> rsh, Object...objs){
Long value = null;
try {
//通过数据库连接池对象,获取到一个数据库连接对象;
connection = dataSource.getConnection();
//通过数据库连接对象,获取执行者对象,并对SQL语句预编译。
pst = connection.prepareStatement(sql);
//通过执行者对象获取参数源信息对象
ParameterMetaData parameterMetaData = pst.getParameterMetaData();
//拿到参数源信息,获得sql语句中参数的个数
int count = parameterMetaData.getParameterCount();
//判断参数数量是否一致
if (count != objs.length) throw new RuntimeException("参数个数不匹配!");
//给?占位符赋值
for (int i = 0; i < objs.length; i++) {
pst.setObject(i+1, objs[i]);//给任意类型字段赋值
}
//执行SQL语句进行查询
resultSet = pst.executeQuery();
//通过ScalarHandler对结果进行处理
value = rsh.handler(resultSet);
} catch (Exception throwables) {
throwables.printStackTrace();
}finally {
DataSourceUtils.close(connection, pst);
}
//返回
return value;
}
最后,在进行增删改的测试类——JDBCTemplateTest中编写测试以上三种查询的语句:
//下面是查询一条的函数
@Test
public void query(){
String sql = "SELECT * FROM student WHERE sid=?";
Student stu = template.queryForObject(sql, new BeanHandler<>(Student.class),1);
System.out.println(stu);
}
//下面是查询所有的函数
@Test
public void queryList(){
String sql = "SELECT * FROM student";
List<Student> stuList = template.queryForList(sql, new BeanListHandler<>(Student.class));
System.out.println(stuList.size());
for(Student stu : stuList) System.out.println(stu);
}
//下面是查询聚合函数的函数
@Test
public void queryScalar(){
String sql = "SELECT COUNT(*) FROM student";
Long value = template.queryForScalar(sql, new ScalarHandler<Long>());
System.out.println(value);
}