MyBatis源码探究(二)

本节将介绍 MyBatis 中基础支持层的功能,如图中阴影部分所示, 基础支持层位于 My Batis 整体架构的最底层,支撑着 MyBatis 的核心处理层,是整个框架的基石。基础支持层 中封装了多个较为通用的、独立的模块,不仅仅为 MyBatis 提供基础支撑, 也可以在合适的场 景中直接复用。
在这里插入图片描述

解析器模块

在 MyBatis 中涉及多个 XML 配置文件,因此我们首先介绍 XML 解析的相关内容。 XML解析常见的方式有三种, 分别是: DOM (Document Object Model)解析方式和 SAX ( Simple API for XML)解析方式,以及从 JDK6.0 版本开始, JDK 开始支持的 StAX(Streaming API for XML) 解析方式。

XPath 简介

MyBatis 在初始化过程中处理 mybatis-config.xml 配置文件以及映射文件时,使用的是 DOM 解析方式,井结合使用 XPath 解析 XML 配置文件。DOM 会将整个 XML 文档 加载到内存中并形成树状数据结构,而 XPath 是一种为查询 XML 文档而设计的语言,它可以 与 DOM 解析方式配合使用,实现对 XML 文档的解析。 XPath 之于 XML 就好比 SQL 语言之于 数据库
XPath 使用路径表达式来选取 XML 文档中指定的节点或者节点集合,与常见的 URL 路径 有些类似。
在 JDK 5.0 版本中推出了 javax.xml.path 包, 它是一个引擎和对象模型独立的 XPath 库。 Java 中使用 XPath 编程的代码模式比较固定。

XPathParser

MyBatis 提供的 XPathParser 类封装了前面涉及的 XPath、 Document 和 EntityResolver,如图所示。
在这里插入图片描述
XPathParser 中各个字段的含义和功能如下所示。


private Document document; // Document 对象
private boolean validation; //是否开启验证
private EntityResolver entityResolver; // 用于加载本地 DTD 文件
pr工vate Properties variables; // mybatis-config. xml 中<propteries>标签定义的键位对集合
private XPath xpath; // XPath 对象

默认情况下, 对 XML 文档进行验证时,会根据 XML 文档开始位置指定的网址加载对应的 DTD 文 件或 XSD 文件 。 如果解析 mybatis-config.xml 配置文件,默认联网加载 http://mybatis.org/dtd/mybatis-3-config.dtd 这个 DTD 文档,当网络比较慢时会导致验证过程缓慢。 在实践中往往会提前设置 EntityResolver 接口对象加载本地的 DTD 文件,从而避免联网加载 DTD 文件。 XMLMapperEntityResolver 是 MyBatis 提供的 EntityRe so Iver 接口的实现类,如图 所示。

在这里插入图片描述

  • EntityResolver 接口的核心是 resolveEntity()方法
  • XPathParser. createDocumentO方法中封装了创建Document对象的过程并触发了加载XML文挡的过程
  • XPathParser 中提供了一系列的 eval*()方法用于解析 boolean、 short、 long、 int、 String、 Node 等类型的信信息
  • 通过调用XPath.evaluate()方法查找指定路径的节点或属性,并进行相应的类型装换

反射工具箱

My Batis 在进行参数处理、结果映射等操作时, 会涉及大量的反射操作。 Java 中的反射虽 然功能强大,但是代码编写起来比较复杂且容易出错,为了简化反射操作的相关代码, MyBatis 提供了专门的反射模块,该模块位于 org.apache.ibatis.reflection 包中,它对常见的反射操作做了 进一步封装,提供了更加简洁方便的反射 API。

Reflector&ReflectorFactory

Reflector

  • Reflector 是 MyBatis 中反射模块的基础,每个 Reflector 对象都对应一个类,在 Reflector 中 缓存了反射操作需要使用的类的元信息。
  • Reflector 的构造方法中会解析指定的 Class 对象,并填充指定集合

ReflectorFactory

