MyBatis(2)的体系结构与核心工作原理分析

Published on with 0 views and 0 comments

MyBatis 的整体框架分为三层,分别是接口层、核心处理层、和基础支持层。如下图
image20220411112649o54dwop.png

MyBatis 的核心工作流程图如下:
image20220411112754t8gk4z8.png

一、基础支持层

基础支持层位于 MyBatis 整体架构的最底层,支撑着 MyBatis 的核心处理层,是整个框架的基石。基
础支持层中封装了多个较为通用的、独立的模块。不仅仅为 MyBatis 提供基础支撑,也可以在合适的场
景中直接复用。

1.反射模块

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

Reflector 类

// 对应的Class 类型
private final Class<?> type;
// 可读属性的名称集合 可读属性就是存在 getter方法的属性,初始值为null
private final String[] readablePropertyNames;
// 可写属性的名称集合 可写属性就是存在 setter方法的属性,初始值为null
private final String[] writablePropertyNames;
// 记录了属性相应的setter方法,key是属性名称,value是Invoker方法
// 他是对setter方法对应Method对象的封装
private final Map<String, Invoker> setMethods = new HashMap<>();
// 属性相应的getter方法
private final Map<String, Invoker> getMethods = new HashMap<>();
// 记录了相应setter方法的参数类型,key是属性名称 value是setter方法的参数类型
private final Map<String, Class<?>> setTypes = new HashMap<>();
// 和上面的对应
private final Map<String, Class<?>> getTypes = new HashMap<>();
// 记录了默认的构造方法
private Constructor<?> defaultConstructor;
// 记录了所有属性名称的集合
private Map<String, String> caseInsensitivePropertyMap = new HashMap<>();

然后我们可以看看 Reflector 中提供的公共的 API 方法
image202204111319357arb39u.png

了解了 Reflector 对象的基本信息后我们需要如何来获取 Reflector 对象呢?在 MyBatis 中给我们提供了一个 ReflectorFactory 工厂对象。所以我们先来简单了解下 ReflectorFactory 对象,当然你也可以直接 new 出来.

ReflectorFactory

ReflectorFactory 接口主要实现了对 Reflector 对象的创建和缓存。

public interface ReflectorFactory {
	// 检测该ReflectorFactory是否缓存了Reflector对象
	boolean isClassCacheEnabled();
	// 设置是否缓存Reflector对象
	void setClassCacheEnabled(boolean classCacheEnabled);
	// 创建指定了Class的Reflector对象
	Reflector findForClass(Class<?> type);
}

ReflectorFactory 的使用:

@Test
public void test02() throws Exception{
	ReflectorFactory factory = new DefaultReflectorFactory();
	Reflector reflector = factory.findForClass(Student.class);
	System.out.println("可读属性:"+Arrays.toString(reflector.getGetablePropertyNames()));
	System.out.println("可写属性:"+Arrays.toString(reflector.getSetablePropertyNames()));
	System.out.println("是否具有默认的构造器:" +reflector.hasDefaultConstructor());
	System.out.println("Reflector对应的Class:" + reflector.getType());
}

Invoker

针对于 Class 中 Field 和 Method 的调用,在 MyBatis 中封装了 Invoker 对象来统一处理

public void test03() throws Exception{
	ReflectorFactory factory = new DefaultReflectorFactory();
	Reflector reflector = factory.findForClass(Student.class);
	// 获取构造器 生成对应的对象
	Object o = reflector.getDefaultConstructor().newInstance();
	MethodInvoker invoker1 = (MethodInvoker) reflector.getSetInvoker("id");
	invoker1.invoke(o,new Object[]{999});
	// 读取
	Invoker invoker2 = reflector.getGetInvoker("id");
	invoker2.invoke(o,null);
}

MetaClass

在 Reflector 中可以针对普通的属性操作,但是如果出现了比较复杂的属性,比如 private Person
person; 这种,我们要查找的表达式 person.userName.针对这种表达式的处理,这时就可以通过
MetaClass 来处理了。我们来看看主要的属性和构造方法

@Test
public void test7(){
	ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
	MetaClass meta = MetaClass.forClass(RichType.class, reflectorFactory);
	System.out.println(meta.hasGetter("richField"));
	System.out.println(meta.hasGetter("richProperty"));
	System.out.println(meta.hasGetter("richList"));
	System.out.println(meta.hasGetter("richMap"));
	System.out.println(meta.hasGetter("richList[0]"));
	System.out.println(meta.hasGetter("richType"));
	System.out.println(meta.hasGetter("richType.richField"));
	System.out.println(meta.hasGetter("richType.richProperty"));
	System.out.println(meta.hasGetter("richType.richList"));
	System.out.println(meta.hasGetter("richType.richMap"));
	System.out.println(meta.hasGetter("richType.richList[0]"));
	// findProperty 只能处理 . 的表达式
	System.out.println(meta.findProperty("richType.richProperty"));
	System.out.println(meta.findProperty("richType.richProperty1"));
	System.out.println(meta.findProperty("richList[0]"));
	System.out.println(Arrays.toString(meta.getGetterNames()));
}

