在mybatis 怎么报错_mybatis源码详解配置解析包源码阅读:binding包

binding包

binding包是主要用来处理 Java方法与 SQL语句之间绑定关系的包。

例如,在第3 章中,我们在 Java 程序中使用代码13-1 所示语句调用了queryUserBySchoolName这一抽象方法,然后映射文件中代码13-2所示的 SQL语句被触发。

【代码13-1】

dc6b58f5ab8c735aa650ef4ed5e29d67.png

【代码13-2】

e13edf4c55127167078ccfa66183019f.png

正是因为 binding 包维护了映射接口中方法和数据库操作节点之间的关联关系,MyBatis才能在调用某个映射接口中的方法时找到对应的数据库操作节点。

只通过 Java方法找到对应的 SQL语句还是不够的。代码13-1中调用的只是一个抽象方法,并没有实现。因此,MyBatis还将数据库操作接入抽象方法中。

binding包具有以下两个功能。

· 维护映射接口中抽象方法与数据库操作节点之间的关联关系;

· 为映射接口中的抽象方法接入对应的数据库操作。

接下来阅读源码分析 binding包如何实现这两个功能。

数据库操作的接入

为映射接口中的抽象方法接入对应的数据库操作是相对底层的操作,先来分析这一过程如何实现。

说起为抽象方法接入实现方法,最先想到的就是动态代理。在 10.1.3节我们介绍了基于反射的动态代理的原理。binding包也是基于这种机制实现功能的。

图13-1给出了为映射接口中的抽象方法接入对应的数据库操作相关类的类图。

a601e40748989d9c5a5433b5bfd9825e.png

图13-1 数据库操作接入相关类的类图

下面对涉及的类一一进行解析。

数据库操作的方法化

要想将一个数据库操作接入一个抽象方法中,首先要做的就是将数据库操作节点转化为一个方法。MapperMethod对象就表示数据库操作转化后的方法。每个 MapperMethod对象都对应了一个数据库操作节点,调用 MapperMethod实例中的 execute方法就可以触发节点中的 SQL语句。

MapperMethod 类有两个属性,这两个属性分别对应了其两个重要的内部类:MethodSignature类和 SqlCommand类。

MethodSignature 内部类指代一个具体方法的签名。代码13-3 给出了增加注释的MethodSignature 类的属性,通过这些属性可以看出 MethodSignature 内部类的属性详细描述了一个方法的细节。

【代码13-3】

057909d5fe2fe8d232d51a7098b33da2.png
54d2c006afb824cd64ab707e8dcf1a64.png

SqlCommand内部类指代一条SQL语句。代码13-4展示了SqlCommand内部类的属性。

【代码13-4】

9afeaec3b4d8ed26ab78da204600aa0b.png

SqlCommand的构造方法主要就是根据传入的参数完成对 name和 type字段的赋值,而 resolveMappedStatement子方法是一切的关键。因为 resolveMappedStatement子方法查询出一个 MappedStatement对象,在 15.1节我们将会了解 MappedStatement完整对应了一条数据库操作语句。代码13-5展示了 resolveMappedStatement方法。

【代码13-5】

980f734360fe63ce0215bde3ad137a61.png

因此说 MapperMethod类将一个数据库操作语句和一个 Java方法绑定在了一起:它的MethodSignature属性保存了这个方法的详细信息;

它的 SqlCommand属性持有这个方法对应的 SQL语句。图13-2展示了 MapperMethod类的功能。

9d91e34a6b2fdbe6ffedfc0545aaf1f0.png

图13-2 MapperMethod类功能示意图

因而只要调用 MapperMethod对象的 execute方法,就可以触发具体的数据库操作,于是数据库操作就被转化为了方法。代码13-6给出了MapperMethod类的 execute方法的源码。可以看出 execute方法根据自身 SQL语句类型的不同触发不同的数据库操作。

【代码13-6】

621a6eb8449cfdcbb5be30efca2befd7.png
55873b72f255ecee9e8e3b046cc00edf.png

在 MapperMethod类的帮助下,只要我们能将 Java映射接口的调用转为对 MapperMethod对象 execute方法的调用,就能在调用某个 Java映射接口时完成指定的数据库操作。

MapperMethod类中还有一个内部类 ParamMap,其源码如代码13-7所示。ParamMap内部类用来存储参数,是 HashMap的子类,但是比 HashMap更为严格:如果试图获取其不存在的键值,它会直接抛出异常。这是因为当我们在数据库操作中引用了一个不存在的输入参数时,这样的错误是无法消解的。

【代码13-7】

d164c8bcffb197d285532aebbcadb3ff.png
dcd89d88f4fe75a1eb5192d61ce3c6b8.png

