乐趣区

关于java:Mybatis-Plus是如何实现动态-SQL-语句的原理你懂吗

Mybatis-Plus(简称 MP)是一个 Mybatis 的加强工具, 那么它是怎么加强的呢?其实就是它曾经封装好了一些 crud 办法,开发就不须要再写 xml 了,间接调用这些办法就行,就相似于 JPA。那么这篇文章就来浏览以下 MP 的具体实现,看看是怎么实现这些加强的。

入口类:MybatisSqlSessionFactoryBuilder

通过在入口类 MybatisSqlSessionFactoryBuilder#build 办法中, 在利用启动时, 将 mybatis plus(简称 MP) 自定义的动静配置 xml 文件注入到 Mybatis 中。

public class MybatisSqlSessionFactoryBuilder extends SqlSessionFactoryBuilder {public SqlSessionFactory build(Configuration configuration) {
            // ... 省略若干行 
            if (globalConfig.isEnableSqlRunner()) {new SqlRunnerInjector().inject(configuration);
            }
            // ... 省略若干行 
            return sqlSessionFactory;
        }
}

这里波及到 2 个 MP2 个性能类

  • 扩大继承自 Mybatis 的 MybatisConfiguration 类: MP 动静脚本构建,注册,及其它逻辑判断。
  • SqlRunnerInjector: MP 默认插入一些动静办法的 xml 脚本办法。

MybatisConfiguration 类

这里咱们重点分析 MybatisConfiguration 类,在 MybatisConfiguration 中,MP 初始化了其本身的 MybatisMapperRegistry,而 MybatisMapperRegistry 是 MP 加载自定义的 SQL 办法的注册器。

MybatisConfiguration 中很多办法是应用 MybatisMapperRegistry 进行重写实现

其中有 3 个重载办法 addMapper 实现了注册 MP 动静脚本的性能。

public class MybatisConfiguration extends Configuration {
    /**
     * Mapper 注册
     */
    protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
    // ....

    /**
     * 初始化调用
     */
    public MybatisConfiguration() {super();
        this.mapUnderscoreToCamelCase = true;
        languageRegistry.setDefaultDriverClass(MybatisXMLLanguageDriver.class);
    }

    /**
     * MybatisPlus 加载 SQL 程序:* <p> 1、加载 XML 中的 SQL </p>
     * <p> 2、加载 SqlProvider 中的 SQL </p>
     * <p> 3、XmlSql 与 SqlProvider 不能蕴含雷同的 SQL </p>
     * <p> 调整后的 SQL 优先级:XmlSql > sqlProvider > CurdSql </p>
     */
    @Override
    public void addMappedStatement(MappedStatement ms) {// ...}
    
    // ... 省略若干行 
    /**
     * 应用本人的 MybatisMapperRegistry
     */
    @Override
    public <T> void addMapper(Class<T> type) {mybatisMapperRegistry.addMapper(type);
    }
    // .... 省略若干行 
}

在 MybatisMapperRegistry 中,MP 将 mybatis 的 MapperAnnotationBuilder 替换为 MP 本人的 MybatisMapperAnnotationBuilder

public class MybatisMapperRegistry extends MapperRegistry {
    @Override
    public <T> void addMapper(Class<T> type) {
        // ... 省略若干行 
        MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
        parser.parse();
        // ... 省略若干行 
    }
}

在 MybatisMapperRegistry 类的 addMapper 办法中,真正进入到 MP 的外围类 MybatisMapperAnnotationBuilder,MybatisMapperAnnotationBuilder 这个类是 MP 实现动静脚本的要害类。

MybatisMapperAnnotationBuilder 动静结构

在 MP 的外围类 MybatisMapperAnnotationBuilder 的 parser 办法中,MP 逐个遍历要加载的 Mapper 类,加载的办法包含上面几个

public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
    @Override
    public void parse() {
        //... 省略若干行 
        for (Method method : type.getMethods()) {
            /** for 循环代码, MP 判断 method 办法是否是 @Select @Insert 等 mybatis 注解办法 **/
            parseStatement(method);
            InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);
            SqlParserHelper.initSqlParserInfoCache(mapperName, method);
        }
        /** 这 2 行代码, MP 注入默认的办法列表 **/
        if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
        }
        //... 省略若干行 
    }

    @Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {Class<?> modelClass = extractModelClass(mapperClass);
        //... 省略若干行 
        List<AbstractMethod> methodList = this.getMethodList(mapperClass);
        TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
        // 循环注入自定义办法
        methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
        mapperRegistryCache.add(className);
    }
}
public class DefaultSqlInjector extends AbstractSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        return Stream.of(new Insert(),
            //... 省略若干行 
            new SelectPage()).collect(toList());
    }
}

在 MybatisMapperAnnotationBuilder 中,MP 真正将框架自定义的动静 SQL 语句注册到 Mybatis 引擎中。而 AbstractMethod 则履行了具体方法的 SQL 语句结构。

具体的 AbstractMethod 实例类,结构具体的办法 SQL 语句

以 SelectById 这个类为例阐明下

/**
 * 依据 ID 查问一条数据
 */
public class SelectById extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        /** 定义 mybatis xml method id, 对应 <id="xyz"> **/
        SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
        /** 结构 id 对应的具体 xml 片段 **/
        SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(),
            sqlSelectColumns(tableInfo, false),
            tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
            tableInfo.getLogicDeleteSql(true, true)), Object.class);
        /** 将 xml method 办法增加到 mybatis 的 MappedStatement 中 **/
        return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
    }
}

至此,MP 实现了在启动时加载自定义的办法 xml 配置的过程,前面的就是 mybatis ${变量} #{变量} 的动静替换和预编译,曾经进入 mybatis 自有性能。

总结一下

MP 总共改写和替换了 mybatis 的十多个类,次要如下图所示:

总体上来说,MP 实现 mybatis 的加强,伎俩略显繁琐和不够直观,其实依据 MybatisMapperAnnotationBuilder 结构出自定义方法的 xml 文件,将其转换为 mybatis 的 Resource 资源,能够只继承重写一个 Mybatis 类:SqlSessionFactoryBean 比方如下:

public class YourSqlSessionFactoryBean extends SqlSessionFactoryBean implements ApplicationContextAware {private Resource[] mapperLocations;

    @Override
    public void setMapperLocations(Resource... mapperLocations) {super.setMapperLocations(mapperLocations);
        /** 暂存应用 mybatis 原生定义的 mapper xml 文件门路 **/
        this.mapperLocations = mapperLocations;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void afterPropertiesSet() throws Exception {ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        /** 只须要通过将自定义的办法结构成 xml resource 和原生定义的 Resource 一起注入到 mybatis 中即可, 这样就能够实现 MP 的自定义动静 SQL 和原生 SQL 的共生关系 **/
        this.setMapperLocations(InjectMapper.getMapperResource(this.dbType, beanFactory, this.mapperLocations));
        super.afterPropertiesSet();}
}

在这边文章中,简略介绍了 MP 实现动静语句的实现过程,并且给出一个可能的更便捷办法。

退出移动版