MyBatis 中的反射模块,用在了 SqlSessionFactory 类中,以及执行 SQL 的时候在 Statement 获取结果集后,在做结果集映射的使用有使用到,

2.类型转换模块

在 PreparedStatement 为 SQL 语句绑定参数时,需要从 Java 类型转换为 JDBC 类型,而从结果集中获取数据时,则需要从 JDBC 类型转换为 Java 类型,所以我们来看下在 MyBatis 中是如何实现类型的转换的。

TypeHandler

MyBatis 中的所有的类型转换器都继承了 TypeHandler 接口,在 TypeHandler 中定义了类型转换器的最基本的功能。

TypeHandlerRegistry

MyBatis 中是将所有的 TypeHandler 都保存注册在了 TypeHandlerRegistry 中的

TypeAliasRegistry

我们在 MyBatis 的应用的时候会经常用到别名,这能大大简化我们的代码,其实在 MyBatis 中是通过 TypeAliasRegistry 类管理的。首先在构造方法中会注入系统常见类型的别名

在构建 SqlSessionFactory 时,在 Configuration 对象实例化的时候在成员变量中完成了 TypeHandlerRegistry 和 TypeAliasRegistry 的实例化

3.日志模块

在 MyBatis 系统启动的时候日志框架是如何选择的呢?首先我们在全局配置文件中我们可以设置对应的日志类型选择

image20220411133645n0r7tdm.png

这个"STDOUT_LOGGING"是怎么来的呢?在 Configuration 的构造方法中其实是设置的各个日志实现的别名的
image202204111337040zxh2dk.png

当我们开启了 STDOUT 的日志管理后,当我们执行 SQL 操作时我们发现在控制台中可以打印出相关的日志信息

那这些日志信息是怎么打印出来的呢?原来在 MyBatis 中的日志模块中包含了一个 JDBC 包,它并不是将日志信息通过 JDBC 操作保存到数据库中,而是通过 JDK 动态代理的方式,将 JDBC 操作通过指定的日志框架打印出来。下面我们就来看看它是如何实现的。

4.binding 模块

MapperRegistry

这个注册中心是用来保存 MapperProxyFactory 对象的

MapperProxyFactory

MapperProxyFactory 是一个工厂对象,专门负责创建 MapperProxy 对象

MapperProxy

通过 MapperProxyFactory 创建的 MapperProxy 是 Mapper 接口的代理对象,实现了 InvocationHandler 接口

MapperMethod

MapperMethod 中封装了 Mapper 接口中对应方法的信息,以及 SQL 语句的信息,我们可以把 MapperMethod 看成是配置文件中定义的 SQL 语句和 Mapper 接口的桥梁。

SqlCommand

SqlCommand 是 MapperMethod 中定义的内部类,记录了 SQL 语句名称以及对应的类型(UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH)

MethodSignature

MethodSignature 也是 MapperMethod 的内部类,在其中封装了 Mapper 接口中定义的方法相关信息。

execute 方法

最后我们需要来看下再 MapperMethod 中最核心的方法 execute 方法,这个方法完成了数据库操作

5.缓存模块

MyBatis 中的缓存分为一级缓存和二级缓存。但本质上是一样的,都是使用 Cache 接口实现的

Cache 接口的实现类很多,但是大部分都是装饰器,只有 PerpetualCache 提供了 Cache 接口的基本实现。

PerpetualCache

PerpetualCache 实现比较简单,底层使用 HashMap 记录缓存项

然后我们可以来看看 cache.decorators 包下提供的装饰器。他们都实现了 Cache 接口。这些装饰器都在 PerpetualCache 的基础上提供了一些额外的功能,通过多个组合实现一些特殊的需求。

BlockingCache 阻塞同步的缓存

@Mapper
@CacheNamespace(blocking = true)

通过源码我们能够发现,BlockingCache 本质上就是在我们操作缓存数据的前后通过 ReentrantLock 对象来实现了加锁和解锁操作。其他的具体实现类,大家可以自行查阅

image202204111348557pzpye8.png

一级缓存