数据库操作方法的接入

在上一节我们已经把一个数据库操作转化为了一个方法(这里指 MapperMethod 对象的 execute方法),可这个方法怎么才能被调用呢?

当调用映射接口中的方法,如“List<User> queryUserBySchoolName(User user)”时,Java 会去该接口的实现类中寻找并执行该方法。而我们的映射接口是没有实现类的,那么调用映射接口中的方法应该会报错才对,又怎么会转而调用 MapperMethod类中的 execute方法呢?

上述工作需要 MapperProxy类的帮助,它基于动态代理将针对映射接口的方法调用转接成了对 MapperMethod对象 execute方法的调用,进而实现了数据库操作。

MapperProxy 继承了 InvocationHandler 接口,是一个动态代理类。这意味着当使用它的实例替代被代理对象后,对被代理对象的方法调用会被转接到 MapperProxy中 invoke方法上。代码13-8展示了 MapperProxy中 invoke方法的源码。

【代码13-8】

b1c5d9e75e20e311bb0f355d56a76398.png

而 MapperProxyFactory则是 MapperProxy的生产工厂,newInstance核心方法会生成一个 MapperProxy对象。

至此,我们知道,只要用对应的 MapperProxy对象作为映射接口的实现,便可以完整地实现为映射接口接入数据库操作的功能。

抽象方法与数据库操作节点的关联

在 13.1节中我们已经通过阅读源码了解了如何将数据库操作转化为一个方法,并将这个方法接入一个映射接口的抽象方法中。

但是,一方面,映射接口文件(UserMapper.class等存有接口的文件)那么多,其中的抽象方法又很多;另一方面,映射文件(UserMapper.xml等存有 SQL操作语句的文件)那么多,映射文件中的数据库操作节点又很多,那么这一切的对应关系怎么维护呢?也就是说,一个映射接口中的抽象方法如何确定自身要接入的 MapperMethod对象是哪一个?

MyBatis分两步解决了这一问题。

第一步,MyBatis 将映射接口与 MapperProxyFactory 关联起来。

这种关联关系是在MapperRegistry类的 knownMappers属性中维护的,如代码13-9所示。

【代码13-9】

91b4c5a2b1aa9faffda85c520af0ba71.png

knownMappers 是一个 HashMap,其键为映射接口,值为对应的 MapperProxyFactory对象。

MapperProxyFactory 的构造方法如代码13-10 所示,只有一个参数便是映射接口。而MapperProxyFactory 的其他属性也不允许修改,因此它生产出的 MapperProxy 对象是唯一的。所以,只要 MapperProxyFactory 对象确定了,MapperProxy 对象也便确定了。于是,MapperRegistry中的 knownMappers属性间接地将映射接口和 MapperProxy对象关联起来。

【代码13-10】

129f0f9a69206e6c776ebbbccdb14206.png

正因为 MapperRegistry中存储了映射接口和 MapperProxy的对应关系,它的 getMapper方法便可以直接为映射接口找出对应的代理对象。该方法的源码如代码13-11所示。

【代码13-11】

3c2be92b86a09bca36ea62801fcf18bf.png
d0b8230b433eb0b9ea285a9ece40769d.png

MapperProxy 对应的是映射文件。通过 MapperRegistry,映射接口和映射文件的对应关系便建立起来。

第二步,此时的范围已经缩小到一个映射接口或者说是 MapperProxy 对象内。由MapperProxy 中的 methodCache 属性维护接口方法和 MapperMethod 对象的对应关系。methodCache属性及注释如代码13-12所示。

【代码13-12】

0505ed35c9cd898aae8748e11c83a6bf.png

这样一来,任意一个映射接口中的抽象方法都和一个 MapperProxy对象关联的MapperMethod对象相对应,这种对应关系如图13-3所示。

2c3d44f3a3bf5f63294a71cf60282549.png

图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】

3a426a293f7755a0e81cfcade0fe9575.png

以代码13-14所示的映射文件为例,mappedStatements存储的数据如图13-4所示。

【代码13-14】

4b8ae56d379760a1c11cf39428dc917a.png
0bffce44255f4cc3a2425bd88583bd57.png

图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的代码对比。

b93b7efbf56b0ed087445aa7d0e43dae.png

图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】

938b9a4085a373855c9a66df02464a37.png
85c21792393c935a56a468b1ffbb1f40.png

通过代码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包

  1. 下篇文章给大家讲解的是通用源码阅读指导mybatis源码详解配置解析包源码阅读:builder包;
  2. 觉得文章不错的朋友可以转发此文关注小编;
  3. 感谢大家的支持!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值