程序员高级码农II 发表于 2021-1-7 07:33:00

mybatis源码详解设置解析包源码阅读:binding包

binding包

binding包是重要用来处理 Java方法与 SQL语句之间绑定关系的包。
例如,在第3 章中,我们在 Java 步调中使用代码13-1 所示语句调用了queryUserBySchoolName这一抽象方法,然后映射文件中代码13-2所示的 SQL语句被触发。
【代码13-1】
https://p1.pstatp.com/large/pgc-image/69daf688835a4854932f1140624dc76a
【代码13-2】
https://p1.pstatp.com/large/pgc-image/b6b10f4278ff47c188d47b4e0a6d8d03
正是由于 binding 包维护了映射接口中方法和数据库操作节点之间的关联关系,MyBatis才气在调用某个映射接口中的方法时找到对应的数据库操作节点。
只通过 Java方法找到对应的 SQL语句还是不够的。代码13-1中调用的只是一个抽象方法,并没有实现。因此,MyBatis还将数据库操作接入抽象方法中。
binding包具有以下两个功能。
· 维护映射接口中抽象方法与数据库操作节点之间的关联关系;
· 为映射接口中的抽象方法接入对应的数据库操作。
接下来阅读源码分析 binding包如何实现这两个功能。
数据库操作的接入

为映射接口中的抽象方法接入对应的数据库操作是相对底层的操作,先来分析这一过程如何实现。
说起为抽象方法接入实现方法,最先想到的就是动态署理。在 10.1.3节我们先容了基于反射的动态署理的原理。binding包也是基于这种机制实现功能的。
图13-1给出了为映射接口中的抽象方法接入对应的数据库操作相干类的类图。
https://p3.pstatp.com/large/pgc-image/9452cf7160de4e04bd72e36c47d9fff2图13-1 数据库操作接入相干类的类图
下面临涉及的类一一进行解析。
数据库操作的方法化
要想将一个数据库操作接入一个抽象方法中,首先要做的就是将数据库操作节点转化为一个方法。MapperMethod对象就表现数据库操作转化后的方法。每个 MapperMethod对象都对应了一个数据库操作节点,调用 MapperMethod实例中的 execute方法就可以触发节点中的 SQL语句。
MapperMethod 类有两个属性,这两个属性分别对应了其两个重要的内部类:MethodSignature类和 SqlCommand类。
MethodSignature 内部类指代一个具体方法的署名。代码13-3 给出了增加注释的MethodSignature 类的属性,通过这些属性可以看出 MethodSignature 内部类的属性详细形貌了一个方法的细节。
【代码13-3】
https://p9.pstatp.com/large/pgc-image/349ada5c101d46dfa56875c4ab0bf6e0
https://p3.pstatp.com/large/pgc-image/7f8f4392657e4a14bd251acd46266d4d
SqlCommand内部类指代一条SQL语句。代码13-4展示了SqlCommand内部类的属性。
【代码13-4】
https://p1.pstatp.com/large/pgc-image/f35866180f4543d7ac2e7530d6eb8f63
SqlCommand的构造方法重要就是根据传入的参数完成对 name和 type字段的赋值,而 resolveMappedStatement子方法是一切的关键。由于 resolveMappedStatement子方法查询出一个 MappedStatement对象,在 15.1节我们将会了解 MappedStatement完备对应了一条数据库操作语句。代码13-5展示了 resolveMappedStatement方法。
【代码13-5】
https://p1.pstatp.com/large/pgc-image/333918466cc9412b93a6b097d7cc6ed8
因此说 MapperMethod类将一个数据库操作语句和一个 Java方法绑定在了一起:它的MethodSignature属性保存了这个方法的详细信息;
它的 SqlCommand属性持有这个方法对应的 SQL语句。图13-2展示了 MapperMethod类的功能。
https://p1.pstatp.com/large/pgc-image/7471859c21494f448d6035f09694daa0图13-2 MapperMethod类功能表示图
因而只要调用 MapperMethod对象的 execute方法,就可以触发具体的数据库操作,于是数据库操作就被转化为了方法。代码13-6给出了MapperMethod类的 execute方法的源码。可以看出 execute方法根据自身 SQL语句类型的不同触发不同的数据库操作。
【代码13-6】
https://p1.pstatp.com/large/pgc-image/52b356703f5340558bbee5ce894441bf
https://p9.pstatp.com/large/pgc-image/c7192d2d441544ea9655441e746295d7
在 MapperMethod类的资助下,只要我们能将 Java映射接口的调用转为对 MapperMethod对象 execute方法的调用,就能在调用某个 Java映射接口时完成指定的数据库操作。
MapperMethod类中还有一个内部类 ParamMap,其源码如代码13-7所示。ParamMap内部类用来存储参数,是 HashMap的子类,但是比 HashMap更为严格:假如试图获取其不存在的键值,它会直接抛出异常。这是由于当我们在数据库操作中引用了一个不存在的输入参数时,如许的错误是无法消解的。
【代码13-7】
https://p1.pstatp.com/large/pgc-image/f7488a740cd04afe965dbdc4458553e4
https://p9.pstatp.com/large/pgc-image/3747da48362b471295f85f8a47d4faac
数据库操作方法的接入
在上一节我们已经把一个数据库操作转化为了一个方法(这里指 MapperMethod 对象的 execute方法),可这个方法怎么才气被调用呢?
当调用映射接口中的方法,如“List<User> queryUserBySchoolName(User user)”时,Java 会去该接口的实现类中寻找并执行该方法。而我们的映射接口是没有实现类的,那么调用映射接口中的方法应该会报错才对,又怎么会转而调用 MapperMethod类中的 execute方法呢?
上述工作需要 MapperProxy类的资助,它基于动态署理将针对映射接口的方法调用转接成了对 MapperMethod对象 execute方法的调用,进而实现了数据库操作。
MapperProxy 继续了 InvocationHandler 接口,是一个动态署理类。这意味着当使用它的实例替代被署理对象后,对被署理对象的方法调用会被转接到 MapperProxy中 invoke方法上。代码13-8展示了 MapperProxy中 invoke方法的源码。
【代码13-8】
https://p1.pstatp.com/large/pgc-image/c35263eb26db489e961a39ec786b509d
而 MapperProxyFactory则是 MapperProxy的生产工厂,newInstance焦点方法会生成一个 MapperProxy对象。
至此,我们知道,只要用对应的 MapperProxy对象作为映射接口的实现,便可以完备地实现为映射接口接入数据库操作的功能。
抽象方法与数据库操作节点的关联

