撸了几天Mybatis源码,将之前写的中间件重构了

4,517 阅读16分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

前言

之前我写过一个工具,就是通过 “原生SQL去查Mongo数据”,具体可以查看我的这篇博文:听说你不会Mongo的API?我写个插件用SQL去查Mongo

写完这个工具后就没有再管它了,当时就是为了实现这样的功能,并没有考虑代码的优雅性,以及可扩展性。
后来我重新审视这个工具时,感觉这个代码乱糟糟的(也许以后看这个版本也是乱糟糟),所以我决定将这个工具重构一下。

最初的想法就是,增加这个工具的扩展性,因为我的这个工具有一些功能是没有支持的,可以交给使用者自己去扩展, 那这样的话,易扩展性就显得比较重要了。

说到扩展性,我觉得常用的就是使用模板方法设计模式,或者回调机制,把不变的部分做成模板,把灵活变动的地方交由用户自己实现,但是我在分析工具核心代码时,觉得这几种方式都不好。

注意:看这篇文章之前,请移步我写的另一篇文章《听说你不会Mongo的API?我写个插件用SQL去查Mongo

代码

代码托管在 Gitee 上,代码地址:gitee.com/listen_w/sq…
现在 master 分支还是第一版本的代码,新的代码在这个 restructure 分支上 image.png

Github 的代码地址 :github.com/jettwangcj/…

哪些地方需要扩展

其实在重构之前我一直在考虑需要重构什么?哪些点可以改进?后来决定以扩展性为主。

既然要扩展,就要考虑哪些地方留给用户扩展的口子,我这次主要留了三个地方,一个是核心的SQL执行器(新增概念,Executor),第二个是SQL解析器(parser包下面),第三个是SQL分析器(新增概念,analyzer包下)

这次重构变化有哪些

这次重构,主要借鉴了 Mybatis 的思想,尤其是拦截器机制,这个版本主要有这些变化:

  1. SQL查询增加可配置缓存;
  2. 支持注解配置SQL语句;
  3. 增加配置核心类,Configuration
  4. 使用拦截器机制实现可扩展性;
  5. 使用设计模式:代理模式、装饰者模式、责任链模式、建造者模式、适配器模式、观察者模式;

整体概览

  1. 项目标注注解 EnableSqlToMongoMapper 的作用:

image.png 由上可以知道,这个注解主要是导入了 SqlToMongoRegistrar,而这个类的作用就是为标注 SqlToMongoMapper 的接口创建代理类,最终是在核心配置类 Configuration 中注册代理对象,具体后面专门分析。

  1. 项目启动后,根据springboot自动装配特性,会加载配置类 SqlToMongoAutoConfiguration,这个配置类会创建一些 Bean image.png 简单看一下各 Bean 的作用
  • MongoTemplateProxy :继承 MongoTemplate,主要是重写 doUpdate 方法,当Mongo文档更新后在此方法发送更新缓存的通知;
  • SaveMongoEventListener:继承 AbstractMongoEventListener,当 Mongo 文档保存时发送更新缓存的通知;
  • InterceptorConfigurer:拦截器配置,默认实现是拦截器适配器:InterceptorConfigurerAdapter,它会添加一个拦截器模板: InterceptorTemplate
  • Configuration:核心配置类,后面具体分析;
  • ClearCacheListener:继承 ApplicationListener,监听清除缓存事件通知;
  • SqlSession:发起 Mongo 查询请求,实际会通过 Executor 实现查询Mongo数据库 ;
  • SQLToMongoTemplate:方便 Mongo 查询的工具,通过 SqlSession 查询;
  1. 一条SQL转Mongo语法过程 image.png

工程目录结构

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

相对之前的最初版本,这里扩展了不少,有兴趣的可以去看看之前的那篇文章,这里主要加了 plugincacheexecutorbinding 包,而且把原来SQL解析和Mongo元数据分析部分都单独拆了出来。

模仿 Mybatis,新增 Configuration 配置类概念

ConfigurationMybatis中的Configuration思想一样,这里也是整个项目的核心配置类,上面我在介绍 "整体概览" 时讲到 SqlToMongoAutoConfiguration配置类,由那张图可以知道,Configuration 基本被所有的Bean依赖,所以说它是个粘合剂也不为过。 image.png

Configuration 封装了一些最核心的配置,比如:缓存配置、Mapper代理、SQL解析映射、拦截器、SQL解析器、分析器等等,以下是 Configuration 的结构: image.png
简单介绍几个核心方法:

  • getAnalyzerInstance:创建 SQL 分析器责任链 (被拦截器链代理的),使用单利设计模式;
  • getPartSQLParserInstance:获取SQL各个部分解析器,具备缓存作用;
  • newPartSQLParser:创建SQL解析器(被拦截器链代理的);
  • newExecutor:创建执行器(缓存执行器 和 默认执行器),并对执行器增加拦截器处理;
  • addInterceptor:添加拦截器;
  • getMapper:获取Mapper代理对象;
  • addMapper:添加Mapper代理对象,并解析出 @Select 注解

