4、源生JDBC问题分析与自定义持久层框架

发布于:2023-01-16 ⋅ 阅读:(384) ⋅ 点赞:(0)

完整讲义、源码与答疑,欢迎关注我的微信公众号“守护之王觉行”,然后添加管理员微信获取,谢谢~❀❀

一、框架概念❀

1.什么是框架

框架的实质就是一项加载。框架的效果就是让项目具有了一定功能。就好像我们入手了一台摄像机去拍摄一部纪录片一样。摄像机就是框架,纪录片就是项目,具体怎么拍摄,还需要我们去操作和配置。放到编程项目里,框架就是引入的工具代码。

2.解决的问题

​ 框架要解决的是技术整合问题。现在软件开发环境和软件规模非常大,我们不可能所有系统代码都从0开始敲。况且优秀的框架已经把基础技术都整合完了,我们只需要在它的基础上进一步开发即可。

3.怎么用框架

Java框架共性:

  1. 现成jar包,引入工程

  2. 对框架运行细节进行定制,一般通过xml配置文件(有格式规范)

  3. 在java程序中调用框架API(接口)

二、软件分层及常用框架❀

1.什么是分层

分层就是把代码按照功能不同进行分类,放入不同文件或文件夹中,常见有如下分层类型:

  1. 类型1:jsp+javabean(表现层+数据对象层,由表现层处理请求)

  2. 类型2:jsp+serlvet+javabean(表现层+请求处理曾+数据对象曾,由请求处理曾处理请求)

  3. 类型三,经典三层:表现层(jsp/html+controller层)+ 业务层(service层)+ 持久层(dao数据库交互层)

  4. 举例,MVC模式是对分层概念的一种实现,其中:

    M(Model):业务模型+数据模型(service+dao)

    V(View): 视图层,主要完成页面展示相关工作(jsp/html)

    C(Controller): 控制层,一般处于请求层与服务层之间,只负责处理程序逻辑,完成数据的收发,不负责具体的业务实现。

2.分层的好处

代码清晰、解耦、便于维护,出现问题容易定位。

3.常见的框架

dao持久层:hibernate、mybatis

service业务层:spring框架(对象容器)

web表现层:springmvc框架

三、自定义持久层框架❀❀❀

1.原生JDBC操作数据库问题分析

JDBC(Java DataBase Connectivity) :Java数据库连接技术:具体讲就是通过Java连接广泛的数据库,并对表中数据执行增、删、改、查等操作的技术。不同的数据口需要不同的JDBC接口去连接数据库。以下是它的相关接口:

接口 作用
Driver 驱动接口,定义建立链接的方式
DriverManager 工具类,用于管理驱动,可以获取数据库的链接
Connection 表示Java与数据库建立的连接对象(接口)
PreparedStatement 发送SQL语句的工具
ResultSet 结果集,用于获取查询语句的结果
package com.mybatis.dao;

import com.mybatis.pojo.User;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;

public class UserDaoImpl implements UserDao {

  /**
  *问题一:数据库配置信息存在硬编码,后期想要更换需要改动代码,不方便
  *解决:使用配置文件
  **/
   private String driver = "com.mysql.jdbc.Driver";
   private String url = "jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8";
   private String username = "root";
   private String password = "root";


   public List<User> queryUserList() throws Exception {
      List<User> userList = new ArrayList<User>();

      Class.forName(driver);
      /**
       * 问题二:频繁获取/释放数据库连接,每次都要JDBC完成三段式握手,影响数据库和应用性能
       * 解决:数据库连接池技术,C3P0,DRUID(阿里巴巴荣誉出品,号称前无古人后无来者世界最强没有之一)Druid德鲁伊特(古代凯尔特人的祭司)
       */
      Connection connection = DriverManager.getConnection(url, username, password);
      /**
       * 问题三:sql语句硬编码,如果想要更换语句,后期难以维护
       * 解决:若sql语句和java代码分离,比如sql写在配置文件中。
       */
      String sql1 = "select * from user";
      /**
       * 问题四:sql语句where条件和占位符?一一对应,后期难以维护
       * 解决:若sql语句和java代码分离,比如sql写在配置文件中。
       */
      String sql2 = "select * from user where username=? and id=?";
      //预处理sql语句
      PreparedStatement preparedStatement1 = connection.prepareStatement(sql1);
     PreparedStatement preparedStatement2 = connection.prepareStatement(sql2);
     preparedStatement2.setString(1, "tom");
     preparedStatement2.setString(2, 2);
      //执行查询
      ResultSet resultSet = preparedStatement.executeQuery();
      User user = null;
      /**
       * 问题五:结果集解析麻烦,查询列硬编码,后期维护麻烦
       * 期望:如果单条记录直接返回实体对象,如果多条记录返回实体的集合。可以利用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射。
       */
       //遍历结果集,封装结果实体
      while(resultSet.next()) {
         user = new User();
         user.setId(resultSet.getInt("id"));
         user.setUsername(resultSet.getString("username"));
         user.setSex(resultSet.getString("sex"));
         user.setBirthday(resultSet.getDate("birthday"));
         user.setAddress(resultSet.getString("address"));
         userList.add(user);
      }
      //释放资源,返回结果集
      resultSet.close();
      preparedStatement.close();
      connection.close();
      return userList;
   }
}

2.问题汇总与解决思路

