自定义持久层框架设计思路分析

504 阅读12分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第28天,点击查看活动详情

我们就要去设计一个自定义持久层框架。在设计自定义持久层框架之前,需要明确一点,我们自定义的这个持久层框架,它的本质还是对原生 JDBC 代码的封装。只不过在封装的过程中,我们需要把原生 JDBC 存在的问题进行规避或者进行解决。

明确这一点之后,接下来我们就可以一起来看一下自定义持久层框架,我们该如何进行设计。其实对于整个的这个设计,我们可以分为使用端和自定义持久层框架本身两部分。

使用端

什么是使用端呢?使用端理解成就是一个项目,在这个项目中,我们将会使用自定义持久层框架来完成与数据库的交互,完成对数据库表的增删改查操作。

既然在这个项目中,你需要去调用自定义持久层框架,需要怎么办呢?我们就需要去引入自定义持久层框架的 jar 包。其实自定义持久层框架,本身也是一个项目,现在这个使用端要去用到自定义持久框架中的类和方法,就要引入它的 jar 包,只有引入了 jar 包,才能调用类中方法。

而我们自定义持久层框架本身,其实也是一个工程。而在这个工程中,它的本质就是对原生 JDBC 代码进行了封装,也就意味着当使用端去调用这个自定义的持久层框架,完成与数据库的交互时,它的底层代码执行的还是调用原生 JDBC。

在上课时实现的原生 JDBC 对数据库操作案例中,一段原生 JDBC 代码能够正常执行,其实有两部分信息它是必不可少的。一部分就是数据库的配置信息,这数据库的配置信息包括包含了数据库的驱动类,连接哪个数据库,用户名是什么,密码是什么,这一部分信息统称为数据库配置信。

另一部就是 SQL 配置信息,却包含了要执行的 SQL 语句是什么?SQL 语句占位符的参数值是什么?对于这一部分信息,对于这一部分信息统称为搜狗配置信息。

一段原生 JDBC 代码,如果想要能够正常执行,对于这些内容几乎都是必不可少的。而对于这个数据库配置信息和 SQL 配置信息应该由谁来提供呢?是不是应该由我们的使用端来提供。

经过我们的分析,我们的使用端其实是要去提供两部分。这两部分配置信息包括了数据库配置信息以及 SQL 配置信息。当然了,SQL 配置信息再细分,它包含了 SQL 语句、参数类型以及返回值类型。

使用端它该如何去提供这两部分配置信息呢?这两部分配置信息不能直接写死在代码中,如果我们是写在了代码中,是不是又存在了这个硬编码问题?而现在我们在封装的过程中,我们现在不能再出现这些问题了。我们应该把这些问题进行规避掉或者进行解决掉。那我们当时的一个解决思路是不是使用配置文件,所以在使用端提供这两部分配置信息时,应该去使用配置文件来提供这两部分配置信息,也就是说我们会来创建两个配置文件。

第一个配置文件取名为 mybatis-config.xml ,一看到这个配置文件的名称,可能会瞬间联想到 MyBatis 框架中核心配置文件,但是在这里,大家需要明白一点,当前创建的这个配置文件,只不过恰好和 MyBatis 框架中的核心配置文件同名而已,没有任何直接的关联。在 mybatis-config.xml 需要去存放数据库的配置信息。

第二个配置文件是mapper.xml,mapper.xml 存放 SL 配置信息。

这个就是使用端要完成的功能。

自定义持久层架

介绍完使用端设计思路和要完成的功能之后,下面我们来介绍自定义持久层框架设计思路和要完成的功能,它本身都要完成功能什么。

对于自定义持久层框架,本质就是对原生 JDBC 代码进行封装。所以它在底层执行的时候还是原生 JDBC 代码,而原生 JDBC 代码想要能够正常执行,它必须要有数据库的配置信息和 SQL 的配置信息。而数据库配置信息和 SQL 配置信息被使用端存放在了 mybatis-config.xml.xml 和 mapper.xml 这两个配置文件中。所以在这个使用端去调用自定义持久层框架时,需要获取到这两配置文件的地址,并且传递进来。