创建 Configuration 配置流程

Configuration Bean 会在springboot自动装配时创建(SqlToMongoAutoConfiguration配置类) image.png

通过 ConfigurationBuilder 建造者设计模式创建 Configuration 对象,由下图可见,创建 Configuration时,主要有以下工作:

  • 解析XML文件,目前只是解析 Select注解,获取SQL语句
  • 添加拦截器
  • 设置缓存
  • 设置缓存管理器

image.png

Configuration作为最核心的配置,后面结合各部分功能一起说明。

如何增加SQL注解配置功能

原先只支持 XML 解析SQL,类似MyBatis的Mapper.xml,原理就是根据Spring boot自动装配,创建Bean时扫描包路径,使用 Dom4j解析xml,解析出<select>标签,将结果保存在一个Map中,这一部分逻辑没啥变化,不过现在改为创建 Configuration 这个Bean时解析 Mapper.xml文件了。 image.png image.png

所以在创建 Configuration 对象时就完成了 XML的解析,类似下图这种 UserMapper.xml这种方式 image.png

但是我怎么同时支持用注解配置SQL呢?并且也希望注解解析的结果和XML解析的结果都在同一个配置里面,如下图:
image.png

我们知道这个 UserMapper 是个接口,一定会为它创建一个代理,那我们可以在创建代理时去解析注解配置的SQL。 image.png 具体代码分析创建过程: image.png 重点是这个Bean工厂,SqlToMongoMapperFactoryBean 有个属性 private Class<T> mapperInterface;,就是 Mapper 接口字节码,可以从这个字节码中解析出 @Select SQL配置注解,通过 afterPropertiesSet 方法,将Mapper接口加入 Configuration 中解析 image.png

knownMappers.put(type, new MapperProxyFactory<>(type));这行代码作用是为Mapper接口创建一个代理工厂,并缓存在一个Map中,MapperProxyFactory 是利用JDK动态代理的方式生产 MapperProxy 的。

这部分是效仿了MyBatis的做法

通过 MapperAnnotationBuilder 解析 Mapper 的SQL注解配置,并将结果保存在核心配置 Configuration 中。 image.png

注意:这里从 SqlToMongoMapperFactoryBean 开始分析,关于手动注册Bean等流程,之前那篇文章已经分析过了,重构版本也是这个地方做了修改

SQL查询增加可配置缓存

由于MyBatis有个一、二级缓存,所以出于性能考虑,是不是也需要有个查询缓存呢? 对于加缓存需要考虑这几个方面:

  • 查询缓存应该添加在哪里呢?
  • 是否应该支持可配置缓存?
  • 能否支持动态启停缓存?
  • 缓存如何更新?
  • 如何让使用者扩展缓存?

查询缓存应该添加在哪里呢?

查询缓存应该加在查询的地方,原来的设计是 SqlSession 提供查询的方法,如果在 SqlSession 中增加缓存查询的方法我觉得不够优雅,所以就弄了一个 Executor 的概念,SqlSession 持有 ExecutorSqlSession 的查询方法通过 Executor 完成,而 Executor 有两种实现,一种是 DefaultExecutor,它会去解析SQL,查询数据;另一种是 CachingExecutor ,它具备缓存功能,它持有 DefaultExecutor ,具体查询工作交由 DefaultExecutor 去做,这也是装饰者设计模式的体现。 image.png

配置是否需要缓存

MyBatis的一级缓存是默认开启的,但我觉得缓存是否开启还是交由使用者来决定比较好,所以在配置文件中留了口子,可配置是否启用缓存。 image.png

Configuration 有个 newExecutor 方法, 创建 Executor 时根据是否开启缓存来决定使用 CachingExecutor 还是 DefaultExecutorimage.png

缓存如何自定义配置

找到 CachingExecutor 执行器,它内部持有一个 Cache , 而 Cache 是通过 Configuration 获取的。 image.png

再回到创建 Configuration 的代码(ConfigurationBuilder.build 方法),如图,如果开启缓存,就配置一个默认的缓存管理器,而缓存管理器会根据配置的缓存类的全路劲名创建 Cache 对象。 image.png 缓存管理器创建缓存时,会利用反射创建Cache的子类对象,如果没有就使用 ConcurrentHashMapCache 对象。 image.png

由上可知,自定义缓存只要两步:

  1. 自己写一个类,实现 com.rrtv.cache.Cache 接口;
  2. 在spring配置中将自己实现的缓存类的全路径名配置上去,比如下图 image.png

Cache 接口 image.png

默认缓存实现

默认缓存的实现很简单,来看 CachingExecutor 类 (代码很简单,看注释即可) image.png 这里要注意一点就是,最后有个设置缓存索引,这个后面再说,和缓存更新有关系。

image.png 默认缓存使用 ConcurrentHashMap 来存储的,生成缓存的方式,就是参数拼接最后Md5。

缓存如何更新

