前言
写完这个工具后就没有再管它了,当时就是为了实现这样的功能,并没有考虑代码的优雅性,以及可扩展性。
后来我重新审视这个工具时,感觉这个代码乱糟糟的(也许以后看这个版本也是乱糟糟),所以我决定将这个工具重构一下。
最初的想法就是,增加这个工具的扩展性,因为我的这个工具有一些功能是没有支持的,可以交给使用者自己去扩展, 那这样的话,易扩展性就显得比较重要了。
说到扩展性,我觉得常用的就是使用模板方法设计模式,或者回调机制,把不变的部分做成模板,把灵活变动的地方交由用户自己实现,但是我在分析工具核心代码时,觉得这几种方式都不好。
代码
代码托管在 Gitee 上,这是代码地址: gitee.com/listen_w/sq…
现在 master 分支还是第一版本的代码,新的代码在这个 restructure 分支上
哪些地方需要扩展
其实在重构之前我一直在考虑需要重构什么?哪些点可以改进?后来决定以扩展性为主。
既然要扩展,就要考虑哪些地方留给用户扩展的口子,我这次主要留了三个地方,一个是 核心的SQL执行器(新增概念,Executor),第二个是SQL解析器(parser包下面),第三个是SQL分析器(新增概念,analyzer包下)
这次重构变化有哪些
这次重构,主要借鉴了 Mybatis 的思想,尤其是拦截器机制,这个版本主要有这些变化:
- SQL查询增加可配置缓存;
- 支持注解配置SQL语句;
- 增加配置核心类, Configuration ;
- 使用拦截器机制实现可扩展性;
- 使用设计模式:代理模式、装饰者模式、责任链模式、建造者模式、适配器模式、观察者模式;
整体概览
- 项目标注注解
EnableSqlToMongoMapper
的作用:
由上可以知道,这个注解主要是导入了 SqlToMongoRegistrar
,而这个类的作用就是为标注 SqlToMongoMapper
的接口创建代理类,最终是在核心配置类 Configuration
中注册代理对象,具体后面专门分析。
- 项目启动后,根据springboot自动装配特性,会加载配置类
SqlToMongoAutoConfiguration
,这个配置类会创建一些 Bean 简单看一下各 Bean 的作用
MongoTemplateProxy
:继承MongoTemplate
,主要是重写doUpdate
方法,当Mongo文档更新后在此方法发送更新缓存的通知;SaveMongoEventListener
:继承AbstractMongoEventListener
,当 Mongo 文档保存时发送更新缓存的通知;InterceptorConfigurer
:拦截器配置,默认实现是拦截器适配器:InterceptorConfigurerAdapter
,它会添加一个拦截器模板:InterceptorTemplate
;Configuration
:核心配置类,后面具体分析;ClearCacheListener
:继承ApplicationListener
,监听清除缓存事件通知;SqlSession
:发起 Mongo 查询请求,实际会通过Executor
实现查询Mongo数据库 ;SQLToMongoTemplate
:方便 Mongo 查询的工具,通过SqlSession
查询;
- 一条SQL转Mongo语法过程
工程目录结构
sqltomongo-spring-boot-starter
└── src
└── main
└── java
├──com.rrtv
│ ├── adapter
│ │ └── MatchExpressionVisitorAdapter.java ------ 解析过滤匹配的 ExpressionVisitorAdapter
│ ├── analyzer ------ SQL分析器,将SQL解析的元数据封装成 Mongo API
│ │ ├── AbstractAnalyzer.java
│ │ ├── Analyzer.java
│ │ ├── GroupAnalyzer.java
│ │ ├── HavingAnalyzer.java
│ │ ├── JoinAnalyzer.java
│ │ ├── LimitAnalyzer.java
│ │ ├── MatchAnalyzer.java
│ │ ├── ProjectAnalyzer.java
│ │ └── SortAnalyzer.java
│ ├── annotation
│ │ ├── EnableSqlToMongoMapper.java ------ 启动类注解
│ │ ├── Intercepts.java ------ 插件注解
│ │ ├── Select.java ------ 查询注解
│ │ ├── Signature.java ------ 插件注解
│ │ └── SqlToMongoMapper.java ------ Mapper 接口类注解
│ ├── binding ------ 绑定,Mapper接口代理注册Bean
│ │ ├── MapperAnnotationBuilder.java ------ Mapper注解解析,解析 Select 注解
│ │ ├── MapperProxy.java
│ │ ├── MapperProxyFactory.java
│ │ └── SqlToMongoMapperFactoryBean.java
│ ├── cache ------ 缓存相关
│ │ ├── Cache.java
│ │ ├── CacheManager.java ------ 缓存管理器
│ │ ├── ClearCacheEvent.java ------ 清除缓存事件
│ │ ├── ClearCacheListener.java ------ 清除缓存监听器
│ │ ├── ConcurrentHashMapCache.java
│ │ ├── DefaultCacheManager.java
│ │ ├── MongoTemplateProxy.java
│ │ └── SaveMongoEventListener.java ------ Mongo 监听器
│ ├── common
│ │ ├── AggregationFunction.java ------ 聚合函数枚举
│ │ ├── ConversionFunction.java ------ 转化函数枚举
│ │ ├── ParserPartTypeEnum.java
│ │ └── MongoParserResult.java ------ SQL解析后封装Mongo API 结果
│ ├── configure
│ │ ├── SqlToMongoAutoConfiguration.java ------ 自动配置
│ │ ├── SqlToMongoMapperFactoryBean.java ------ SqlToMongoMapper 工厂Bean
│ │ └── SqlToMongoRegistrar.java ------ Mapper 接口 注册
│ ├── exception ------ 自定义异常
│ │ ├── BindingException.java
│ │ ├── NotSupportFunctionException.java
│ │ ├── NotSupportSubSelectException.java
│ │ ├── PluginException.java
│ │ ├── SqlParameterException.java
│ │ ├── SqlParserException.java
│ │ ├── SqlTypeException.java
│ │ └── TableAssociationException.java
│ ├── executor ------ 具体执行器
│ │ ├── CachingExecutor.java
│ │ ├── DefaultExecutor.java
│ │ └── Executor.java
│ ├── orm
│ │ ├── Configuration.java ------ 核心配置类,重点
│ │ ├── ConfigurationBuilder.java
│ │ ├── DefaultSqlSession.java ------ SqlSession 实现类
│ │ ├── DomParser.java ------ Dom 解析
│ │ ├── SqlSession.java
│ │ ├── SqlSessionBuilder.java
│ │ └── XNode.java ------ xml 解析结果封装
│ ├── parser ------ SQL 解析
│ │ ├── data ------ SQL 各个部分解析结果
│ │ │ ├── GroupData.java
│ │ │ ├── LimitData.java
│ │ │ ├── LookUpData.java
│ │ │ ├── MatchData.java
│ │ │ ├── PartSQLParserData.java
│ │ │ ├── PartSQLParserResult.java
│ │ │ ├── ProjectData.java
│ │ │ └── SortData.java
│ │ ├── GroupSQLParser.java ------ 解析 SQL 分组
│ │ ├── HavingSQLParser.java ------ 解析 SQL Having
│ │ ├── JoinSQLParser.java ------ 解析 SQL 表关联
│ │ ├── LimitSQLParser.java ------ 解析 SQL Limit
│ │ ├── PartSQLParser.java ------ 解析 SQL Limit
│ │ ├── OrderSQLParser.java ------ 解析 SQL 排序
│ │ ├── ProjectSQLParser.java ------ 解析 SQL 查询字段
│ │ ├── SelectSQLTypeParser.java ------ SQL 查询解析器,调用各个解析类解析SQL,并将元数据封装 Mongo 查询API
│ │ └── WhereSQLParser.java ------ 解析 SQL where 条件
│ ├── plugin ------ 插件相关,用于扩展
│ │ ├── Interceptor.java ------ 拦截器接口
│ │ ├── InterceptorChain.java ------ 拦截器链,封装所有拦截器
│ │ ├── InterceptorConfigurer.java ------ 拦截器配置,用于自定义拦截器
│ │ ├── InterceptorConfigurerAdapter.java ------ 拦截器配置适配器,默认添加拦截器模板
│ │ ├── InterceptorTemplate.java ------ 拦截器模板
│ │ ├── Invocation.java
│ │ └── Plugin.java ------ 插件具体逻辑
│ ├── util
│ │ ├── SqlCommonUtil.java ------ SQL 公共 util
│ │ ├── SqlParameterSetterUtil.java ------ SQL 设置参数 util
│ │ ├── SqlSupportedSyntaxCheckUtil.java ------ SQL 支持语法检查 util
│ │ └── StringUtils.java
│ └── SQLToMongoTemplate.java ------ 用于Mongo 查询的 bean,使用者直接注入该 Bean
└── resources
└── META-INF
└── spring.factories
复制代码
相对之前的最初版本,这里扩展了不少,有兴趣的可以去看看之前的那篇文章,这里主要加了 plugin
、 cache
、 executor
、 binding
包,而且把原来SQL解析和Mongo元数据分析部分都单独拆了出来。
模仿Mybatis,新增Configuration配置类概念
Configuration
和 Mybatis
中的 Configuration
思想一样,这里也是整个项目的核心配置类,上面我在介绍 "整体概览" 时讲到 SqlToMongoAutoConfiguration
配置类,由那张图可以知道, Configuration
基本被所有的Bean依赖,所以说它是个粘合剂也不为过。
Configuration
封装了一些最核心的配置,比如:缓存配置、Mapper代理、SQL解析映射、拦截器、SQL解析器、分析器等等,以下是 Configuration
的结构:
简单介绍几个核心方法:
getAnalyzerInstance
getPartSQLParserInstance
newPartSQLParser
newExecutor
addInterceptor
getMapper
addMapper
创建 Configuration
配置流程
Configuration
Bean 会在springboot自动装配时创建( SqlToMongoAutoConfiguration
配置类)
通过 ConfigurationBuilder
建造者设计模式创建 Configuration
对象,由下图可见,创建 Configuration
时,主要有以下工作:
- 解析XML文件,目前只是解析 Select注解,获取SQL语句
- 添加拦截器
- 设置缓存
- 设置缓存管理器
Configuration
作为最核心的配置,后面结合各部分功能一起说明。
如何增加SQL注解配置功能
原先只支持 XML 解析SQL,类似MyBatis的Mapper.xml,原理就是根据Spring boot自动装配,创建Bean时扫描包路径,使用 Dom4j解析xml,解析出<select>标签,将结果保存在一个Map中,这一部分逻辑没啥变化,不过现在改为创建 Configuration
这个Bean时解析 Mapper.xml文件了。
所以在创建 Configuration
对象时就完成了 XML的解析,类似下图这种 UserMapper.xml这种方式
但是我怎么同时支持用注解配置SQL呢?并且也希望注解解析的结果和XML解析的结果都在同一个配置里面,如下图:
我们知道这个 UserMapper 是个接口,一定会为它创建一个代理,那我们可以在创建代理时去解析注解配置的SQL。
具体代码分析创建过程:重点是这个Bean工厂, SqlToMongoMapperFactoryBean
有个属性 private Class<T> mapperInterface;
,就是 Mapper 接口字节码,可以从这个字节码中解析出 @Select
SQL配置注解,通过 afterPropertiesSet
方法,将Mapper接口加入 Configuration
中解析
knownMappers.put(type, new MapperProxyFactory<>(type));
这行代码作用是为Mapper接口创建一个代理工厂,并缓存在一个Map中, MapperProxyFactory
是利用JDK动态代理的方式生产 MapperProxy
的。
这部分是效仿了MyBatis的做法
通过 MapperAnnotationBuilder
解析 Mapper 的SQL注解配置,并将结果保存在核心配置 Configuration
中。
注意:这里从 SqlToMongoMapperFactoryBean 开始分析,关于手动注册Bean等流程,之前那篇文章已经分析过了,重构版本也是这个地方做了修改
SQL查询增加可配置缓存
由于MyBatis有个一、二级缓存,所以出于性能考虑,是不是也需要有个查询缓存呢? 对于加缓存需要考虑这几个方面:
- 查询缓存应该添加在哪里呢?
- 是否应该支持可配置缓存?
- 能否支持动态启停缓存?
- 缓存如何更新?
- 如何让使用者扩展缓存?
查询缓存应该添加在哪里呢?
查询缓存应该加在查询的地方,原来的设计是 SqlSession
提供查询的方法,如果在 SqlSession
中增加缓存查询的方法我觉得不够优雅,所以就弄了一个 Executor
的概念, SqlSession
持有 Executor
, SqlSession
的查询方法通过 Executor
完成,而 Executor
有两种实现,一种是 DefaultExecutor
,它会去解析SQL,查询数据;另一种是 CachingExecutor
,它具备缓存功能,它持有 DefaultExecutor
,具体查询工作交由 DefaultExecutor
去做,这也是 装饰者设计模式 的体现。
配置是否需要缓存
MyBatis的一级缓存是默认开启的,但我觉得缓存是否开启还是交由使用者来决定比较好,所以在配置文件中留了口子,可配置是否启用缓存。
Configuration
有个 newExecutor
方法, 创建 Executor
时根据是否开启缓存来决定使用 CachingExecutor
还是 DefaultExecutor
;
缓存如何自定义配置
找到 CachingExecutor
执行器,它内部持有一个 Cache
, 而 Cache
是通过 Configuration
获取的。
再回到创建 Configuration
的代码( ConfigurationBuilder.build
方法),如图,如果开启缓存,就配置一个默认的缓存管理器,而缓存管理器会根据配置的缓存类的全路劲名创建 Cache
对象。
缓存管理器创建缓存时,会利用反射创建Cache的子类对象,如果没有就使用 ConcurrentHashMapCache
对象。
由上可知,自定义缓存只要两步:
- 自己写一个类,实现
com.rrtv.cache.Cache
接口; - 在spring配置中将自己实现的缓存类的全路径名配置上去,比如下图
Cache
接口
默认缓存实现
默认缓存的实现很简单,来看 CachingExecutor
类 (代码很简单,看注释即可)
这里要注意一点就是,最后有个设置缓存索引,这个后面再说,和缓存更新有关系。
默认缓存使用 ConcurrentHashMap
来存储的,生成缓存的方式,就是参数拼接最后Md5。
缓存如何更新
缓存的更新这是非常重要的一点,要更新缓存,就要知道,缓存什么时候需要更新,修改了Mongo的实体就需要更新缓存了,这里有两个问题:
- 怎么监听哪个实体被修改了
- 被修改的实体影响了哪些查询语句
监听实体被修改的方式
jpa有审计的功能,Mongo也有文档修改监听功能,对这一块不了解的可以看我的这篇文章: 一招解决 SpringDataMongodb 审计(统一维护公共字段)功能 实体修改后就需要清除缓存,清除缓存一般在缓存管理器中完成,那监听到修改后如何通知缓存管理器清除缓存呢?这里使用 发布订阅设计模式 ,严格来说是使用 Spring的事件通知机制 。
事件发送
事件监听
事件监听后,通过缓存管理器来清除缓存
清除与被修改的实体相关的缓存
现在有个问题就是,我怎么知道这个实体被哪个缓存引用着,其实这个决解方案也很简单,有点像倒排索引,比如我一条SQL语句: select * from user u inner join user_info ui on u.id = ui.uid
,那如果 user 或者 user_info 修改了,就需要把这条SQL对应的缓存删除吧,所以就可以搞一个MAP,比如:key是 user,value是 这条语句的缓存Key,当修改user实体了,就可以找到这个Map对应的Vaule,删除这个Value对应的缓存就行。
由以上思路:我们在查询SQL时,获取SQL的表,将表和缓存Key维护在Map中
当发现实体变化时,只要清除这个Map对应的Key,value就行
最核心变化,模仿Mybatis,通过拦截器来扩展本工具的核心模块
拦截器是本次重构的核心,通过拦截器来扩展核心功能。这里有几个注意的地方:
- 拦截器作用在哪些地方
- 拦截器工作原理
- 如何添加自己的拦截器
拦截器作用在哪些地方
这次拦截器扩展的点,主要在这些地方 执行器( Executor
)的查询方法、SQL解析器( PartSQLParser
)的 proceedData
方法、Mongo分析器( Analyzer
)的 proceed
方法。
拦截器工作原理
Configuration
里面维护了 拦截器链
Configuration
创建执行器、SQL解析器、Mongo 分析器代码:
由上图可见,创建这些组件时都有这个 interceptorChain.pluginAll
代码。
Plugin
代码比较长,直接粘贴了,可以看到每个拦截器都在目标对象上加了一层代理,通过代理去执行拦截器的方法。
/**
* @Classname Plugin
* @Description
* @Date 2022/8/11 18:10
* @Created by wangchangjiu
*/
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = Optional.ofNullable(interceptor.getClass().getAnnotation(Intercepts.class))
.orElse(interceptor.getClass().getSuperclass().getAnnotation(Intercepts.class));
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
try {
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}
复制代码
这里有个重点方法: Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor)
,这个方法就是获取注解中要拦截的类和方法,举个例子:
这里是一个拦截器模板, getSignatureMap
方法就是解析 @Intercepts、@Signature
这两个注解,然后找到要拦截的类和方法,保存到Map中。由于创建执行器、SQL解析器、Mongo 分析器时返回的是代理对象,所以在执行这个组件方法时会调用代理对象的 invoke
方法,也就是 Plugin.invoke
方法,该方法就会判断要执行的类的方法是否需要拦截,需要的话就执行自己定义的拦截方法。
注意,拦截器的思想完全来自 Mybatis,我这里讲的比较简单,如果不理解,就去找资料看看MyBatis的拦截器原理是一样的。
用户如何自定义拦截器
首先拦截器维护在 Configuration 中,在创建 Configuration 时,有个参数 InterceptorConfigurer
InterceptorConfigurer
是配置的 Bean
所以我们只要自己写个类,实现 InterceptorConfigurer
接口,并把这个类加入Spring容器
那知道怎么加入拦截器了,接下来就是怎么写一个拦截器了,为了使用者方便,我这里实现了一个拦截器模板,用户只需要继承这个模板,扩展自己需要的方法就行了
package com.rrtv.plugin;
import com.rrtv.analyzer.Analyzer;
import com.rrtv.annotation.Intercepts;
import com.rrtv.annotation.Signature;
import com.rrtv.executor.Executor;
import com.rrtv.parser.*;
import com.rrtv.parser.data.PartSQLParserData;
import lombok.extern.slf4j.Slf4j;
import net.sf.jsqlparser.statement.select.PlainSelect;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
/**
* @Classname DefaultInterceptor
* @Description 拦截器模板,自定义拦截器可以继承这个模板
* @Date 2022/8/12 16:39
* @Created by wangchangjiu
*/
@Slf4j
@Intercepts(
{
@Signature(type = PartSQLParser.class, method = "proceedData", args = {PlainSelect.class, PartSQLParserData.class}),
@Signature(type = Executor.class, method = "selectOne", args = {String.class, Class.class, Object[].class}),
@Signature(type = Executor.class, method = "selectList", args = {String.class, Class.class, Object[].class}),
@Signature(type = Analyzer.class, method = "proceed", args = {List.class, PartSQLParserData.class})
}
)
public class InterceptorTemplate implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
Object[] args = invocation.getArgs();
if(target instanceof Executor){
return this.interceptExecutor(invocation);
} else if(target instanceof Analyzer) {
return this.interceptAnalyzer(invocation);
} else {
PlainSelect plain = (PlainSelect) args[0];
PartSQLParserData data = (PartSQLParserData) args[1];
if(target instanceof HavingSQLParser){
return this.interceptHavingSQLParser(plain, data, invocation);
} else if(target instanceof GroupSQLParser) {
return this.interceptGroupSQLParser(plain, data, invocation);
} else if(target instanceof JoinSQLParser) {
return this.interceptJoinSQLParser(plain, data, invocation);
} else if(target instanceof LimitSQLParser) {
return this.interceptLimitSQLParser(plain, data, invocation);
} else if(target instanceof OrderSQLParser) {
return this.interceptOrderSQLParser(plain, data, invocation);
} else if(target instanceof ProjectSQLParser) {
return this.interceptProjectSQLParser(plain, data, invocation);
} else if(target instanceof WhereSQLParser) {
return this.interceptWhereSQLParser(plain, data, invocation);
}
}
return invocation.proceed();
}
private Object interceptAnalyzer(Invocation invocation) throws Exception {
return invocation.proceed();
}
private Object interceptExecutor(Invocation invocation) throws Exception {
return invocation.proceed();
}
public Object interceptHavingSQLParser(PlainSelect plain, PartSQLParserData data, Invocation invocation) throws Exception {
return invocation.proceed();
}
public Object interceptGroupSQLParser(PlainSelect plain, PartSQLParserData data, Invocation invocation) throws Exception {
return invocation.proceed();
}
public Object interceptLimitSQLParser(PlainSelect plain, PartSQLParserData data, Invocation invocation) throws Exception {
return invocation.proceed();
}
public Object interceptOrderSQLParser(PlainSelect plain, PartSQLParserData data, Invocation invocation) throws Exception {
return invocation.proceed();
}
public Object interceptProjectSQLParser(PlainSelect plain, PartSQLParserData data, Invocation invocation) throws Exception {
return invocation.proceed();
}
public Object interceptWhereSQLParser(PlainSelect plain, PartSQLParserData data, Invocation invocation) throws Exception {
return invocation.proceed();
}
public Object interceptJoinSQLParser(PlainSelect plain, PartSQLParserData data, Invocation invocation) throws Exception {
return invocation.proceed();
}
}
复制代码
这个模板对SQL执行器、SQL解析器、SQL分析器的方法都有拦截,想要扩展那部分就实现对应的方法就行。 比如:
SQL解析器( PartSQLParser
)、Mongo分析器( Analyzer
)设计
SQL解析器( PartSQLParser
)设计
原来这块并没有任何设计,如下图:这种无法扩展,而且也得也不够优雅
现在的做法,由于SQL解析器是对SQL各个部分单独解析的,而且没有先后顺序,所以完全可以抽象出一个解析器,并行的循环解析,每个解析器实现具体的解析过程,并设置解析结果。
解析器接口:
以Where解析器为例
其他解析器就不列举了,和where解析器思想一样。
所有解析器,解析器在 parser
包下,一条查询语句各个部分有对应的解析
创建解析器时优先从缓存中获取,缓存没有,那么按照解析SQL的部位来创建解析器(实际是被拦截器包裹的解析器代理)
Mongo分析器( Analyzer
)设计
分析器也是按照SQL各个部分,分析组装Mongo语法的API,不同的是,组装Mongo API时是有顺序的,所以这里使用 责任链设计模式 。
Configuration.getAnalyzerInstance()
创建分析器组件,使用 单例设计模式、建造者设计模式和责任链设计模式 ,使用抽象类 AbstractAnalyzer
按照顺序添加SQL分析器,同样每个分析器都被拦截器链包裹的,也就是说这里添加的都是SQL分析器代理对象。
分析器接口
分析器抽象类
由上图可知,分析器抽象类内部类 Builder
有个 addAnalyzer
方法,该方法就是设置责任链关系,维护下一个分析器。
以 SortAnalyzer
为例,具体分析器实现
下个版本
这个工具我会一直维护,功能也会一直去叠加,下个版本想支持动态SQL,类似 MyBatis 的动态标签,比如:"<if>"、"<where>" 等标签。