手把手搭建mybatis框架

43 阅读11分钟

1、前置知识

1.1、JDBC

1.1.1、JDBC介绍

JDBC(Java Data Base Connectivity,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。

1.1.2、JDBC核心接口和类

主要是java.sql.*中几个核心的类和接口:

  1. java.sql.DriverManager:用来加载不同数据库厂商的JDBC驱动,并为创建新的数据库连接提供支持;
  2. java.sql.Driver:指定数据库的驱动入口,DriverManager将通过该类作为连接数据的参数;
  3. java.sql.Connection:完成针对某指定数据库的连接功能;
  4. java.sql.Statement:在一个已经建立的连接中,作为SQL语句执行的容器,它有两个子类:
  5. java.sql.CallableStatement :用于执行数据库中已经创建好的存储过程。
  6. java.sql.PreparedStatement:用于执行预编译的SQL语句。
  7. java.sql.ResultSet:用于存储执行特定SQL语句后返回的结果集。

image.png

1.2、JDK动态代理 && CGLib动态代理

1.2.1、动态代理介绍

  • JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理;
  • CGLib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

1.2.2、JDK动态代理步骤

  1. 创建被代理类及接口(JDK代理是接口代理)
  2. 创建Handle类实现 InvocationHandler接口 ,重写invoke方法
  3. 通过Proxy的newProxyInstance()方法获取代理类对象
  4. 通过代理类对象调用被代理类的方法

image.png

1.2.3、CGLib动态代理

  1. cglib(Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。
  2. JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。 如果想代理没有实现接口的类,就可以使用CGLIB实现。
  3. CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。 它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
  4. CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。 不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。

image.png img

1.2.4、两者区别

  • JDK代理只能对实现接口的类生成代理;CGLib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
  • JDK代理使用的是反射机制实现aop的动态代理,CGLib代理使用字节码处理框架ASM,通过修改字节码生成子类。所以jdk动态代理的方式创建代理对象效率较高,执行效率较低,CGLib创建效率较低,执行效率高。
  • JDK动态代理机制是委托机制,具体说动态实现接口类,在动态生成的实现类里面委托hanlder去调用原始实现类方法,CGLib则使用的继承机制,具体说被代理类和代理类是继承关系,所以代理类是可以赋值给被代理类的,如果被代理类有接口,那么代理类也可以赋值给接口。

1.3、反射机制介绍

AVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射就是把java类中的各种成分映射成一个个的Java对象

例如:一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把个个组成部分映射成一个个对象。

1.3.1、Class类介绍

Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中。Class类的实例表示java应用运行时的类(class ans enum)或接口(interface and annotation)(每个java类运行时都在JVM里表现为一个class对象,可通过类名.class、类型.getClass()、Class.forName("类名")等方法获取class对象)。数组同样也被映射为class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。基本类型boolean,byte,char,short,int,long,float,double和关键字void同样表现为 class 对象。

关于Class类的几点说明:

  1. Class类也是类的一种,与class关键字是不一样的。
  2. 手动编写的类被编译后会产生一个Class对象,其表示的是创建的类的类型信息,而且这个Class对象保存在同名.class的文件中(字节码文件)
  3. 每个通过关键字class标识的类,在内存中有且只有一个与之对应的Class对象来描述其类型信息,无论创建多少个实例对象,其依据的都是用一个Class对象。
  4. Class类只存私有构造函数,因此对应Class对象只能有JVM创建和加载
  5. Class类的对象作用是运行时提供或获得某个对象的类型信息,这点对于反射技术很重要(关于反射稍后分析)。

1.3.2、类的加载流程

image.png

1.3.3、反射的使用

在Java中,Class类与java.lang.reflect类库一起对反射技术进行了全力的支持。在反射包中,我们常用的类主要有Constructor类表示的是Class 对象所表示的类的构造方法,利用它可以在运行时动态创建对象、Field表示Class对象所表示的类的成员变量,通过它可以在运行时动态修改成员变量的属性值(包含private)、Method表示Class对象所表示的类的成员方法,通过它可以动态调用对象的方法(包含private),下面将对这几个重要类进行分别说明。

1.3.3.1、Class类对象的获取

在类加载的时候,jvm会创建一个class对象

class对象是可以说是反射中最常用的,获取class对象的方式的主要有三种

  • 根据类名:类名.class
  • 根据对象:对象.getClass()
  • 根据全限定类名:Class.forName(全限定类名)
1.3.3.2、Class类的方法
方法名说明
forName()(1)获取Class对象的一个引用,但引用的类还没有加载(该类的第一个对象没有生成)就加载了这个类。(2)为了产生Class引用,forName()立即就进行了初始化。
Object-getClass()获取Class对象的一个引用,返回表示该对象的实际类型的Class引用。
getName()取全限定的类名(包括包名),即类的完整名字。
getSimpleName()获取类名(不包括包名)
getCanonicalName()获取全限定的类名(包括包名)
isInterface()判断Class对象是否是表示一个接口
getInterfaces()返回Class对象数组,表示Class对象所引用的类所实现的所有接口。
getSupercalss()返回Class对象,表示Class对象所引用的类所继承的直接基类。应用该方法可在运行时发现一个对象完整的继承结构。
newInstance()返回一个Oject对象,是实现“虚拟构造器”的一种途径。使用该方法创建的类,必须带有无参的构造器。
getFields()获得某个类的所有的公共(public)的字段,包括继承自父类的所有公共字段。 类似的还有getMethods和getConstructors。
getDeclaredFields获得某个类的自己声明的字段,即包括public、private和proteced,默认但是不包括父类声明的任何字段。类似的还有getDeclaredMethods和getDeclaredConstructors。

2、Mybatis 框架流程简介

2.1、Mybatis主要部件以及关系

mybatis数据读写阶段的流程

image.png

主要的核心部件解释如下:

  • SqlSession 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能
  • Executor MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护
  • StatementHandler 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
  • ParameterHandler 负责对用户传递的参数转换成JDBC Statement 所需要的参数,
  • ResultSetHandler 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
  • TypeHandler 负责java数据类型和jdbc数据类型之间的映射和转换
  • MappedStatement MappedStatement维护了一条<select|update|delete|insert>节点的封装,
  • SqlSource 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
  • BoundSql 表示动态生成的SQL语句以及相应的参数信息
  • Configuration MyBatis所有的配置信息都维持在Configuration对象之中。

2.2、Mybatis设计流程

image.png

1. 读取配置文件,建立数据库连接

将属性和连接数据库的操作封装在 Configuration 对象中供后面的组件调用。本文将使用 dom4j 来读取 xml 文件,它具有性能优异和非常方便使用的特点。

2. 创建 SqlSession,搭建 Configuration 和 Executor 之间的桥梁

我们经常在使用框架时看到 Session,Session 到底是什么呢?一个 Session 仅拥有一个对应的数据库连接。类似于一个前段请求 Request,它可以直接调用 exec(SQL) 来执行 SQL 语句。从流程图中的箭头可以看出,SqlSession 的成员变量中必须得有 Executor 和 Configuration 去集中做调配,箭头就像是一种关联关系。我们自己的 MySqlSession 将有一个 getMapper 方法,然后使用动态代理生成对象后,就可以做数据库的操作了。

3. 创建 MapperProxy,使用动态代理生成 Mapper 对象

我们只是希望对指定的接口生成一个对象,使得执行它的时候能运行一句 sql 罢了,而接口无法直接调用方法,所以这里使用动态代理生成对象,在执行时还是回到 SqlSession 中调用查询,最终由Executor 做 JDBC 查询。这样设计是为了单一职责,可扩展性更强。

4. 创建 Executor,封装 JDBC 操作数据库

Executor 是一个执行器,负责 SQL 语句的生成和查询缓存(缓存还没完成)的维护,也就是 jdbc 的代码将在这里完成。

2.3、实现自己的 Mybatis

2.3.1、工程文件及目录:

image.png

2.3.2、初始化阶段

初始化阶段主要是将配置文件加载到内存,保存到configuration对象中,是放在SqlsessionFactory.java中实现。SqlsessionFactory.java

image.png

mybatis中的初始化时解析mybatis-config.xml和xxxmapper.xml然后将加载的内容放到configuration中,其中做了很多解析mybatis-config中的属性、以及xxxmapper中resultmap、sql、select、update、delete…等,而本文只是做了解析db.properties和xxxmapper.xml(还是简化内容的xml),到这里初始化就已经结束了,数据已经保存保存到了configuration中

2.3.3、代理、数据读写及结果解析

1、代理:代理阶段就是生成动态代理的mapper接口,使得面向接口编程得到更好的展现,使用动态代理模式生成动态代理接口

(1)使用了一个简单工厂

MapperProxyFactory .java

image.png

(2)MapperProxy是代理对象实现了InvocationHandler接口,最终是调用它的invoke方法,目前只写了查询逻辑

MapperProxy.java

image.png

(三)、数据读写

执行sql从MapperProxy.invoke()所以执行到了sqlsession中,源码中sqlsession其实基本不做执行sql的操作,它是使用executor来执行,源码中excutor中也是比较复杂的,有很多的设计模式(代理、模板…),还有很多缓存的逻辑

image.png

其实代码执行到这里已经完成了整个流程,但是这里executor是将查询的操作、结果的处理、参数处理放到了StatementHandler、ParameterHandler、ResultSetHandler中

(四)、结果解析

DefaultStatementHandler .java

StatementHandler的作用是使用数据库的Statement或PrepareStatement执行操作,启承上启下作用源码中的statementHandler也比这里复杂很多,有处理批量操作、修改、还有调用存储过程的statementHandler

image.png

DefaultParameterHandler .java

ParameterHandler是对预编译的SQL语句进行参数设置,这里就简单做了Integer和String。

image.png

DefaultResultSetHandler .java

ResultSetHandler是对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型,默认返回的是resultType,然后通过反射的技术对该class的类型对象进行赋值(源码也是用反射)

image.png

3、项目地址

个人github:

项目地址:github.com/angellinlin…