而自定义持有层框架要完成的功能,就是根据传进来的地址对这两个配置文件进行解析,得到这两个配置文件里面的内容,把这两个内容解析出来之后,就可以执行底层 JDBC 代码,实现相关业务功能的操作。这个就是自定义持久层框架的大体设计思路。

大体的思路介绍完成之后,我们接下来就详细分析它的操作。

加载配置文件。

mybatis-config.xml 和 mapper.xml 这两个配置文件是以文件的形式存储在硬盘上。加载配置文件需要完成的功能就是先把这两个配置文件加载成流,以流的形式先让它加载到内存中存放。

那么如何加载这两个配置文件呢?首先要去创建一个 Resources 类,这个类中定义一个getResourceAsStream() 方法,同时,在这个方法中,需要传递一个路径。这个路径其实传递的就是 mybatis-config.xml 和 mapper.xml 这两个配置文件的路径,比如把 mybatis-config.xml 配置文件传递过来了,getResourceAsStream() 这个方法就会根据传递进来这个路径来找到这个配置文件,并把这个配置文件加载成一个字节数流( inputStream)。

加载配置文件要实现的目的就是根据配置文件的路径加载配置文件,并将配置文件加载成字节输入流存储在内存中。这个就是加载配置文件设计思路和要实现的功能。

介绍完这个加载配置文件,这里有个问题需要思考。这个问题就是在 Resources 类中定义了的 getResourceAsStream 这个方法需不需要调用两次。因为在使用创建了两个配置文件,对这两个配置文件都要进行加载的。这个方法需不需要去执行两次,其实执行两次也可行的。

但是可以换一种思路,我们还有另外一种实现思路,就是在 mybatis-config.xml 这个配置文件中存放 mapper.xml 全路径。如果一旦在 mybatis-config.xml 中存了一份 mapper.xml,我们再去传入这个 mybatis-config.xml 路径时,只传入 mybatis-config.xml 这个配置文件即可。这样再去加载这个配置文件时,它不仅仅把数据库的配置信息进行了加载,同时把 mapper.xml 配置文件的全路径也进行了加载。如果已经对 mapper.xml 配置文件全路径进行了加载,以后想获取到 SQL 的配置信息,也是易如反掌。这个也是一种设计实现的思路。

创建 JavaBean

在加载配置文件的过程中,并没有对这两个配置文件进行解析,只不过是把这两个配置文件加载成了流的形式,并存储在内存中。

加载配置文件完成之后,需要完成的功能是要针对面向对象的思想去进行设计和实现。

第一点,把配置文件加载成流的形式存储到内存中了,但是内存中的数据一个难点是操作麻烦。

第二点,要针对面向对象的思想需要完成的功能,把 mybatis-config.xml 和 mapper.xml 这两个配置文件进行解析,把里面的信息封装成两个 Java 实体对象,也就是 JavaBean,这样才方便后期的存取。

这两个 JavaBean 就相当于容器对象,存放的就是对配置文件解析出来的信息。第一个实体对象取名为 Configuration,这个核心配置文件实体类,主要目的是用来存放 mybatis-config.xml 解析出来的信息。

第二个实体对象取名为 MapperStatement,这个是映射配置文件实体类。主要目的是用来存放 mapper.xml 解析出来的信息。

解析配置文件

第一步已经把配置文件进行了加载,把它加载成输入流存储内存中。第二步创建把容器对象,用于存放解析配置的信息。接下来第三步非常关键,解析配置文件。

解析配置文件的方式有很多种,尤其是对于 XML 格式的配置的解析。解析 XML 格式的配置文件可以使用的 dom4j 这种技术对配置文件进解析。

创建一个 SqlSessionFactoryBuilder 类,在这个类中要有一个 build() 方法,这个方法能够执行,需要有一个 inputStream 参数。这个 inputStream 参数在第一步中已经把配置文件加载加载成字节输入流的形式存在内存中。所以在调用 build 方法时,直接把这个inputStream 传递过来即可。

在这个 build 方法中,要完成的功能就两个。第一个,使用 dom4j 解析配置文件,将解析出来的信息封装到容器对象中,而这个容器对象就是在第二步中创建的 Configuration 和 MapperStatement 这两个容器对象。