先简单介绍一下 Type 接口的基础知识, Type 是所有类型的父接口,它有四个子接口和一个实现类
在这里插入图片描述

  • Class 比较常见,它表示的是原始类型。 Class 类的对象表示 NM 中的一个类或接口, 每个 Java 类在 NM 里都表现为一个 Class 对象。在程序中可以通过“类名.class”、“对 象.getC!ass()”或是 Class 对象,所有元素类型相同且维数相同的数组都共享同一个 Class 对象。
  • ParameterizedType 表示的是参数化类型,例如 List<String>、 Map<Integer,String>、 Service<User>这种带有泛型的类型。
    Parameterized Type 接口中常用的方法有三个,分别是:
    • Type getRawType()一一返回参数化类型中的原始类型,例如 List<String>的原始类 型为 List。
    • Type[] getActualTypeArguments()一一获取参数化类型的类型变量或是实际类型列 表,例如 Map<Integer, String>的实际泛型列表 Integer 和 String。需要注意的是, 该列表的元素类型都是 Type,也就是说,可能存在多层嵌套的情况。
    • Type getOwnerType()一一返回是类型所属的类型,例如存在 A类,其中定义了 内部类lnnerA<l>,则 InnerA<l>所属的类型为A<T>,如果是顶层类型则返回 null。 这种关系比较常见的示例是 Map<K,V>接口与 Map.Entry<K,V>接口 , Map<K,V> 接口是 Map.Entry<K,V>接口的所有者。
  • Type Variable 表示的是类型变量,它用来反映在 NM 编译该泛型前的信息。例如 List 中的 T 就是类型变量,它在编译时需被转换为一个具体的类型后才能正常使用。 该接口中常用的方法有三个,分别是:
    • Type[] getBounds()一一获取类型变量的上边界,如果未明确声明上边界则默认为 Object。 例如 class Test<K extends Person>中 K 的上界就是 Person。

    • D getGenericDeclaration()一一获取声明该类型变量的原始类型,例如 class Test<K extends Person>中的原始类型是 Test。

    • String getName()一一获取在源码中定义时的名字,上例中为 K。

    • GenericArrayType 表示的是数组类型且组成元素是 ParameterizedType 或 TypeVariable. 例如 List<String>[]或 T[]。该接口只有 Type getGenericComponentType()一个方法,它 返回数组的组成元素。

    • WildcardType 表示的是通配符泛型,例如? extends Number 和? super Integer。
      WildcardType 接口有两个方法,分别是:

      • Type[] getUpperBounds()一一-返回泛型变量的上界。 o Type[] getLowerBounds()一-返回泛型变量的下界。
      • Type[] getLowerBounds()一-返回泛型变量的下界。

ObjectFactory

My Batis 中有很多模块会使用到 ObjectFactory 接口,该接口提供了多个 create()方法的重载, 通过这些 create()方法可以创建指定类型的对象。
除了使用 MyBatis 提供的 DefaultObjectFactory 实现,我们还可以在 mybatis-config.xml 配置 文件中指定自定义的 ObjectFactory 接口实现类,从而实现功能上的扩展

Property 工具集

反射模块中使用到的三个属性工具类,分别是 PropertyTokenizer 、 PropertyNamer 和 PropertyCopier。
在使用 MyBatis 的过程中,我们经常会碰到一些属性表达式,例如,在查询某用户(User) 的订单(Order)
在这里插入图片描述
在这里插入图片描述

MetaClass

MetaC!ass 通过 Reflector 和 PropertyTokenizer 组合使用, 实现了对复杂的属性表达式的解 析,并实现了获取指定属性描述信息的功能

ObjectWrapper

MetaClass 是 MyBatis 对类级别的元信息的封装和处理,下面来看 MyBatis 对对象级别的元 信息的处理。 ObjectWrapper 接口是对对象的包装,抽象了对象的属性信息,它定义了一系列查 询对象属性信息的方法,以及更新属性的方法。
在这里插入图片描述

类型转换

JDBC 数据类型与 Java 语言中的数据类型井不是完全对应的,所以在 PreparedStatement 为 SQL 语句绑定参数时,需要从 Java 类型转换成 JDBC 类型,而从结果集中获取数据时,则需要 从 JDBC 类型转换成 Java 类型。 MyBatis 使用类型处理器完成上述两种转换
在这里插入图片描述

TypeHandler

My Batis 中所有的类型转换器都继承了 TypeHandler 接口 ,在 TypeHandler 接口中定义了如 下四个方法, 这四个方法分为两类: setParameter()方法负责将数据由 JdbcType 类型转换成 Java 类型: getResult()方法及其重载负责将数据由 Java 类型转换成 JdbcType 类型。

public interface TypeHandler<T> { 
//在通过 PreparedStatement 为 SQL 语句绑定参数时,会将数据由 JdbcType 类型转换成 Java 类型 
void setParameter(PreparedStatement ps,int i, T parameter, JdbcType jdbcType) throws SQLException; 
//从 ResultSet 中获取数据时会调用此方法,会将数据由 Java 类型转换成 JdbcType 类型 
T getResult(ResultSet rs, String columnName) throws SQLException;
T getResult(ResultSet rs, int columnindex) throws SQLException; 
T getResult(CallableStatement cs, int columnindex) throws SQLException; 
}

