提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
1.JDBC框架的介绍
1.1 什么是框架
- 框架(Framework),是基于建筑概念,适用于理解软件等技术领域各种框架、架构概念。是用于承载一个系统必要功能的基础要素的集合。
- 源自于建筑学,隶属土木工程,后发展到软件工程领域
- 软件工程框架:经过验证的,具有一定功能的,半成品软件
- 经过验证
- 具有一定功能
- 半成品。
- 将重复执行的代码块进行高度封装。
- 提供一个可以访问的方式,对功能进行调用。
1.2 框架的作用
- 提高开发效率
- 增强可重用性
- 提供编写规范
- 节约维护成本
- 解耦底层实现以原理
1.3 实现一个JDBC框架需要技术
- 反射技术
- 泛型
- 集合
- JDBC
2.知识回顾
2.1 反射技术
反射机制:是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能够调用 它的任意属性和方法; 这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
在自定义JDBC框架时,我们所要用到的反射技术有
1.获取字节码文件对象 Class 类名.class; 2.根据字节码对象,创建事务描述类对象 T newInstance() : 通过字节码对象快速的生成一个本类的对象 3.获取类中的所有属性 Field[] getDeclaredFields() : 返回 Constructor 对象的一个数组,这些对象反映此 Class 对象表示的类声明的所有构造方法。 4.获取类中的方法 -- 构造方法 PropertyDescriptor(String propertyName, Class<?> beanClass) 通过调用 getFoo 和 setFoo 存取方法,为符合标准 Java 约定的属性构造一个 PropertyDescriptor。 -- 常用方法 Method getWriteMethod() : 获得应该用于写入属性值的方法。 Object invoke(Object obj, Object... args) 对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
2.2 泛型
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类 型或者是某个方法的返回值及参数类型。这个类型参数将在使用时确定。
泛型类的格式 : public class 类名<泛型>{ //此类中就多了一种 泛型类型 供此类使用 //只有在具体创建此类对象时来确定泛型的具体数据类型 } 泛型方法的格式 : 权限修饰符 状态修饰符 <泛型> 返回值类型 方法名(形式参数列表){ //此方法中就多了一种 泛型类型 供此方法使用 方法体; //return ..; } 泛型接口的格式 : public interface 接口名<泛型>{ //此 接口名中就多了一种 泛型类型 供此 接口名使用 //只有在具体创建此 接口名的实现类对象时来确定泛型的具体数据类型 }
2.3 JDBC技术
JDBC(Java DataBase Connectivity,java数据库连接) 是一种用于执行SQL语句的Java API,可以 为多种关系型数据库提供统一访问,它是由一组用Java语言编写的类和接口组成的。
相关的方法
1.注册驱动 Class.forName("com.mysql.driver"); 2.连接数据库 Connection connection = DriverManager.getConnection(数据库地址,用户名,密码); 3.创建SQL执行对象 Statement statement = connection.createStatement(); PrepareStatement prepareStatement = connenction.prepareStatement(sql语句); prepareStatement.setObject(占位符编号,值); 4.执行SQL语句 int rows = statement.excuteUpdate(sql); ResultSet resultSet = statement.excuteQuery(sql); int rows = prepareStatement.excuteUpdate(); ResultSet resultSet = prepareStatement.excuteQuery(); 5.释放资源 connection.close(); resultSet.close(); prepareStatement.close(); statement.close();
3.自定义JDBC框架—JDBCFrarmework
前面我们说过,框架就是实现了一定功能的半成品。实际上就是将重复的操作进行高度封装,提供一个可以访问的方法。这样可以提高代码的复用性,提升开发者的开发效率,提供了统一的编码格式,方便后期代码的维护。那么在JDBCFramework框架中我们要编写两个核心的功能:
- update() : 是用来做添加、删除、修改的方法,其返回值未影响的记录数。
- querySelector() : 是用来做查询单个记录,查询多条记录的方法。查询单个记录返回值是一个对象,查询多条记录的返回值是一个List集合。
3.1 准备工作
1.在数据库中准备一张学生信息表如下图:
2.在项目src目录下创建jdbc.properties配置文件
jdbc.url=jdbc:mysql://localhost:3306/mydb1
jdbc.username=root
jdbc.password=root
jdbc.driver=com.mysql.jdbc.Driver
3.创建Student的事务描述类
public class Student {
private Integer id;
private String name;
private Integer age;
public Student() {
}
public Student(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
4.提供一个JDBCUtils工具类
该类封装了数据连接与资源释放的操作。
public class JDBCUtils {
// 定义数据库配置信息
private static String url = null;
private static String driver = null;
private static String username = null;
private static String password = null;
// 读取jdbc.properties配置文件
static {
// 通过字节码对象读取配置文件
InputStream resourceAsStream = JDBCUtils.class.getClassLoader().getResourceAsStream("jdbc.properties");
// 加载配置文件到集合
Properties properties = new Properties();
try {
properties.load(resourceAsStream);
} catch (IOException e) {
e.printStackTrace();
}
// 配置信息
url = properties.getProperty("jdbc.url");
username = properties.getProperty("jdbc.username");
password = properties.getProperty("jdbc.password");
driver = properties.getProperty("jdbc.driver");
}
// 注册驱动
private static void registDriver() throws ClassNotFoundException {
Class.forName(driver);
}
// 获取数据库连接
public static Connection getConnection() throws SQLException, ClassNotFoundException {
registDriver();
return DriverManager.getConnection(url,username,password);
}
// 释放资源
public static void release(Connection connection , Statement statement , ResultSet resultSet){
if(connection != null){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
connection = null;
}
if(statement != null){
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
statement = null;
}
if(resultSet != null){
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
resultSet = null;
}
}
public static void release(Connection connection , Statement statement ){
release(connection, statement,null);
}
}
3.2 JDBCFrarmework—update()方法编写
update() : 是用来进行增、删、改的操作,返回值是影响的记录数。
首先我们来看看使用jdbc技术来实现增删改查的方式:
通过上述代码可以得出:
- 定义必要的信息、获取数据库的连接、释放资源都是重复的代码!
- 而我们最终的核心功能仅仅只是执行一条sql语句而已啊!
- 所以我们可以抽取出一个JDBCFrarmework模板类,来封装一个update()方法专门帮我们执行 增删改的sql语句!
- 将之前那些重复的操作,都抽取到模板类中的方法里。就能大大简化我们的使用步骤!
update()方法的代码演示
/**
* 用来执行增、删、改的操作
* @param sql 要执行的SQL语句
* @param params 修改的参数值
* @return
*/
public static int update(String sql , Object...params){
int rows = 0;
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
// 1.连接数据库
connection = JDBCUtils.getConnection();
// 2.创建执行SQL对象
preparedStatement = connection.prepareStatement(sql);
// 3.获取sql中的占位符的个数
int parameterCount = preparedStatement.getParameterMetaData().getParameterCount();
// 4.将可变参循环放入sql语句中
for (int i = 0; i < parameterCount; i++) {
preparedStatement.setObject(i+1,params[i]);
}
// 5.执行SQL语句
rows = preparedStatement.executeUpdate();
}catch (Exception e){
e.printStackTrace();
}finally {
// 6.释放资源
JDBCUtils.release(connection,preparedStatement);
}
// 6.返回影响的记录数
return rows;
}
测试使用
// 添加方法
public static void main(String[] args) {
int rows = JDBCFrarmework.update(
"insert into student (name , age) values(?,?)",
"唐三",
15
);
System.out.println("rows = " + rows);
}
// 修改操作
public static void main(String[] args) {
int rows = JDBCFrarmework.update(
"update student set age = ? where id = ?",
22,
7
);
System.out.println("rows = " + rows);
}
// 删除操作
public static void main(String[] args) {
int rows = JDBCFrarmework.update(
"delete from student where id = ?",
7
);
System.out.println("rows = " + rows);
}
3.3 JDBCFrarmework–querySelector()方法编写
querySelector() : 是用来做查询单个记录,查询多条记录的方法。查询单个记录返回值是一个对象,查询多条记录的返回值是一个List集合。
首先我们先来看看通过JDBC技术来实现的查询,如图:
通过上述代码可以得出:
- 除了SQL语句和封装的数据不一致外其它语句基本一致,都是重复的代码。
- 而我们最终的核心功能仅仅只是执行一条sql语句。
- 所以我们可以抽取出一个JDBCFrarmework模板类,来封装一个querySelector()方法专门帮我们执行 查询 的sql语句!
- 将之前那些重复的操作,都抽取到模板类中的方法里。就能大大简化我们的使用步骤!
除此之外,我们还要考虑两个问题:
- 问题一 : 返回值和封装的结果集的类型如何确定?
- 问题二 : 将不同的查询方式返回的ResultSe结果集封装成List集合或对象?
解决方案:
- 问题一 : 既然我们不知道类型,那么我们可以通过泛型来处理结果集。
- 问题二:我们可以通过定义一个接口ResultSetHandler,提供一个resultHandler方法来解决不同的查询方式来处理不同的ResultSet结果集。该方法返回的就是封装的结果。封装的结果过程通过用户去重写ResultSetHandler中的resultHandler方法来实现。
- 所以querySelector()方法的实现就是通过 泛型 + 接口 的方式实现的。
querySelector()代码演示
1.首先创建ResultSetHandler接口,提供resultHandler()方法
/**
* 封装ResultSet结果集
* @param <T>
*/
public interface ResultSetHandler<T> {
T ResultSetHandler(ResultSet resultSet);
}
2.querySelector()方法的实现
/**
* 是用来做查询单个记录,查询多条记录的方法。
* 查询单个记录返回值是一个对象,
* 查询多条记录的返回值是一个List集合。
* @param sql 执行的查询语句
* @param resultSetHandler 用户来实现对ResultSet封装
* @param params
* @param <T> 返回封装的结果
* @return
*/
public static<T> T querySelector(String sql , ResultSetHandler<T> resultSetHandler , Object...params){
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
T reult = null;
try {
// 1.连接数据库
connection = JDBCUtils.getConnection();
// 2.获取指向SQL对象
preparedStatement = connection.prepareStatement(sql);
// 3.获取SQL语句中占位符的个数
int parameterCount = preparedStatement.getParameterMetaData().getParameterCount();
// 4.循环添加SQl语句占位符的内容
for (int i = 0; i < parameterCount; i++) {
preparedStatement.setObject(i+1,params[i]);
}
// 5.执行查询语句
resultSet = preparedStatement.executeQuery();
// 6.将结果集交给ResultSetHandler接口处理,用户去实现
reult = resultSetHandler.resultSetHandler(resultSet);
}catch (Exception e){
e.printStackTrace();
}finally {
// 7.释放资源
JDBCUtils.release(connection,preparedStatement,resultSet);
}
return reult;
}
测试用例
// 查询一个学生信息
public static void main(String[] args) {
Student student = JDBCFrarmework.querySelector(
"select * from student where id = ?",
new ResultSetHandler<Student>() {
@Override
public Student resultSetHandler(ResultSet resultSet) {
Student student = null;
try {
if (resultSet.next()){
student = new Student(
resultSet.getInt("id"),
resultSet.getString("name"),
resultSet.getInt("age")
);
}
}catch (Exception e){
e.printStackTrace();
}
return student;
}
},
1
);
System.out.println("student = " + student);
}
// 查询多个学生信息
public static void main(String[] args) {
List<Student> list = JDBCFrarmework.querySelector(
"select * from student",
new ResultSetHandler<List<Student>>() {
@Override
public List<Student> resultSetHandler(ResultSet resultSet) {
List<Student> students = null;
try {
students = new ArrayList<>();
while (resultSet.next()){
students.add(new Student(
resultSet.getInt("id"),
resultSet.getString("name"),
resultSet.getInt("age")
));
}
}catch (Exception e){
e.printStackTrace();
}
return students;
}
}
);
System.out.println("list = " + list);
}
3.4 ResultSet结果集的封装
通过上述的测试代码我们可以体会到,对于ResultSet封装还是由用户来完成的,代码服用率还是一般。所以我们还是要对结果集进行封装处理。对于结果集的处理分为两种:
3.4.1 封装结果集为一条记录的方式—ResultSetOneHandler
ResultSetOneHandler实现思路:
- 定义一个处理类,实现ResultSetHandler接口
- 通过构造方法,获取事务描述类的字节码文对象
- 重写接口的抽象方法resultSetHandler
- 通过反射技术,创建事务描述类对象
- 通过反射技术,将属性值注入事务描述类对象中。
代码演示
public class ResultSetOneHandler<T> implements ResultSetHandler<T> {
// 1.获取事务描述类的字节码文件
private Class<T> tClass;
public ResultSetOneHandler(Class<T> tClass) {
this.tClass = tClass;
}
@Override
public T resultSetHandler(ResultSet resultSet) {
T result = null;
try {
// 2.通过反射技术,获取事务描述类的对象
result = tClass.newInstance();
// 封装对象
if (resultSet.next()) {
// 3.获取事务描述类的所有属性对象
Field[] fields = tClass.getDeclaredFields();
for (Field field : fields) {
// 4.获取每一个属性名称
String fieldName = field.getName();
// 5.定义属性描述器的对象
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(fieldName,tClass);
// 6.获取指定set方法
Method writeMethod = propertyDescriptor.getWriteMethod();
// 7.给属性设置值
writeMethod.invoke(result,resultSet.getObject(fieldName));
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 返回结果
return result;
}
}
测试代码
public static void main(String[] args) {
Student student = JDBCFrarmework.querySelector(
"select * from student where id = ?" ,
new ResultSetOneHandler<Student>(Student.class),
1
);
System.out.println("student = " + student);
}
3.4.2 封装结果集为多条记录的方式 — ResultSetListHandler
ResultSetListHandler实现思路 :
- 通过构造方法获取事务描述类的字节码文件。
- 重写resultSetHandler方法
- 创建list集合,将元素添加到list集合中
- 通过反射技术创建事务描述类的对象
- 通过反射技术获取所有属性对象
- 通过属性对象获取相应的set方法
- 为事务描述对象符属性的值
- 将事务描述类对象添加到集合中
public class ResultSetListHandler<T> implements ResultSetHandler<List<T>> {
// 创建事务描述类的字节码文件对象
private Class<T> tClass = null;
public ResultSetListHandler(Class<T> tClass) {
this.tClass = tClass;
}
@Override
public List<T> resultSetHandler(ResultSet resultSet) {
// 1.创建集合
List<T> resultList = new ArrayList<>();
try {
// 2.遍历resultSet集合
while (resultSet.next()) {
// 3.通过反射技术创建事务描述类的对象
T result = tClass.newInstance();
// 4.获取事务描述类所有的属性对象
Field[] fields = tClass.getDeclaredFields();
// 5.分别给属性赋值
for (Field field : fields) {
// 6.获取属性名称
String fieldName = field.getName();
// 7.创建事务描述对象
PropertyDescriptor propertyDescriptor = new PropertyDescriptor(fieldName, tClass);
// 8.获取属性对应的set方法
Method writeMethod = propertyDescriptor.getWriteMethod();
// 9.给属性赋值
writeMethod.invoke(result , resultSet.getObject(fieldName));
}
// 10.向集合中添加对象
resultList.add(result);
}
}catch (Exception e){
e.printStackTrace();
}
// 11.返回集合
return resultList;
}
}
代码测试
public static void main(String[] args) {
List<Student> list = JDBCFrarmework.querySelector(
"select * from student",
new ResultSetListHandler<Student>(Student.class)
);
System.out.println("list = " + list);
}
4.总结
- 软件工程中的框架指的是经过验证的,具有一定功能的,半成品软件 。
- 自定义JDBC框架所需的技术:反射、jdbc、泛型、集合
- 自定义JDBC框架的核心功能:update()是用来增删改操作,querySelector()使用来做查询操作。
- 实现该框架的核心思想:反射+泛型 思想。
- 源码可以点击如下链接下载:JDBCFramrmework源码
。