自定义JDBC框架-JDBCFrarmework

发布于:2022-12-22 ⋅ 阅读:(440) ⋅ 点赞:(0)

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


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实现思路:

  1. 定义一个处理类,实现ResultSetHandler接口
  2. 通过构造方法,获取事务描述类的字节码文对象
  3. 重写接口的抽象方法resultSetHandler
  4. 通过反射技术,创建事务描述类对象
  5. 通过反射技术,将属性值注入事务描述类对象中。

代码演示

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实现思路 :

  1. 通过构造方法获取事务描述类的字节码文件。
  2. 重写resultSetHandler方法
  3. 创建list集合,将元素添加到list集合中
  4. 通过反射技术创建事务描述类的对象
  5. 通过反射技术获取所有属性对象
  6. 通过属性对象获取相应的set方法
  7. 为事务描述对象符属性的值
  8. 将事务描述类对象添加到集合中
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源码


网站公告

今日签到

点亮在社区的每一天
去签到