在 13.1节中我们已经通过阅读源码了解了如何将数据库操作转化为一个方法,并将这个方法接入一个映射接口的抽象方法中。
但是,一方面,映射接口文件(UserMapper.class等存有接口的文件)那么多,其中的抽象方法又许多;另一方面,映射文件(UserMapper.xml等存有 SQL操作语句的文件)那么多,映射文件中的数据库操作节点又许多,那么这一切的对应关系怎么维护呢?也就是说,一个映射接口中的抽象方法如何确定自身要接入的 MapperMethod对象是哪一个?
MyBatis分两步解决了这一问题。
第一步,MyBatis 将映射接口与 MapperProxyFactory 关联起来。
这种关联关系是在MapperRegistry类的 knownMappers属性中维护的,如代码13-9所示。
【代码13-9】
https://p3.pstatp.com/large/pgc-image/6d999cd7c6cc4e09b814115eae8157ae
knownMappers 是一个 HashMap,其键为映射接口,值为对应的 MapperProxyFactory对象。
MapperProxyFactory 的构造方法如代码13-10 所示,只有一个参数便是映射接口。而MapperProxyFactory 的其他属性也不允许修改,因此它生产出的 MapperProxy 对象是唯一的。所以,只要 MapperProxyFactory 对象确定了,MapperProxy 对象也便确定了。于是,MapperRegistry中的 knownMappers属性间接地将映射接口和 MapperProxy对象关联起来。
【代码13-10】
https://p1.pstatp.com/large/pgc-image/477df0d0a21c41b4a5e0f76bee384ed6
正由于 MapperRegistry中存储了映射接口和 MapperProxy的对应关系,它的 getMapper方法便可以直接为映射接口找出对应的署理对象。该方法的源码如代码13-11所示。
【代码13-11】
https://p1.pstatp.com/large/pgc-image/1e6a0e3595074dd7b2cf8151b1c29d63
https://p3.pstatp.com/large/pgc-image/608e18b43273418db886060f4e68218b
MapperProxy 对应的是映射文件。通过 MapperRegistry,映射接口和映射文件的对应关系便建立起来。
第二步,此时的范围已经缩小到一个映射接口大概说是 MapperProxy 对象内。由MapperProxy 中的 methodCache 属性维护接口方法和 MapperMethod 对象的对应关系。methodCache属性及注释如代码13-12所示。
【代码13-12】
https://p1.pstatp.com/large/pgc-image/5ac35476b1064834abd93c9d34c9d065
如许一来,任意一个映射接口中的抽象方法都和一个 MapperProxy对象关联的MapperMethod对象相对应,这种对应关系如图13-3所示。
https://p3.pstatp.com/large/pgc-image/ee572808a5a74646b86ea1bdc462171a图13-3 抽象方法与数据库操作节点的对应关系
通过图13-3也可以看出,MapperProxy类就是映射接口的一个署理类。署理关系建立完成后,只要调用映射接口中的方法,都会被对应的MapperProxy 截获,而 MapperProxy会创建或选取合适的 MapperMethod对象,并触发其 execute方法。于是,针对映射接口中抽象方法的调用就转变为了具体的数据库操作。
数据库操作接入总结