为方便用户自定义 TypeHandler 实现, MyBatis 提供了 BaseType Handler 这个抽象类,它实 现了 TypeHandler 接口,并继承了 TypeReference 抽象类
在 BaseTypeHandler 中实现了 TypeHandler.setParameter()方法和 TypeHandler.getResult()方法, 具体实现如下所示。需要注意的是,这两个方法对于非空数据的处理都交给了子类实现。
BaseTypeHandler 的实现类比较多,但大多是直接调用 PreparedStatementResultSetCallableStatement 的对应方法,实现比较简单。
一般情况下, TypeHandler 用于完成单个参数以及单个列值的类型转换, 如果存在多列值转 换成一个 Java 对象的需求, 应该优先考虑使用在映射文件中定义合适的映射规则 (< resultMap> 节点)完成映射。

TypeHandlerRegistry

如何 知道何时使用哪个 TypeHandler 接口实现完成转换呢?
在 MyBatis 初始化过程中,会为所有己知的 TypeHandler 创建对象,并实现注册到 TypeHandlerRegistry 中,由 TypeHandlerRegis盯F 负责管理这些 TypeHandler 对象。

  • 注册 TypeHandler 对象
    TypeHandlerRegistry.register()方法实现了注册 TypeHandler 对象的功能, 该注册过程会向上 述四个集合中添加 TypeHandler 对象。 register()方法有多个重载,这些重载之间的调用关系如图所示
    在这里插入图片描述
  • 查找 TypeHandler
    介绍完注册 乃peHandler 对象的功能之后,再来介绍 TypeHandlerRegis町 提供的查找 TypeHandler 对象的功能。 TypeHandlerRegistry.getTypeHandler()方法实现了从上述四个集合中获 取对应 TypeHandler 对象的功能。 TypeHandlerRegistry.getTypeHandler()方法有多个重载, 这些重 载之间的关系如图
    在这里插入图片描述

经过一系列类型转换之后, TypeHandlerRegistry.getTypeHandler()方法的多个重载都会调用 TypeHandlerRegistry.getTypeHandle(Type,JdbcType)这个重载方法, 它会根据指定的 Java 类型和 JdbcType 类型查找相应的 TypeHandler 对象

TypeAliasRegistry

在编写 SQL 语句时,使用别名可以方便理解以及维护,例如表名或列名很长时,我们一般 会为其设计易懂易维护的别名。 MyBatis 将 SQL 语句中别名的概念进行了延伸和扩展, MyBatis 可以为一个类添加一个别名,之后就可以通过别名引用该类。
My Batis 通过 TypeAliasRegis町 类完成别名注册和管理的功能, TypeAliasRegistry 的结构比 较简单,它通过 TYPE_ALIASES 宇段 CMap<Stri吨, Class<?>>类型)管理别名与 Java 类型之间 的对应关系,通过 TypeAliasRegistry.registerAlias()方法完成注册别名

DataSource

MyBatis 提供了两个 javax.sql.DataSource 接口实现,分别是 PooledDataSource 和 UnpooledDataSource 。 Mybatis 使用不同的 DataSourceFactory 接口实现创建不同类型的 DataSource,如图
在这里插入图片描述

PooledDataSource

PooledDataSource 实现了简易数据库连接池的功能,它依赖的组件如图所示,其中需要注意的是, PooledDataSource 创建新数据库连接的功能是依赖其中封装的 UnpooledDataSource 对象实现的。

PooledDataSource 并不会直接管理java. sq I. Connection 对象,而是管理 PooledConnection 对 象。在 PooledConnection 中封装了真正的数据库连接对象(java.sql.Connection)以及其代理对 象,这里的代理对象是通过 JDK 动态代理产生的。 PooledConnection 继承了 lnvocationHandler 接口,该接口在前面介绍 JDK 动态代理时已经详细描述过了,这里不再重复。

PooledDataSource.getConnection()方法首先会调用 PooledDataSource.popConnection()方法获 取 PooledConnection 对象,然后通过 PooledConnection.getProxyConnection()方法获取数据库连 接的代理对象。 popConnection()方法是 PooledDataSource 的核心逻辑之一,其具体逻辑如图
在这里插入图片描述
通过前面对 PooledConnection.invoke()方法的分析我们知道, 当调用连接的代理对象的 close()方法时,并未关闭真正的数据连接,而是调用 PooledDataSource.pushConnection()方法将 PooledConnection 对象归还给连接池,供之后重用 。 PooledDataSource.pushConnection()方法也是 PooledDataSource 的核心逻辑之一,其逻辑如图
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值