第二个,创建 SqlSessionFactory 对象。对象这个对象,你可能会有些疑惑,其实它就是一个工厂类。既然它是工厂,它的主要作用就是来生产 sqlSession 的。SqlSession 就是会话对象。

使用自定义持久层框架与数据库进行交互时,增删改查方法都封装在这个 SqlSession中 。

这里有个问题,既然去使用 SqlSession 对象,为什么不直接使用 new 关键,而是先要去创建SqlSessionFactory ,再由 SqlSessionFactory 去生产。其实在这里使用到了一种设计模式,即工厂模式。

对于工厂模式,我们并不陌生,使用工厂模式去生产对象,可以去降低程序间的耦合。同时我们可以针对不同的需求生产出不同状态的一个对象,这个是工厂模式能够带给的一个好处。当然对于这个设计模式,在课程后期会详细的介绍,尽请期待。

对于这个思路分析呢,先简单的能够知道涉及到了一个工厂模式,其实就可以了。

创建 SqlSession 接口

在第三步思路分析过程中,已经分析创建 SqlSessionFactory 类,下面就需要创建 SqlSession 接口。这个创建的思想是基于开闭原则来创建的,同时创建 DefaultSqlSessionFactory 类。

SqlSessionFactory 是一个工厂类,作用是用来生产 SqlSession。所以在这个接口定义 openSession() 方法,主要实现的功能是开启会话,用于生产 SqlSession。

创建 SqlSession 接口及实现类 DefaultSession

这个接口和实现类中,定义对数据库 CRUD 操作的方法。其中包括 selectList()、selectOne()、update()、delete() 等方法。

SqlSession 中已经封装了这些与数据库进行交互的方法。其实在这些方法中就可以直接来写原生 JDBC 代码,完成与数据库的操作。

只不过,可以基于面向对象再进行深入一层的封装。

创建 Executor 接口及实现类 SimpleExecutor

这一步要完成功能创建 Executor 接口以及 SimpleExecutor 实现类,这一步是衍生出来。

其实就是把原生 JDBC 代码封装到 SimpleExecutor 这个实现类中的 query 方法中,在这个方法中执行的就是原生 JDBC 代码。

前面介绍过,一段 JDBC 代码如果想要能够正常执行,它是需要两部分信息,一部分是数据库配置信息,还有一部分是 SQL 的配置信息。这两部分信息封装在这 JavaBean 中。在前三步执行的过程中,已经使用到 dom4j 对这两个配置文件进行了解析,并将这两个配置文件解析出来的信息封装到了这两个 JavaBean 中。所以 query 方法想要能够执行,是必须要获取到这两部分信息,而这两部分信息又封装在 Configuration 和 MapperStatement 这两个 JavaBean中。

所以这个 query 方法是需要参数,其中,需要参数分别是数据库配置信息(Configuration)和 SQL 配置信息(MapperStatement)。除了这两个参数以外,还需要一个参数,Object...params,这个参数就是具体参数值,是使用端去调用自定义持久层框架,完成对数据库操作时传入具体的一个参数值。

这个使用端调用自定义持久层框架传递参数时,我们并不能确定传递的参数个数是一个还是多个,所以在这里采用了可变参。

到目前为止,就把自定义持久层框架的设计思路介绍完,下期将针对当前分析出来的这个设计思路,通过程序具体实现。

小结

  • 使用端:提供两部分信息,这两部分信息分别包括数据库配置信息和 SQL 配置信息,其中 SQL 配置信息包括 SQL 语句、参数类型、返回值类型。以 XML 形式的配置文件来提供这两部分信息。
  • 自定义持久层框架本质就是对原生 JDBC 代码的封装,需要实现功能如下:
    • 加载配置文件。
    • 创建两个 JavaBean,存放配置文件解析的配置信息。
    • 使用 dom4j 解析配置文件。
    • 创建 SqlSessionFactory 接口以及 DefaultSqlSessionFactory 实现类。
    • 创建 SqlSession 接口以及 DefaultSession 实现类。
    • 最后创建 Executor 接口以及 SimpleExecutor 实现类,这一步时衍生出来。