4.1 插件模块
MyBatis 通过拦截器(Interceptor)实现插件功能。
4.1.1 责任链模式
在责任链模式中,将完整的实现逻辑拆分到多个只包含部分逻辑的、功能单一的 Handler 处理类中,开发人员可以根据业务需求将多个 Handler 对象组合成一条责任链,实现请求的处理。在一条责任链中,每个 Handler 对象都包含对下一个 Handler 对象的引用,一个 Handler 对象处理完请求消息时,会把消息传给下一个 Handler 对象继续处理,依此类推,直到整条责任链结束。
责任链模式可以通过重用 Handler 类实现代码复用,还可以通过动态改变责任链内 Handler 对象的组合顺序或动态新增、删除 Handler 对象,满足新的需求。这大大提高了系统的灵活性,也符合开闭原则。
4.1.2 Interceptor
MyBatis 允许用户使用自定义拦截器对 SQL 语句执行过程中的某一点进行拦截。默认情况下可以拦截 Executor、ParameterHandler、ResultSetHandler 以及 StatementHandler 的方法。使用时需要实现 Interceptor 接口。
public interface Interceptor {
// 执行拦截逻辑的方法
Object intercept(Invocation invocation) throws Throwable;
// 决定是否触发 intercept() 方法
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// 根据配置初始化 Interceptor 对象
default void setProperties(Properties properties) {
// NOP
}
}
-
@Intercepts 注解指定了一个 @Signature 注解列表
-
@Signature 注解标识该插件需要拦截的方法签名,据此确定唯一方法 :
type:需要拦截的类型
method:需要拦截的方法
args:被拦截方法的参数列表
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
@Intercepts({
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(
type = Executor.class,
method = "close",
args = {boolean.class}
)
})
public class ExamplePlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
return null;
}
}
ExamplePlugin 拦截的两个方法分别是:
- Executor#query(MappedStatement, Object, RowBounds, ResultHandler)
- Executor#close(boolean)
最后在 mybatis-config.xml 中对该拦截器进行配置,一个用户定义的拦截器就配置好了。
<!-- 插件配置,顺序要在<environments>之前 -->
<plugins>
<plugin interceptor="com.example.chapter4.section1.ExamplePlugin">
<!-- 配置属性 -->
<property name="testProp" value="100"/>
</plugin>
</plugins>
在 MyBatis 中使用上述四类拦截器,都是通过 Configuration.new*()
系列方法创建的。下面以 Executor 的 Configuration.newExecutor() 方法为例进行分析。
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
// 根据参数,选择合适的 Executor 实现
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 根据配置决定是否开启二级缓存
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 创建 Executor 代理对象
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
InterceptorChain
记录了 mybatis-config.xml 文件中配置的拦截器。pluginAll() 方法会给他们分别创建代理对象。
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
interceptor.plugin()
调用 Plugin.wrap()
方法
public static Object wrap(Object target, Interceptor interceptor) {
// 用户自定义 Interceptor 中 @Signature 注解的信息
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 目标类型
Class<?> type = target.getClass();
// 目标类型实现的接口,即上述 4 类对象
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 使用 JDK 动态代理创建代理对象
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
Plugin.invoke()
实现拦截功能
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 当前方法所在类或接口中,可被当前 Interceptor 拦截的方法
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 如果当前方法需要被拦截,调用 intercept() 方法进行拦截
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
4.1.3 应用场景分析
1. 分页查询
2. JsqlParser介绍
3. 分表插件
4. 其他场景
4.2 MyBatis 与 Spring 集成
4.2.1 Spring基本概念
1. IoC
IoC(Inversion of Control,控制反转),其思想是将开发人员设计好的对象交给 IoC 容器控制,而不是直接在程序中通过 new 来创建。当需要使用某个对象时,由 IoC 容器创建该对象并注入依赖对象中。
2. DI
DI(Dependency Injection,依赖注入),对象之间的依赖关系是由容器在运行期间决定的,也就是说,由容器动态地确定并维持两个对象之间的某个依赖关系。
3. AOP
AOP(Aspect Oriented Programming,面向切面编程),对面向对象编程的补充和完善。在面向对象编程中,开发人员可以通过封装、继承、多态等概念建立对象的层次结构。但是对于权限检测、日志输出、事务管理等相关的代码,会横跨整个对象层次结构,散落在多个对象中。AOP 利用“横切”技术将影响了多个类的公共代码抽取出来,封装到一个可重用的模块中,称为 Aspect(切面)。
4.2.2 Spring MVC 介绍
Spring MVC 是 Spring 提供的一个强大而灵活的 Web 框架,它是一款实现了 MVC 设计模式的、请求驱动的轻量级 Web 框架。
MVC(Model-View-Controller,模型-视图-控制器)架构模式,Model 主要负责封装需要在视图上展示的数据;View 只用于展示数据,不包含任何业务逻辑;Controller 主要负责接收用户的请求,调用底层的 Service 层执行具体的业务逻辑。
MVC 通过分离模型、视图和控制器实现业务逻辑与界面之间的解耦。
在 Spring MVC 应用中,Model 通常由 POJO 对象组成,它会在业务逻辑层中被处理,在持久层中被持久化。View 的常用方案是 JSP,Controller 则是指 Spring 注解配置的 Controller 类。
DispatcherServlet 是一个前端控制器,是整个 Spring MVC 框架的核心组件。它主要负责整体流程的调度,在接收 HTTP 请求之后,会根据请求调用 Spring MVC 中的各个组件。拦截指定格式的 URL 请求,初始化 WebApplicationContext,初始化 Spring MVC 各个组成组件,根据 Controller 返回的逻辑视图名选择具体的视图进行渲染等一系列工作。
Spring MVC 中常用的接口及其含义:
- Controller 接口:用户可以通过实现 Controller 接口实现控制器,但是多数情况下,使用的是 @Controller 注解
- HandlerMapping 接口:主要负责用户请求到 Controller 之间的映射
- HandlerInterceptor 接口:拦截器,可以自定义,用来完成拦截请求的操作
- ModelAndView:Controller 处理完请求之后,会将视图的名称以及模型数据封装成 ModelAndView 对象返回到 DispatcherServlet 中
- ViewResolver接口:主要负责将视图的逻辑名称映射成具体的视图
- View 接口:具体视图
Spring MVC 处理一个 HTTP 请求的整体流程
- 用户通过浏览器发送 HTTP 请求提交到 DispatcherServlet
- DispatcherServlet 根据请求查找一个或多个 HandlerMapping,并根据 HandlerMapping 找到请求对应的 Controller
- DispatcherServlet 将请求提交到 Controller 处理
- Controller 调用 Service 层进行处理
- Service 处理完成后,Controller 返回 ModelAndView
- DispatcherServlet 查询一个或多个 ViewResolver 视图解析器,进行视图解析
- 查找 ModelAndView 指定的视图
- HTTP 响应,View 负责将结果显示到客户端
4.2.3 集成环境搭建
略
4.2.4 MyBatis-Spring剖析
1. SqlSessionFactoryBean
2. SpringManagedTransaction
3. SqlSessionTemplate&SqlSessionDaoSupport
4. MapperFactoryBean&MapperScannerConfigure
4.3 拾遗
4.3.1 应用sql节点
使用 sql 节点,可以将 SQL 语句中的公共部分独立出来,然后使用 include 节点引入这些公共部分。
4.3.2 OgnlUtils 工具类
OGNL 表达式主要应用:
- if 节点的 test 属性以及 bind 节点的 value 属性。
- “${}” 参数
4.3.3 SQL语句生成器
private String selectPersonSql() {
return new SQL() {
{
SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
FROM("PERSON P");
FROM("ACCOUNT A");
INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
WHERE("P.ID = A.ID");
WHERE("P.NAME like ?");
OR();
WHERE("P.LAST_NAME like ?");
GROUP_BY("P.ID");
HAVING("P.LAST_NAME like ?");
OR();
HAVING("P.FIRST_NAME like ?");
ORDER_BY("P.ID");
ORDER_BY("P.FULL_NAME");
}
}.toString();
}
4.3.4 动态SQL脚本插件
MyBatis 使用 XmlLanguageDriver 驱动器实现动态 SQL 语句的解析和处理
用户可以通过实现org.apache.ibatis.scripting.LanguageDriver
接口自定义动态 SQL 语句驱动器
4.3.5 MyBatis-Generator 逆向工程
MyBatis-Generator 是 MyBatis 提供的,用以帮助开发人员自动生成映射配置文件的工具。
4.4 本章小结
- MyBatis 的插件扩展方式
- MyBatis 与 Spring 集成
- MyBatis 使用技巧