缓存的更新这是非常重要的一点,要更新缓存,就要知道,缓存什么时候需要更新,修改了Mongo的实体就需要更新缓存了,这里有两个问题:

  • 怎么监听哪个实体被修改了
  • 被修改的实体影响了哪些查询语句

监听实体被修改的方式

jpa有审计的功能,Mongo也有文档修改监听功能,对这一块不了解的可以看我的这篇文章:一招解决 SpringDataMongodb 审计(统一维护公共字段)功能 实体修改后就需要清除缓存,清除缓存一般在缓存管理器中完成,那监听到修改后如何通知缓存管理器清除缓存呢?这里使用发布订阅设计模式,严格来说是使用 Spring的事件通知机制

事件发送 image.png image.png

事件监听 image.png 事件监听后,通过缓存管理器来清除缓存

清除与被修改的实体相关的缓存

现在有个问题就是,我怎么知道这个实体被哪个缓存引用着,其实这个决解方案也很简单,有点像倒排索引,比如我一条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中 image.png image.png

当发现实体变化时,只要清除这个Map对应的Key,value就行 image.png

最核心变化,模仿Mybatis,通过拦截器来扩展本工具的核心模块

拦截器是本次重构的核心,通过拦截器来扩展核心功能。这里有几个注意的地方:

  • 拦截器作用在哪些地方
  • 拦截器工作原理
  • 如何添加自己的拦截器

拦截器作用在哪些地方

这次拦截器扩展的点,主要在这些地方 执行器(Executor)的查询方法、SQL解析器(PartSQLParser)的 proceedData 方法、Mongo分析器(Analyzer)的 proceed 方法。

拦截器工作原理

Configuration 里面维护了 拦截器链 image.png

Configuration 创建执行器、SQL解析器、Mongo 分析器代码: image.png image.png image.png 由上图可见,创建这些组件时都有这个 interceptorChain.pluginAll 代码。 image.png image.png

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),这个方法就是获取注解中要拦截的类和方法,举个例子: image.png 这里是一个拦截器模板,getSignatureMap 方法就是解析 @Intercepts、@Signature 这两个注解,然后找到要拦截的类和方法,保存到Map中。由于创建执行器、SQL解析器、Mongo 分析器时返回的是代理对象,所以在执行这个组件方法时会调用代理对象的 invoke 方法,也就是 Plugin.invoke 方法,该方法就会判断要执行的类的方法是否需要拦截,需要的话就执行自己定义的拦截方法。

注意,拦截器的思想完全来自 Mybatis,我这里讲的比较简单,如果不理解,就去找资料看看MyBatis的拦截器原理是一样的。

用户如何自定义拦截器

首先拦截器维护在 Configuration 中,在创建 Configuration 时,有个参数 InterceptorConfigurer image.png

InterceptorConfigurer 是配置的 Bean image.png

所以我们只要自己写个类,实现 InterceptorConfigurer 接口,并把这个类加入Spring容器中,比如,实现一个 MyInterceptorConfigurer 类,并加入 Spring 容器。 image.png image.png

那知道怎么加入拦截器了,接下来就是怎么写一个拦截器了,为了使用者方便,我这里实现了一个拦截器模板,用户只需要继承这个模板,扩展自己需要的方法就行了

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分析器的方法都有拦截,想要扩展那部分就实现对应的方法就行。 比如: image.png

SQL解析器(PartSQLParser)、Mongo分析器(Analyzer)设计

SQL解析器(PartSQLParser)设计

原来这块并没有任何设计,如下图:这种无法扩展,而且也得也不够优雅 image.png image.png

现在的做法,由于SQL解析器是对SQL各个部分单独解析的,而且没有先后顺序,所以完全可以抽象出一个解析器,并行的循环解析,每个解析器实现具体的解析过程,并设置解析结果。 image.png

解析器接口:
image.png

以Where解析器为例 image.png 其他解析器就不列举了,和where解析器思想一样。

所有解析器,解析器在 parser 包下,一条查询语句各个部分有对应的解析
image.png

创建解析器时优先从缓存中获取,缓存没有,那么按照解析SQL的部位来创建解析器(实际是被拦截器包裹的解析器代理) image.png

Mongo分析器(Analyzer)设计

分析器也是按照SQL各个部分,分析组装Mongo语法的API,不同的是,组装Mongo API时是有顺序的,所以这里使用责任链设计模式

image.png

Configuration.getAnalyzerInstance()创建分析器组件,使用单例设计模式、建造者设计模式和责任链设计模式,使用抽象类 AbstractAnalyzer 按照顺序添加SQL分析器,同样每个分析器都被拦截器链包裹的,也就是说这里添加的都是SQL分析器代理对象。 image.png

分析器接口 image.png

分析器抽象类 image.png 由上图可知,分析器抽象类内部类 Builder 有个 addAnalyzer 方法,该方法就是设置责任链关系,维护下一个分析器。

SortAnalyzer 为例,具体分析器实现 image.png

下个版本

这个工具我会一直维护,功能也会一直去叠加,下个版本想支持动态SQL,类似 MyBatis 的动态标签,比如:"<if>"、"<where>" 等标签。