1、问题一:数据库配置文件存在硬编码问题

解决:使用配置文件。

2、问题二:频繁获取/释放数据库连接,影响数据库和应用性能

解决:数据库连接池技术,C3P0,DRUID(阿里巴巴荣誉出品,号称前无古人后无来者世界最强没有之一)Druid,德鲁伊特(古代凯尔特人的祭司)

3、问题三:sql语句硬编码,后期难以维护

解决:sql语句和java代码分离,比如sql写在配置文件中。

4、问题四:sql语句where条件和占位符?一一对应,后期难以维护

解决:sql语句和java代码分离,比如sql写在配置文件中。

5、问题五:结果集解析麻烦,查询列硬编码,后期维护麻烦

解决:如果单条记录直接返回实体对象,如果多条记录返回实体的集合。可以利用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射。

3.自定义持久层框架设计

在这里插入图片描述

3.1使⽤端

提供核⼼配置⽂件:

sqlMapConfig.xml : 存放数据源信息,引⼊mapper.xml

Mapper.xml : sql语句的配置⽂件信息

注意:因为数据源信息一般很少改动,查询语句一般经常改动,所以应当将两种配置信息写到两个配置文件中,方便解耦,防止管理混乱。

3.2框架端

3.2.1读取配置⽂件

读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可 以创建javaBean来存储

(1)Configuration : 存放数据库基本信息、Map<唯⼀标识,Mapper> 唯⼀标识:namespace + “.” + id

(2)MappedStatement:存放sql语句、statement类型、输⼊参数java类型、输出参数java类型

3.2.2解析配置⽂件

创建sqlSessionFactoryBuilder类:

⽅法:sqlSessionFactory build():

第⼀:使⽤dom4j解析配置⽂件,将解析出来的内容封装到Configuration和MappedStatement中

第⼆:创建SqlSessionFactory的实现类DefaultSqlSession

3.2.3创建SqlSessionFactory:

⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象

3.3.4创建sqlSession接⼝及实现类:

作用:封装crud(增删改查)⽅法

⽅法:selectList(String statementId,Object param):查询所有

selectOne(String statementId,Object param):查询单个

具体实现:封装JDBC完成对数据库表的查询操作

涉及到的设计模式: Builder构建者设计模式、⼯⼚模式、代理模式

关于Java的反射机制、工厂模式、代理模式,推荐阅读以下文章:

Java–反射机制原理、几种Class获取方式及应用场景_吾日三省贾斯汀的博客-CSDN博客

4 自定义持久层框架优化

通过上述我们的⾃定义框架,我们解决了JDBC操作数据库带来的⼀些问题:例如频繁创建释放数据库连 接,硬编码,⼿动封装返回结果集等问题。

但是现在我们继续来分析刚刚完成的⾃定义框架代码,有没有什么问题?

问题如下:

  • dao的实现类中存在重复的代码,整个操作的过程模板重复(创建sqlsession,调⽤sqlsession⽅ 法,关闭 sqlsession)
  • dao的实现类中存在硬编码,调⽤sqlsession的⽅法时,参数statement的id硬编码

解决方案:

使用代理模式来创建DAO层接口的代理对象,代码如下。

sqlsession关键实现代码如下(完整项目代码获取方式已写在文章顶部):

package com.motalJava.sqlSession;

import com.motalJava.pojo.Configuration;
import com.motalJava.pojo.MappedStatement;

import java.lang.reflect.*;
import java.util.List;

/**
 * @author Hissen
 * @date 2022/8/4 16:51
 * @description
 */
public class DefaultSqlsession implements SqlSession {

    private Configuration configuration;

    public DefaultSqlsession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementid, Object... params) throws Exception {

        //完成对simpleExecutor里的query方法的调用
        simpleExecutor simpleExecutor = new simpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementid);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement,params);

        return (List<E>) list;
    }

    @Override
    public <T> T selectOne(String statementid, Object... params) throws Exception {
        List<Object> object = selectList(statementid, params);
        if(object.size() == 1){
            return (T) object.get(0);
        }else {
            throw new RuntimeException("查询结果为空或返回过多");
        }
    }

    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        //使用JDK动态代理为DAO接口生成代理对象,并返回

        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlsession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /*proxy:当前代理对象的应用;method:当前被调用方法的引用;args:传递的参数*/
                /*底层依然是去执行JDBC代码,但是我们可以先根据传参不同,选择调用selectList或者selectOne,再让该方法去执行simpleExecutor.query完成底层调用*/
                //为selectList或者selectOne准备参数,1:statementid,sql语句的唯一标识,由namespace.id组成
                //由于invoke方法无法获得statementid值,所以我们需要将statementid的命名进行改动,让namespace.id = 接口全限定名.方法名,借助method获得想要的参数

                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId  = className + "." + methodName;

                //准备参数2: params = args
                //获取被调用方法的返回值类型
                Type genericReturnType = method.getGenericReturnType();
                //判断是否进行了 泛型类型参数化,如果是,则调用list,否则是一个实体,调用one
                if(genericReturnType instanceof ParameterizedType){
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                }

                return selectOne(statementId,args);

            }
        });

        return (T) proxyInstance;
    }
}

查询全部运行结果:

在这里插入图片描述

完整讲义、源码与答疑,欢迎关注我的微信公众号“守护之王觉行”,添加管理员微信获取,谢谢~❀❀

本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

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