> 文档中心 > 【mybatis plus源码解析】(二)详解SQL注入器底层原理,mybatis plus是如何实现自动注入CRUD操作

【mybatis plus源码解析】(二)详解SQL注入器底层原理,mybatis plus是如何实现自动注入CRUD操作


系列文章目录

【mybatis plus源码解析】mybatis plus执行原理
【mybatis plus源码解析】(二)详解自定义SQL注入器底层原理

文章目录

  • 系列文章目录
  • 前言
  • 一、ISqlInjector SQL自动注入器接口的相关uml图
    • ISqlInjector顶级接口,只做一件事
    • 再来看看AbstractSqlInjector抽象类
    • DefaultSqlInjector默认SQL 注入器
    • 默认的注入器类治理介绍完了,里面其实做了一件事就是调用注入mapper默认实现的CRUD方法
  • 二、注入方法相关的类
    • 先看看UML图
    • AbstractMethod抽象方法类
    • 简单看个SelectById类,以这个方法为例子
    • 了解完上面的类,现在回头看看初始化时注入方法,不了解的可以先看我的上篇文章。
  • 总结

前言

上篇文章介绍了【mybatis plus源码解析】mybatis plus执行原理,这回接着上篇文章,建议看完上篇文章再来看这篇,这里主要介绍相关类

一、ISqlInjector SQL自动注入器接口的相关uml图

uml图

ISqlInjector顶级接口,只做一件事

在这里插入图片描述

再来看看AbstractSqlInjector抽象类

AbstractSqlInjector类实现了inspectInject注入方法