一级缓存也叫本地缓存(Local Cache),MyBatis 的一级缓存是在会话(SqlSession)层面进行缓存的。MyBatis 的一级缓存是默认开启的,不需要任何的配置(如果要关闭,localCacheScope 设置为 STATEMENT)。

二级缓存

二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是 namespace 级别的,可以被多个 SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。

二级缓存的设置,首先是 settings 中的 cacheEnabled 要设置为 true,当然默认的就是为 true,这个步骤决定了在创建 Executor 对象的时候是否通过 CachingExecutor 来装饰。那么设置了 cacheEnabled 标签为 true 是否就意味着 二级缓存是否一定可用呢?当然不是,我们还需要在对应的映射文件中添加 cache 标签才行。

<!-- 声明这个namespace使用二级缓存 -->
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"
size="1024" <!—- 最多缓存对象个数,默认1024 -->
eviction="LRU" <!—- 回收策略 -->
flushInterval="120000" <!—- 自动刷新时间 ms,未配置时只有调用时刷新 -->
readOnly="false"/> <!—- 默认是false(安全),改为true可读写时,对象必须支持序列化 -->

这样的设置表示当前的映射文件中的相关查询操作都会触发二级缓存,但如果某些个别方法我们不希望走二级缓存怎么办呢?我们可以在标签中添加一个 useCache=false 来实现的设置不使用二级缓存

第三方缓存

<dependency>
	<groupId>org.mybatis.caches</groupId>
	<artifactId>mybatis-redis</artifactId>
	<version>1.0.0-beta2</version>
</dependency>

然后加上 Cache 标签的配置

<cache type="org.mybatis.caches.redis.RedisCache"
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>

然后添加 Redis 的属性文件

host=192.168.100.120
port=6379
connectionTimeout=5000
soTimeout=5000
database=0
6.插件模块

插件是一种常见的扩展方式,大多数开源框架也都支持用户通过添加自定义插件的方式来扩展或者
改变原有的功能,MyBatis 中也提供的有插件,虽然叫插件,但是实际上是通过拦截器(Interceptor)实现
的,在 MyBatis 的插件模块中涉及到责任链模式和 JDK 动态代理

1.自定义插件

1.1 创建 Interceptor 实现类,我们创建的拦截器必须要实现 Interceptor 接口,Interceptor 接口的定义为

public interface Interceptor {
	// 执行拦截逻辑的方法
	Object intercept(Invocation invocation) throws Throwable;
	// 决定是否触发 intercept()方法
	default Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}
	// 根据配置 初始化 Intercept 对象
	default void setProperties(Properties properties) {
		// NOP
	}
}

在 MyBatis 中 Interceptor 允许拦截的内容是

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

1.2 配置拦截器,创建好自定义的拦截器后,我们需要在全局配置文件中添加自定义插件的注册

<plugins>
	<plugin interceptor="com.gupaoedu.interceptor.FirstInterceptor">
	<property name="testProp" value="1000"/>
	</plugin>
</plugins>

2.插件实现原理

以 PageHelper 为例
image2022041114091156utiyo.png

首先会获取一个 SqlUtils 对象

sqlUtil.processPage(invocation);方法

通过 ThreadLocal 来获取分页数据信息

3.插件的应用场景分析
image20220411141118ogabnnq.png

7.事务模块

在实际开发中,控制数据库事务是一件非常重要的工作,MyBatis 使用 Transaction 接口对事务进行了抽象,定义的接口为

public interface Transaction

Transaction 接口的实现有两个分别是 JdbcTransaction 和 ManagedTransaction 两个

JdbcTransaction 依赖于 JDBC Connection 来控制事务的提交和回滚,声明的相关的属性为

protected Connection connection; // 事务对应的数据库连接
protected DataSource dataSource; // 数据库连接所属的 数据源
protected TransactionIsolationLevel level; // 事务的隔离级别
protected boolean autoCommit; // 是否自动提交

在构造方法中会完成除了 Connection 属性外的另外三个属性的初始化,而 Connection 会延迟初始化,在我们执行 getConnection 方法的时候才会执行相关的操作。源码比较简单,请自行查阅

ManagedTransaction 的实现更加的简单,它同样依赖 DataSource 字段来获取 Connection 对象,
但是 commit 方法和 rollback 方法都是空的,事务的提交和回滚都是依赖容器管理的。在实际开发中
MyBatis 通常会和 Spring 集成,数据库的事务是交给 Spring 进行管理的,这个我们会在 MyBatis 整合
Spring 中给大家介绍 SpringManagedTransaction。


标题:MyBatis(2)的体系结构与核心工作原理分析
作者:cuijianzhe
地址:https://cjzshilong.cn/articles/2022/04/11/1649659873822.html