在 2.3节我们提到将映射接口调用转化为对应的 SQL语句的执行是MyBatis完成的焦点功能之一。为了让大家对这个功能的实现有一个概括性的了解,我们将对这一功能进行一次总结。
初始化阶段
MyBatis 在初始化阶段会进行各个映射文件的解析,然后将各个数据库操作节点的信息记录到 Configuration对象的 mappedStatements属性中。代码13-13 展示了 Configuration 对象的 mappedStatements 属性,其布局是一个StrictMap(一个不允许覆盖键值的 HashMap),该StrictMap的键为 SQL语句的“namespace值.语句 id 值”(假如语句id 值没有歧义的话,还会单独再以语句 id 值为键放入一份数据),值为数据库操作节点的详细信息。
【代码13-13】
https://p9.pstatp.com/large/pgc-image/c5018fd3a5214ffeac9fc40d35f8e920
以代码13-14所示的映射文件为例,mappedStatements存储的数据如图13-4所示。
【代码13-14】
https://p1.pstatp.com/large/pgc-image/676b781aed2b4090b6ba32af97044bc4
https://p1.pstatp.com/large/pgc-image/499153eee7ce4865bd918e234c447a94图13-4 mappedStatements存储的数据
MyBatis 还会在初始化阶段扫描全部的映射接口,并根据映射接口创建与之关联的MapperProxyFactory,两者的关联关系由 MapperRegistry 维护。当调用 MapperRegistry 的getMapper方法(SqlSession的getMapper方法最终也会调用到这里)时,MapperProxyFactory会生产出一个 MapperProxy对象作为映射接口的署理。
数据读写阶段
当映射接口中有方法被调用时,会被署理对象 MapperProxy 劫持,转而触发了MapperProxy对象中的 invoke方法。MapperProxy对象中的 invoke方法会创建或取出该映射接口方法对应的 MapperMethod对象,这部门操作已经在代码13-8中先容过了。
在创建 MapperMethod对象的过程中,MapperMethod中 SqlCommand子类的构造方法会去 Configuration对象的 mappedStatements属性中根据当前映射接口名、方法名索引前期已经存好的 SQL语句信息。这部门操作已经在代码13-5中先容过了。然后,MapperMethod对象的 execute方法被触发,在 execute方法内会根据不同的 SQL语句类型进行不同的数据库操作。这部门操作已经在代码13-6中先容过了。
如许,一个针对映射接口中的方法调用,终于被转化为了对应的数据库操作。
MyBatis与Spring、Spring Boot的整合

通常 MyBatis会和 Spring、Spring Boot等框架配合使用,此时,MyBatis的使用更为简单。图13-5给出了单独使用 MyBatis和基于 Spring使用 MyBatis的代码对比。
https://p3.pstatp.com/large/pgc-image/f7fbc35b048742b58f51e5d6e15afe5c图13-5 单独使用MyBatis和基于Spring使用MyBatis的代码对比
在 Spring或 Spring Boot中,MyBatis不需要调用 getMapper方法获取映射接口的具体实现类,甚至连设置文件都可以省略。可这是怎么做到的呢?
这些问题的答案不属于 MyBatis源码的范围,但是简要了解它们能资助我们更好地了解 MyBatis的工作原理。
MyBatis与 Spring的整合功能由 mybatis-spring项目提供,该项目是由 MyBatis团队开发的用于将 MyBatis接入 Spring的工具。基于它,能够简化 MyBatis在 Spring中的应用。以 Spring为例,我们可以在 Spring的设置文件 applicationContext.xml 中设置如代码13-15所示的片段,它指明了 MyBatis映射接口文件所在的包。
【代码13-15】
https://p1.pstatp.com/large/pgc-image/fab5d7915a0c4675b5901e9432bc7a6f
https://p1.pstatp.com/large/pgc-image/3f9866e0d5ee46318dbd7259ed992d78
通过代码13-15所示的设置后,Spring在启动阶段会使用 MapperScannerConfigurer类对指定包进行扫描。对于扫描到的映射接口,mybatis-spring 会将其看成MapperFactoryBean对象注册到 Spring的 Bean列表中。而 MapperFactoryBean可以给出映射接口的署理类。
如许,我们可以在代码中直接使用@Autowired 注解来注入映射接口。然后在调用该接口时,MapperFactoryBean给出的署理类会将操作转接给 MyBatis。
Spring Boot项目诞生的目的是简化 Spring项目中的设置工作。在Spring Boot中使用MyBatis更为简单,2.2节给出的就是 MyBatis和 Spring Boot整合的示例。两者整合重要也是靠 mybatis-spring 项目的支持。但在此根本上,增加了负责完成主动设置工作的mybatis-spring-boot-autoconfigure 项目,并将相干项目一同合并封装到了 mybatis-spring-boot-starter项目中。于是只需引用 mybatis-spring-boot-starter项目,即可将 MyBatis整合到Spring Boot中。
读者可以在了解了 Spring框架的机制后详细阅读 mybatis-spring的源码。
本文给大家讲解的内容是通用源码阅读指导mybatis源码详解设置解析包源码阅读:binding包


[*]下篇文章给大家讲解的是通用源码阅读指导mybatis源码详解设置解析包源码阅读:builder包;
[*]觉得文章不错的朋友可以转发此文关注小编;
[*]感谢大家的支持!
页: [1]
查看完整版本: mybatis源码详解设置解析包源码阅读:binding包