/ * SQL 自动注入器 * * @author hubin * @since 2018-04-07 */public abstract class AbstractSqlInjector implements ISqlInjector {    protected final Log logger = LogFactory.getLog(this.getClass());    @Override    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {    //通过反射获取实体类对象 Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0); if (modelClass != null) {     String className = mapperClass.toString();     //获取mapperRegistry缓存用于对比,防止覆盖     Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());     if (!mapperRegistryCache.contains(className)) {     //通过实体类对象反射方式获取信息(比如实体类上的注解)封装成TableInfo存储表信息对象  TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);  List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo);//获取要注入的方法集合  if (CollectionUtils.isNotEmpty(methodList)) {      // 循环注入自定义方法      methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));  } else {      logger.debug(mapperClass.toString() + ", No effective injection method was found.");  }  mapperRegistryCache.add(className);     } }    }    /     * 

* 获取 注入的方法 *

* * @param mapperClass 当前mapper * @return 注入的方法集合 * @since 3.1.2 add mapperClass */
public abstract List<AbstractMethod> getMethodList(Class<?> mapperClass,TableInfo tableInfo);}

DefaultSqlInjector默认SQL 注入器

DefaultSqlInjector实现了AbstractSqlInjector类的getMethodList方法,规定哪些方法可以实现自动注入

/ * SQL 默认注入器 * * @author hubin * @since 2018-04-10 */public class DefaultSqlInjector extends AbstractSqlInjector {    @Override    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) { Stream.Builder<AbstractMethod> builder = Stream.<AbstractMethod>builder()     .add(new Insert())     .add(new Delete())     .add(new DeleteByMap())     .add(new Update())     .add(new SelectByMap())     .add(new SelectCount())     .add(new SelectMaps())     .add(new SelectMapsPage())     .add(new SelectObjs())     .add(new SelectList())     .add(new SelectPage()); if (tableInfo.havePK()) {     builder.add(new DeleteById())  .add(new DeleteBatchByIds())  .add(new UpdateById())  .add(new SelectById())  .add(new SelectBatchByIds()); } else {     logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.",  tableInfo.getEntityType())); } return builder.build().collect(toList());    }}

默认的注入器类治理介绍完了,里面其实做了一件事就是调用注入mapper默认实现的CRUD方法

二、注入方法相关的类

先看看UML图

在这里插入图片描述

AbstractMethod抽象方法类

/ * 抽象的注入方法类 * * @author hubin * @since 2018-04-06 */@SuppressWarnings("serial")public abstract class AbstractMethod implements Constants {    protected static final Log logger = LogFactory.getLog(AbstractMethod.class);    protected Configuration configuration;    protected LanguageDriver languageDriver;    protected MapperBuilderAssistant builderAssistant;    /     * 方法名称     * @since 3.5.0     */    protected final String methodName;    /     * @see AbstractMethod#AbstractMethod(java.lang.String)     * @since 3.5.0     */    @Deprecated    public AbstractMethod() { methodName = null;    }    /     * @param methodName 方法名     * @since 3.5.0     */    protected AbstractMethod(String methodName) { Assert.notNull(methodName, "方法名不能为空"); this.methodName = methodName;    }    /     * 注入自定义方法     */    public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) { this.configuration = builderAssistant.getConfiguration(); this.builderAssistant = builderAssistant; this.languageDriver = configuration.getDefaultScriptingLanguageInstance(); /* 注入自定义方法 */ injectMappedStatement(mapperClass, modelClass, tableInfo);    }    /     * 是否已经存在MappedStatement     *     * @param mappedStatement MappedStatement     * @return true or false     */    private boolean hasMappedStatement(String mappedStatement) { return configuration.hasStatement(mappedStatement, false);    }    /     * SQL 更新 set 语句     *     * @param table 表信息     * @return sql set 片段     */    protected String sqlLogicSet(TableInfo table) { return "SET " + table.getLogicDeleteSql(false, false);    }    /     * SQL 更新 set 语句     *     * @param logic  是否逻辑删除注入器     * @param ew     是否存在 UpdateWrapper 条件     * @param table  表信息     * @param alias  别名     * @param prefix 前缀     * @return sql     */    protected String sqlSet(boolean logic, boolean ew, TableInfo table, boolean judgeAliasNull, final String alias,final String prefix) { String sqlScript = table.getAllSqlSet(logic, prefix); if (judgeAliasNull) {     sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", alias), true); } if (ew) {     sqlScript += NEWLINE;     sqlScript += convertIfEwParam(U_WRAPPER_SQL_SET, false); } sqlScript = SqlScriptUtils.convertSet(sqlScript); return sqlScript;    }    /     * SQL 注释     *     * @return sql     */    protected String sqlComment() { return convertIfEwParam(Q_WRAPPER_SQL_COMMENT, true);    }    /     * SQL 注释     *     * @return sql     */    protected String sqlFirst() { return convertIfEwParam(Q_WRAPPER_SQL_FIRST, true);    }    protected String convertIfEwParam(final String param, final boolean newLine) { return SqlScriptUtils.convertIf(SqlScriptUtils.unSafeParam(param),     String.format("%s != null and %s != null", WRAPPER, param), newLine);    }    /     * SQL 查询所有表字段     *     * @param table 表信息     * @param queryWrapper 是否为使用 queryWrapper 查询     * @return sql 脚本     */    protected String sqlSelectColumns(TableInfo table, boolean queryWrapper) { /* 假设存在用户自定义的 resultMap 映射返回 */ String selectColumns = ASTERISK; if (table.getResultMap() == null || table.isAutoInitResultMap()) {     /* 未设置 resultMap 或者 resultMap 是自动构建的,视为属于mp的规则范围内 */     selectColumns = table.getAllSqlSelect(); } if (!queryWrapper) {     return selectColumns; } return convertChooseEwSelect(selectColumns);    }    /     * SQL 查询记录行数     *     * @return count sql 脚本     */    protected String sqlCount() { return convertChooseEwSelect(ASTERISK);    }    /     * SQL 设置selectObj sql select     *     * @param table 表信息     */    protected String sqlSelectObjsColumns(TableInfo table) { return convertChooseEwSelect(table.getAllSqlSelect());    }    protected String convertChooseEwSelect(final String otherwise) { return SqlScriptUtils.convertChoose(String.format("%s != null and %s != null", WRAPPER, Q_WRAPPER_SQL_SELECT),     SqlScriptUtils.unSafeParam(Q_WRAPPER_SQL_SELECT), otherwise);    }    /     * SQL map 查询条件     */    protected String sqlWhereByMap(TableInfo table) { if (table.isWithLogicDelete()) {     // 逻辑删除     String sqlScript = SqlScriptUtils.convertChoose("v == null", " ${k} IS NULL ",  " ${k} = #{v} ");     sqlScript = SqlScriptUtils.convertForeach(sqlScript, COLUMN_MAP, "k", "v", "AND");     sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null and !%s.isEmpty", COLUMN_MAP, COLUMN_MAP), true);     sqlScript += (NEWLINE + table.getLogicDeleteSql(true, true));     sqlScript = SqlScriptUtils.convertWhere(sqlScript);     return sqlScript; } else {     String sqlScript = SqlScriptUtils.convertChoose("v == null", " ${k} IS NULL ",  " ${k} = #{v} ");     sqlScript = SqlScriptUtils.convertForeach(sqlScript, COLUMN_MAP, "k", "v", "AND");     sqlScript = SqlScriptUtils.convertWhere(sqlScript);     sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null and !%s", COLUMN_MAP,  COLUMN_MAP_IS_EMPTY), true);     return sqlScript; }    }    /     * EntityWrapper方式获取select where     *     * @param newLine 是否提到下一行     * @param table   表信息     * @return String     */    protected String sqlWhereEntityWrapper(boolean newLine, TableInfo table) { if (table.isWithLogicDelete()) {     String sqlScript = table.getAllSqlWhere(true, true, WRAPPER_ENTITY_DOT);     sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER_ENTITY),  true);     sqlScript += (NEWLINE + table.getLogicDeleteSql(true, true) + NEWLINE);     String normalSqlScript = SqlScriptUtils.convertIf(String.format("AND ${%s}", WRAPPER_SQLSEGMENT),  String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,      WRAPPER_NONEMPTYOFNORMAL), true);     normalSqlScript += NEWLINE;     normalSqlScript += SqlScriptUtils.convertIf(String.format(" ${%s}", WRAPPER_SQLSEGMENT),  String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,      WRAPPER_EMPTYOFNORMAL), true);     sqlScript += normalSqlScript;     sqlScript = SqlScriptUtils.convertChoose(String.format("%s != null", WRAPPER), sqlScript,  table.getLogicDeleteSql(false, true));     sqlScript = SqlScriptUtils.convertWhere(sqlScript);     return newLine ? NEWLINE + sqlScript : sqlScript; } else {     String sqlScript = table.getAllSqlWhere(false, true, WRAPPER_ENTITY_DOT);     sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER_ENTITY), true);     sqlScript += NEWLINE;     sqlScript += SqlScriptUtils.convertIf(String.format(SqlScriptUtils.convertIf(" AND", String.format("%s and %s", WRAPPER_NONEMPTYOFENTITY, WRAPPER_NONEMPTYOFNORMAL), false) + " ${%s}", WRAPPER_SQLSEGMENT),  String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,      WRAPPER_NONEMPTYOFWHERE), true);     sqlScript = SqlScriptUtils.convertWhere(sqlScript) + NEWLINE;     sqlScript += SqlScriptUtils.convertIf(String.format(" ${%s}", WRAPPER_SQLSEGMENT),  String.format("%s != null and %s != '' and %s", WRAPPER_SQLSEGMENT, WRAPPER_SQLSEGMENT,      WRAPPER_EMPTYOFWHERE), true);     sqlScript = SqlScriptUtils.convertIf(sqlScript, String.format("%s != null", WRAPPER), true);     return newLine ? NEWLINE + sqlScript : sqlScript; }    }    protected String sqlOrderBy(TableInfo tableInfo) { /* 不存在排序字段,直接返回空 */ List<TableFieldInfo> orderByFields = tableInfo.getOrderByFields(); if (CollectionUtils.isEmpty(orderByFields)) {     return StringPool.EMPTY; } orderByFields.sort(Comparator.comparingInt(TableFieldInfo::getOrderBySort)); StringBuilder sql = new StringBuilder(); sql.append(NEWLINE).append(" ORDER BY "); sql.append(orderByFields.stream().map(tfi -> String.format("%s %s", tfi.getColumn(),     tfi.getOrderByType())).collect(joining(","))); /* 当wrapper中传递了orderBy属性,@orderBy注解失效 */ return SqlScriptUtils.convertIf(sql.toString(), String.format("%s == null or %s", WRAPPER,     WRAPPER_EXPRESSION_ORDER), true);    }    /     * 过滤 TableFieldInfo 集合, join 成字符串     */    protected String filterTableFieldInfo(List<TableFieldInfo> fieldList, Predicate<TableFieldInfo> predicate,Function<TableFieldInfo, String> function, String joiningVal) { Stream<TableFieldInfo> infoStream = fieldList.stream(); if (predicate != null) {     return infoStream.filter(predicate).map(function).collect(joining(joiningVal)); } return infoStream.map(function).collect(joining(joiningVal));    }    /     * 获取乐观锁相关     *     * @param tableInfo 表信息     * @return String     */    protected String optlockVersion(TableInfo tableInfo) { if (tableInfo.isWithVersion()) {     return tableInfo.getVersionFieldInfo().getVersionOli(ENTITY, ENTITY_DOT); } return EMPTY;    }    /     * 查询     */    protected MappedStatement addSelectMappedStatementForTable(Class<?> mapperClass, String id, SqlSource sqlSource,TableInfo table) { String resultMap = table.getResultMap(); if (null != resultMap) {     /* 返回 resultMap 映射结果集 */     return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null,  resultMap, null, NoKeyGenerator.INSTANCE, null, null); } else {     /* 普通查询 */     return addSelectMappedStatementForOther(mapperClass, id, sqlSource, table.getEntityType()); }    }    /     * 查询     * @since 3.5.0     */    protected MappedStatement addSelectMappedStatementForTable(Class<?> mapperClass, SqlSource sqlSource, TableInfo table) { return addSelectMappedStatementForTable(mapperClass, this.methodName, sqlSource, table);    }    /     * 查询     */    protected MappedStatement addSelectMappedStatementForOther(Class<?> mapperClass, String id, SqlSource sqlSource,Class<?> resultType) { return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.SELECT, null,     null, resultType, NoKeyGenerator.INSTANCE, null, null);    }    /     * 查询     *     * @since 3.5.0     */    protected MappedStatement addSelectMappedStatementForOther(Class<?> mapperClass, SqlSource sqlSource, Class<?> resultType) { return addSelectMappedStatementForOther(mapperClass, this.methodName, sqlSource, resultType);    }    /     * 插入     */    protected MappedStatement addInsertMappedStatement(Class<?> mapperClass, Class<?> parameterType, String id,      SqlSource sqlSource, KeyGenerator keyGenerator,      String keyProperty, String keyColumn) { return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.INSERT, parameterType, null,     Integer.class, keyGenerator, keyProperty, keyColumn);    }    /     * 插入     * @since 3.5.0     */    protected MappedStatement addInsertMappedStatement(Class<?> mapperClass, Class<?> parameterType,      SqlSource sqlSource, KeyGenerator keyGenerator,      String keyProperty, String keyColumn) { return addInsertMappedStatement(mapperClass, parameterType, this.methodName, sqlSource, keyGenerator, keyProperty, keyColumn);    }    /     * 删除     */    protected MappedStatement addDeleteMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource) { return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.DELETE, null,     null, Integer.class, NoKeyGenerator.INSTANCE, null, null);    }    /     * @since 3.5.0     */    protected MappedStatement addDeleteMappedStatement(Class<?> mapperClass, SqlSource sqlSource) { return addDeleteMappedStatement(mapperClass, this.methodName, sqlSource);    }    /     * 更新     */    protected MappedStatement addUpdateMappedStatement(Class<?> mapperClass, Class<?> parameterType, String id,      SqlSource sqlSource) { return addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.UPDATE, parameterType, null,     Integer.class, NoKeyGenerator.INSTANCE, null, null);    }    /     * 更新     *     * @since 3.5.0     */    protected MappedStatement addUpdateMappedStatement(Class<?> mapperClass, Class<?> parameterType,      SqlSource sqlSource) { return addUpdateMappedStatement(mapperClass, parameterType, this.methodName, sqlSource);    }    /     * 添加 MappedStatement 到 Mybatis 容器     */    protected MappedStatement addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource,SqlCommandType sqlCommandType, Class<?> parameterType,String resultMap, Class<?> resultType, KeyGenerator keyGenerator,String keyProperty, String keyColumn) { String statementName = mapperClass.getName() + DOT + id; if (hasMappedStatement(statementName)) {     logger.warn(LEFT_SQ_BRACKET + statementName + "] Has been loaded by XML or SqlProvider or Mybatis's Annotation, so ignoring this injection for [" + getClass() + RIGHT_SQ_BRACKET);     return null; } /* 缓存逻辑处理 */ boolean isSelect = sqlCommandType == SqlCommandType.SELECT; return builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType,     null, null, null, parameterType, resultMap, resultType,     null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn,     configuration.getDatabaseId(), languageDriver, null);    }    /     * @since 3.5.0     */    protected MappedStatement addMappedStatement(Class<?> mapperClass, SqlSource sqlSource,SqlCommandType sqlCommandType, Class<?> parameterType,String resultMap, Class<?> resultType, KeyGenerator keyGenerator,String keyProperty, String keyColumn) { return addMappedStatement(mapperClass, this.methodName, sqlSource, sqlCommandType, parameterType, resultMap, resultType, keyGenerator, keyProperty, keyColumn);    }    /     * 注入自定义 MappedStatement     *     * @param mapperClass mapper 接口     * @param modelClass  mapper 泛型     * @param tableInfo   数据库表反射信息     * @return MappedStatement     */    public abstract MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo);    /     * 获取自定义方法名,未设置采用默认方法名     * https://gitee.com/baomidou/mybatis-plus/pulls/88     *     * @return method     * @author 义陆无忧     * @see AbstractMethod#AbstractMethod(java.lang.String)     * @deprecated 3.5.0     */    @Deprecated    public String getMethod(SqlMethod sqlMethod) { return StringUtils.isBlank(methodName) ? sqlMethod.getMethod() : this.methodName;    }}

简单看个SelectById类,以这个方法为例子

/ * 根据ID 查询一条数据 * * @author hubin * @since 2018-04-06 */public class SelectById extends AbstractMethod {    public SelectById() {    //给methodName属性赋值 super(SqlMethod.SELECT_BY_ID.getMethod());    }    /     * @param name 方法名     * @since 3.5.0     */    public SelectById(String name) {    //给methodName属性赋值 super(name);    }    @Override    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {    //获取SqlMethod类,这是一个枚举类,下面会有介绍 SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID; //生成最终的SqlSource对象 SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(),  sqlSelectColumns(tableInfo, false),  tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),  tableInfo.getLogicDeleteSql(true, true)), Object.class);  //将最终封装好的MappedStatement对象加入配置类对象中 return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);    }}

在这里插入图片描述

断点看看
在这里插入图片描述
可以看到sqlMethod枚举类封装了相关sql语句,短点往下
在这里插入图片描述
最终生成了封装好sql语句和参数的SqlSource对象,这里生成的是StaticSqlSource静态对象

了解完上面的类,现在回头看看初始化时注入方法,不了解的可以先看我的上篇文章。

在这里插入图片描述
来看看parserInjector这个方法
【mybatis plus源码解析】(二)详解SQL注入器底层原理,mybatis plus是如何实现自动注入CRUD操作
GlobalConfigUtils.getSqlInjector(configuration)这句代码其实就是返回配置文件中设置的SqlInjector注入器,说明我们可以自定义注入器,默认注入器是DefaultSqlInjector。官方文档也正好证明这一点
在这里插入图片描述
【mybatis plus源码解析】(二)详解SQL注入器底层原理,mybatis plus是如何实现自动注入CRUD操作
最终这个方法调用了AbstractSqlInjector的inspectInject方法将CRUD相关的sql对象注入到配置中去

总结

到这里基本就介绍完了。到这里其实就能想到可以通过自定义BaseMapper,并且编写响应method对象,自定义实现ISqlInjector注入器。这样我们就能够对mybatis-plus进行扩展来实现更多的CRUD方法。后面文章我会出一片详细的