mybatis源码之集成mybatis-plus源码

2023-05-16

本文将结合源码介绍mybatis-plus的原理,包括:

  • BaseMapper API
  • MybatisSqlSessionFactoryBean类
  • BaseMapper API Statement解析
  • Wrapper查询构建原理

系列文档:

  • mybatis源码之创建SqlSessionFactory代码分析
  • mybatis源码之创建SqlSessionFactory代码分析 - mapper xml解析
  • mybatis源码之执行查询SQL代码分析
  • mybatis源码之执行insert代码分析
  • mybatis源码之mapper接口扫描原理分析
  • mybatis源码之集成spring原理
  • mybatis源码之集成springboot原理
  • mybatis源码之集成mybatis-plus源码
  • mybatis源码之mybatis-plus执行查询(基础篇完结)
  • MybatisPlusAutoConfiguration源码分析
  • Autowired注入Service变成了biaomidou的Mapper代理

mybatis-plus

MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。

  • 无侵入:只做增强不做改变,引入它不会对现有工程产生影响
  • 损耗小:启动即会自动注入基本CURD,性能基本无损耗,直接面向对象操作
  • 强大的CRUD操作:内置通用Mapper、通用Service,仅仅通过少量配置即可实现单表大部分CRUD操作,更有强大的条件构造器,满足各类使用需求
  • 支持Lambda形式调用:通过Lambda表达式,方便的编写各类查询条件,无需再担心字段写错
  • 支持主键自动生成:支持多达4种主键策略(内含分布式唯一ID生成器 - Sequence),可自由配置,完美解决主键问题
  • 支持ActiveRecord模式:支持ActiveRecord形式调用,实体类只需继承Model类即可进行强大的CRUD操作
  • 支持自定义全局通用操作:支持全局通用方法注入(Write once, use anywhere)
  • 内置代码生成器:采用代码或者Maven插件可快速生成Mapper、Model、Service、Controller层代码,支持模板引擎,更有超多自定义配置等您来使用
  • 内置分页插件:基于MyBatis物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通List查询
  • 分页插件支持多种数据库:支持MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer等多种数据库
  • 内置性能分析插件:可输出SQL语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
  • 内置全局拦截插件:提供全表delete、update操作智能分析阻断,也可自定义拦截规则,预防误操作

源码分析

BaseMapper API

mybatis-plus会自动为BaseMapper API创建MappedStatement,后续我们会分析这个过程:

public interface BaseMapper<T> extends Mapper<T> {

    int insert(T entity);

    int deleteById(Serializable id);

    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    int delete(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    int updateById(@Param(Constants.ENTITY) T entity);

    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    T selectById(Serializable id);

    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    <E extends IPage<Map<String, Object>>> E selectMapsPage(
        E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

MybatisSqlSessionFactoryBean类

这个类也实现了FactoryBean接口,最终也会创建一个DefaultSqlSessionFactory对象,基本上和SqlSessionFactoryBean的作用一致,只是在中间加入了mybatis-plus的Statement注入等逻辑。

另外还有一些mybatis-plus的自定义配置参数,我们平时能够使用的只有GlobalConfig和DbConfig这两个类。

MybatisConfiguration类

这个类由mybatis-plus提供,继承了mybatis的Configuration类,用于封装mybatis和mybatis-plus的核心配置参数。

与本文相关的内容暂时只有这个:

protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);

在分析mybatis getMapper(Class)原理时,我们了解到mybatis是使用MapperRegistry.getMapper(Class)方法实现的Mapper接口扫描和创建代理。而在mybatis-plus中,使用的是MybatisMapperRegistry类,这个类由mybatis-plus提供,继承了MapperRegistry类,重写了getMapper(Class)和addMapper(Class)等核心方法,在这些方法中实现了mybatis-plus BaseMapper API Statement的解析和注入。后续有详细分析。

GlobalConfig类

封装全局配置信息:

public class GlobalConfig implements Serializable {
    // 是否开启LOGO
    private boolean banner = true;
    // 是否初始化SqlRunner
    private boolean enableSqlRunner = false;
    // 数据库相关配置
    private DbConfig dbConfig;
    // SQL注入器
    private ISqlInjector sqlInjector = new DefaultSqlInjector();
    // Mapper父类
    private Class<?> superMapperClass = Mapper.class;
    // 仅用于缓存SqlSessionFactory
    private SqlSessionFactory sqlSessionFactory;
    // 缓存已注入CRUD的Mapper信息
    private Set<String> mapperRegistryCache = new ConcurrentSkipListSet<>();
    // 元对象字段填充控制器
    private MetaObjectHandler metaObjectHandler;
    // 主键生成器
    private IdentifierGenerator identifierGenerator;
}

DbConfig类

封装DB通用配置:

public static class DbConfig {
    // 主键类型
    private IdType idType = IdType.ASSIGN_ID;
    // 表名前缀
    private String tablePrefix;
    // schema
    private String schema;
    //
    private String columnFormat;
    //
    private String propertyFormat;
    //
    private boolean replacePlaceholder;
    // 转义符
    private String escapeSymbol;
    // 表名是否使用驼峰转下划线命名,只对表名生效
    private boolean tableUnderline = true;
    // 大写命名,对表名和字段名均生效
    private boolean capitalMode = false;
    // 表主键生成器
    private IKeyGenerator keyGenerator;
    // 逻辑删除全局属性名
    private String logicDeleteField;
    // 逻辑删除全局值,默认1表示已删除
    private String logicDeleteValue = "1";
    // 逻辑未删除全局值,默认0表示未删除
    private String logicNotDeleteValue = "0";
    // 字段insert验证策略
    private FieldStrategy insertStrategy = FieldStrategy.NOT_NULL;
    // 字段update验证策略
    private FieldStrategy updateStrategy = FieldStrategy.NOT_NULL;
    // 字段select验证策略
    private FieldStrategy selectStrategy = FieldStrategy.NOT_NULL;
}

这个类里面的insertStrategy、updateStrategy、selectStrategy后续深入分析时再做分析。

BaseMapper API Statement解析

代码入口

入口在**com.baomidou.mybatisplus.core.MybatisConfiguration.getMapper(Class, SqlSession)**方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mybatisMapperRegistry.getMapper(type, sqlSession);
}

这里的mybatisMapperRegistry就是上文介绍过的MybatisMapperRegistry类对象。

MybatisMapperRegistry的getMapper(Class)方法

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    // 这里换成MybatisMapperProxyFactory而不是MapperProxyFactory
    final MybatisMapperProxyFactory<T> mapperProxyFactory = knownMappers.get(type);
    if (mapperProxyFactory == null) {
        throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry.");
    }
    try {
        return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
        throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
}

MybatisMapperRegistry的addMapper(Class)方法

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
        if (hasMapper(type)) {
            return;
        }
        boolean loadCompleted = false;
        try {
            // 这里也换成MybatisMapperProxyFactory而不是MapperProxyFactory
            knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
            // 这里也换成MybatisMapperAnnotationBuilder而不是MapperAnnotationBuilder
            MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
            // 这里有mybatis-plus的Statement解析、注入逻辑
            parser.parse();
            loadCompleted = true;
        } finally {
            if (!loadCompleted) {
                knownMappers.remove(type);
            }
        }
    }
}

MybatisMapperAnnotationBuilder的parse()方法

public void parse() {
    String resource = type.toString();
    if (!configuration.isResourceLoaded(resource)) {
        loadXmlResource();
        configuration.addLoadedResource(resource);
        String mapperName = type.getName();
        assistant.setCurrentNamespace(mapperName);
        parseCache();
        parseCacheRef();
        // 从以下开始加入了mybatis-plus的核心逻辑
        InterceptorIgnoreHelper.InterceptorIgnoreCache cache =
            InterceptorIgnoreHelper.initSqlParserInfoCache(type);
        for (Method method : type.getMethods()) {
            if (!canHaveStatement(method)) {
                continue;
            }
            if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
                && method.getAnnotation(ResultMap.class) == null) {
                parseResultMap(method);
            }
            try {
                // 加入注解过滤缓存
                InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);
                SqlParserHelper.initSqlParserInfoCache(mapperName, method);
                parseStatement(method);
            } catch (IncompleteElementException e) {
                // 使用MybatisMethodResolver而不是MethodResolver
                configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
            }
        }
        // 注入CURD动态SQL,放在最后,可能会有人会用注解重写sql
        try {
            if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
                parserInjector();
            }
        } catch (IncompleteElementException e) {
            configuration.addIncompleteMethod(new InjectorResolver(this));
        }
    }
    parsePendingMethods();
}

parserInjector()方法

由于mybatis-plus注入CRUD SQL的核心逻辑在这个方法,我们重点看一下这个方法:

void parserInjector() {
  GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}

这里获取到的是DefaultSqlInjector对象。前文分析过。

inspectInject方法:

public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
    // 解析model类型,比如Blog、User类型
    Class<?> modelClass = extractModelClass(mapperClass);
    if (modelClass != null) {
        String className = mapperClass.toString();
        Set<String> mapperRegistryCache =
            GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
        // 未解析过才做model解析
        if (!mapperRegistryCache.contains(className)) {
            // 待解析的方法
            List<AbstractMethod> methodList = this.getMethodList(mapperClass);
            if (CollectionUtils.isNotEmpty(methodList)) {
                // 通过model解析数据库表信息
                TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
                // 循环注入自定义方法
                methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
            } else {
                logger.debug(mapperClass.toString() + ", No effective injection method was found.");
            }
            // 加入到缓存
            mapperRegistryCache.add(className);
        }
    }
}

public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
    return Stream.of(
        new Insert(), new Delete(), new DeleteByMap(), new DeleteById(),
        new DeleteBatchByIds(), new Update(), new UpdateById(), new SelectById(),
        new SelectBatchByIds(), new SelectByMap(), new SelectOne(), new SelectCount(),
        new SelectMaps(), new SelectMapsPage(), new SelectObjs(), new SelectList(), new SelectPage()
    ).collect(toList());
}

TableInfo类

TableInfo类,封装从model解析出来的数据库表信息,这个类还是比较简单的:

public class TableInfo implements Constants {

    // 实体类型
    private Class<?> entityType;
    // 表主键ID生成类型,不展开分析
    private IdType idType = IdType.NONE;
    // 表名称
    private String tableName;
    // 表映射结果集
    private String resultMap;
    // 是否是需要自动生成的resultMap
    private boolean autoInitResultMap;
    // 主键是否有存在字段名与属性名关联,true表示要进行as
    private boolean keyRelated;
    // 表主键ID字段名
    private String keyColumn;
    // 表主键ID属性名
    private String keyProperty;
    // 表主键ID属性类型
    private Class<?> keyType;
    // 表主键ID Sequence
    private KeySequence keySequence;
    // 表字段信息列表
    private List<TableFieldInfo> fieldList;
    // 命名空间,对应的mapper接口的全类名
    private String currentNamespace;
    // MybatisConfiguration引用
    private Configuration configuration;
    // 是否开启下划线转驼峰
    private boolean underCamel;
    // 缓存包含主键及字段的sql select
    private String allSqlSelect;
    // 缓存主键字段的sql select
    private String sqlSelect;
    // 表字段是否启用了插入填充
    private boolean withInsertFill;
    // 表字段是否启用了更新填充
    private boolean withUpdateFill;
    // 表字段是否启用了逻辑删除
    private boolean withLogicDelete;
    // 逻辑删除字段
    private TableFieldInfo logicDeleteFieldInfo;
    // 表字段是否启用了乐观锁
    private boolean withVersion;
    // 乐观锁字段
    private TableFieldInfo versionFieldInfo;

TableFieldInfo类,封装表字段信息:

public class TableFieldInfo implements Constants {

    // 属性的引用
    private final Field field;
    // 字段名
    private final String column;
    // 属性名
    private final String property;
    // 属性表达式#{property}, 可以指定jdbcType, typeHandler等
    private final String el;
    // 属性类型
    private final Class<?> propertyType;
    // 是否是基本数据类型
    private final boolean isPrimitive;
    // 属性是否是CharSequence类型
    private final boolean isCharSequence;
    // 字段insert验证策略
    private final FieldStrategy insertStrategy;
    // 字段update验证策略
    private final FieldStrategy updateStrategy;
    // 字段where验证策略
    private final FieldStrategy whereStrategy;
    // 是否是乐观锁字段
    private final boolean version;
    // 是否进行select查询
    private boolean select = true;
    // 是否是逻辑删除字段
    private boolean logicDelete = false;
    // 逻辑删除值
    private String logicDeleteValue;
    // 逻辑未删除值
    private String logicNotDeleteValue;
    // 字段update set部分注入
    private String update;
    // where字段比较条件
    private String condition = SqlCondition.EQUAL;
    // 字段填充策略
    private FieldFill fieldFill = FieldFill.DEFAULT;
    // 表字段是否启用了插入填充
    private boolean withInsertFill;
    // 表字段是否启用了更新填充
    private boolean withUpdateFill;
    // 缓存sql select
    private String sqlSelect;
    // JDBC类型
    private JdbcType jdbcType;
    // 类型处理器
    private Class<? extends TypeHandler<?>> typeHandler;

AbstractMethod.inject()方法

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);
}

// 这是一个抽象方法,由子类实现,以SelectList类为例
public abstract MappedStatement injectMappedStatement(
    Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo);

SelectList类:

public class SelectList extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(
        Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {

        // <script>%s SELECT %s FROM %s %s %s</script>
        SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
        // 这里根据sqlMethod、tableInfo等信息生成一个动态SQL
        // 与我们在mapper xml文件里面配置效果一样
        String sql = String.format(
            sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(),
            sqlWhereEntityWrapper(true, tableInfo), sqlComment());

        // 生成SqlSource,此处是一个动态DynamicSqlSource对象
        // 此处languageDriver是MybatisXMLLanguageDriver对象
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
    }
}

// MybatisXMLLanguageDriver.createSqlSource(configuration, sql, modelClass)
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
    GlobalConfig.DbConfig config = GlobalConfigUtils.getDbConfig(configuration);
    if (config.isReplacePlaceholder()) {
        List<String> find = SqlUtils.findPlaceholder(script);
        if (CollectionUtils.isNotEmpty(find)) {
            try {
                script = SqlUtils.replaceSqlPlaceholder(script, find, config.getEscapeSymbol());
            } catch (MybatisPlusException e) {
                throw new IncompleteElementException();
            }
        }
    }
    return super.createSqlSource(configuration, script, parameterType);
}

// super.createSqlSource(configuration, script, parameterType)
// 这里回到了之前分析mapper xml解析的代码上,已经分析过,此处不再展开分析
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
  XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
  return builder.parseScriptNode();
}
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
  if (script.startsWith("<script>")) {
    XPathParser parser =
        new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());
    return createSqlSource(configuration, parser.evalNode("/script"), parameterType);
  } else {
    script = PropertyParser.parse(script, configuration.getVariables());
    TextSqlNode textSqlNode = new TextSqlNode(script);
    if (textSqlNode.isDynamic()) {
      return new DynamicSqlSource(configuration, textSqlNode);
    } else {
      return new RawSqlSource(configuration, script, parameterType);
    }
  }
}

this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo)就是创建MappedStatement对象并将其注册到Configuration中:

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, new NoKeyGenerator(), null, null);
    } else {
        /* 普通查询 */
        return addSelectMappedStatementForOther(mapperClass, id, sqlSource, table.getEntityType());
    }
}

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)) {
        return null;
    }
    /* 缓存逻辑处理 */
    boolean isSelect = false;
    if (sqlCommandType == SqlCommandType.SELECT) {
        isSelect = true;
    }
    // 这里就回到了之前分析过的注册MappedStatement的逻辑,不再展开分析
    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);
}

到这里为止,mybatis-plus注册BaseMapper API Statement的逻辑就分析完成了。

Wrapper查询构建原理

一个示例

List<Blog> list = this.blogMapper.selectList(new LambdaQueryWrapper<Blog>()
    .like(Blog::getTitle, "spring")
    .eq(Blog::getId, 2)
);

Wrapper抽象类

BaseMapper接口的大多数方法都接收一个Wrapper的实现类对象作为查询条件。

比如:

T selectOne(Wrapper<T> queryWrapper);
Integer selectCount(Wrapper<T> queryWrapper);
List<T> selectList(Wrapper<T> queryWrapper);
List<Map<String, Object>> selectMaps(Wrapper<T> queryWrapper);
List<Object> selectObjs(Wrapper<T> queryWrapper);

这个类实现了ISqlSegment接口,自己也定义了一些方法,这些方法在之后拼接动态SQL时会起到很大的作用:

public abstract class Wrapper<T> implements ISqlSegment {

    public abstract T getEntity();

    public String getSqlSelect();

    public String getSqlSet();

    public String getSqlComment();

    public String getSqlFirst();

    // 获取MergeSegments
    public abstract MergeSegments getExpression();

    /**
     * 获取自定义SQL 简化自定义XML复杂情况
     * 使用方法: `select xxx from table` + ${ew.customSqlSegment}
     */
    public String getCustomSqlSegment();

    // 查询条件为空(包含entity)
    public boolean isEmptyOfWhere();

    // 查询条件不为空(包含entity)
    public boolean nonEmptyOfWhere();

    // 查询条件为空(不包含entity)
    public boolean isEmptyOfNormal();

    // 查询条件为空(不包含entity)
    public boolean nonEmptyOfNormal();

    // 深层实体判断属性
    public boolean nonEmptyOfEntity();

    // 根据实体FieldStrategy属性来决定判断逻辑
    private boolean fieldStrategyMatch(T entity, TableFieldInfo e);

    // 深层实体判断属性
    public boolean isEmptyOfEntity();

    // 获取格式化后的执行sql
    public String getTargetSql();

    // 条件清空
    abstract public void clear();
}

/**
 * SQL片段接口
 */
@FunctionalInterface
public interface ISqlSegment extends Serializable {

    // 获取SQL片段
    String getSqlSegment();
}

他有一个抽象子类:AbstractWrapper类。

AbstractWrapper抽象类

定义了核心的条件拼接方法,比如:

public Children eq(boolean condition, R column, Object val);
public Children ne(boolean condition, R column, Object val);
public Children gt(boolean condition, R column, Object val);
public Children ge(boolean condition, R column, Object val);
public Children lt(boolean condition, R column, Object val);
public Children le(boolean condition, R column, Object val);
public Children like(boolean condition, R column, Object val);
public Children notLike(boolean condition, R column, Object val);
public Children likeLeft(boolean condition, R column, Object val);
public Children likeRight(boolean condition, R column, Object val);
public Children between(boolean condition, R column, Object val1, Object val2);
public Children notBetween(boolean condition, R column, Object val1, Object val2);
public Children and(boolean condition, Consumer<Children> consumer);
public Children or(boolean condition, Consumer<Children> consumer);
public Children exists(boolean condition, String existsSql);
public Children notExists(boolean condition, String existsSql);
public Children isNull(boolean condition, R column);
public Children isNotNull(boolean condition, R column);
public Children in(boolean condition, R column, Collection<?> coll);
public Children notIn(boolean condition, R column, Collection<?> coll);
public Children groupBy(boolean condition, R... columns);
public Children orderBy(boolean condition, boolean isAsc, R... columns);
public Children having(boolean condition, String sqlHaving, Object... params);

方法的实现此处不做展开。

QueryWrapper类

这个类继承了AbstractWrapper抽象类,实现了Query接口,实现了Query中的方法:

public QueryWrapper<T> select(String... columns);
public QueryWrapper<T> select(Class entityClass, Predicate predicate);
public String getSqlSelect();
protected QueryWrapper<T> instance();
public void clear();

AbstractLambdaWrapper抽象类和LambdaQueryWrapper类

AbstractLambdaWrapper类重写了AbstractWrapper类的columnsToString方法,使用lambda转换列名。

此处的lambda解析还是很值得学习的,暂时不展开分析了。

实现原理

selectList的MappedStatement结构

首先看一下MappedStatement的结构,之后再做分析:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

上面的四张图简单说明一下:

  1. 拼接select子句和from子句
  2. 拼接where查询条件,这里面包含两部分:一部分是使用entity作为查询条件,一部分是使用wrapper作为查询条件

在介绍完LambdaQueryWrapper之后我们在详细说明这里面的几个重要属性。

LambdaQueryWrapper.like方法

default Children like(R column, Object val) {
    return like(true, column, val);
}

// AbstractWrapper.like
public Children like(boolean condition, R column, Object val) {
    return likeValue(condition, LIKE, column, val, SqlLike.DEFAULT);
}

protected Children likeValue(
    boolean condition, SqlKeyword keyword, R column, Object val, SqlLike sqlLike) {
    return doIt(
        condition,
        () -> columnToString(column), // 列名
        keyword, // LIKE
        // #{ew.paramNameValuePairs.MPGENVAL1}
        // 生成类似这样的表达式
        () -> formatSql("{0}", SqlUtils.concatLike(val, sqlLike)));
}

protected Children doIt(boolean condition, ISqlSegment... sqlSegments) {
    if (condition) {
        // 此处将sqlSegments加到了expression中,这是一个容器,保存所有的ISqlSegment
        expression.add(sqlSegments);
    }
    return typedThis;
}

// protected MergeSegments expression;

MergeSegments类,从名字可以看出,这个类聚合了其他类型的ISqlSegment对象:

public class MergeSegments implements ISqlSegment {

    private final NormalSegmentList normal = new NormalSegmentList();
    private final GroupBySegmentList groupBy = new GroupBySegmentList();
    private final HavingSegmentList having = new HavingSegmentList();
    private final OrderBySegmentList orderBy = new OrderBySegmentList();

    private String sqlSegment = StringPool.EMPTY;
    private boolean cacheSqlSegment = true;

    public void add(ISqlSegment... iSqlSegments) {
        List<ISqlSegment> list = Arrays.asList(iSqlSegments);
        ISqlSegment firstSqlSegment = list.get(0);
        if (MatchSegment.ORDER_BY.match(firstSqlSegment)) {
            orderBy.addAll(list);
        } else if (MatchSegment.GROUP_BY.match(firstSqlSegment)) {
            groupBy.addAll(list);
        } else if (MatchSegment.HAVING.match(firstSqlSegment)) {
            having.addAll(list);
        } else {
            normal.addAll(list);
        }
        cacheSqlSegment = false;
    }

    // 这个方法后续再详细分析
    @Override
    public String getSqlSegment() {
        if (cacheSqlSegment) {
            return sqlSegment;
        }
        cacheSqlSegment = true;
        if (normal.isEmpty()) {
            if (!groupBy.isEmpty() || !orderBy.isEmpty()) {
                sqlSegment = groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment();
            }
        } else {
            sqlSegment = normal.getSqlSegment() + 
                groupBy.getSqlSegment() + having.getSqlSegment() + orderBy.getSqlSegment();
        }
        return sqlSegment;
    }

    public void clear() {
        // ...
    }
}

LambdaQueryWrapper.eq方法

与like方法一样,不展开分析了。

SelectList类

public class SelectList extends AbstractMethod {

    @Override
    public MappedStatement injectMappedStatement(
        Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {

        // 封装原始SQL
        SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
        // 格式化动态SQL
        String sql = String.format(
            sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(),
            sqlWhereEntityWrapper(true, tableInfo), sqlComment());
        // 生成DynamicSqlSource
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
    }
}

原始SQL:

<script>%s SELECT %s FROM %s %s %s\\n</script>

格式化后的动态SQL:

<script>
  <choose>
    <when test="ew != null and ew.sqlFirst != null">
      ${ew.sqlFirst}
    </when>
    <otherwise></otherwise>
  </choose> SELECT 
  <!-- 拼接查询字段 -->
  <choose>
    <when test="ew != null and ew.sqlSelect != null">
      ${ew.sqlSelect}
    </when>
    <otherwise>id,title,content,create_time,update_time</otherwise>
  </choose> FROM blog  <!-- 拼接表名 -->
  <!-- 拼接查询条件 -->
  <if test="ew != null">
    <where>
      <!-- 拼接实体对象查询条件 -->
      <if test="ew.entity != null">
        <if test="ew.entity.id != null">id=#{ew.entity.id}</if>
        <if test="ew.entity['title'] != null"> AND title=#{ew.entity.title}</if>
        <if test="ew.entity['content'] != null"> AND content=#{ew.entity.content}</if>
        <if test="ew.entity['createTime'] != null"> AND create_time=#{ew.entity.createTime}</if>
        <if test="ew.entity['updateTime'] != null"> AND update_time=#{ew.entity.updateTime}</if>
      </if>
      <!-- 拼接Wrapper对象查询条件 -->
      <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
        <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment}
      </if>
    </where>
    <!-- 拼接Wrapper对象查询条件,这里通常没有办法进来,存在疑问 -->
    <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
      ${ew.sqlSegment}
    </if>
  </if> 
  <choose>
    <when test="ew != null and ew.sqlComment != null">
      ${ew.sqlComment}
    </when>
    <otherwise></otherwise>
  </choose>
</script>

生成的SqlSource:

在这里插入图片描述

查询执行过程

之前分析过了,此处不再展开。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

mybatis源码之集成mybatis-plus源码 的相关文章

  • Centos7安装Python3.7详细教程

    Centos7安装Python3 7详细教程 注 xff1a 如果安装更高的python版本 xff0c 只需修改wget 后面的地址即可 xff0c 然后注意执行命令时候的路径等问题 如 xff1a 安装python3 7 5 则 xff

随机推荐

  • 多用户同时修改同一条数据(并发修改数据)

    如果两个用户同时打开一条记录 xff0c 修改后提交会产生更新冲突 办法有三 xff1a 1 打开同时锁定表的记录 2 用lock对修改方法加锁 2 捕获错误 xff0c 撤消其中一个用户的修改 场景描述如下 xff1a 用户A B同时打开
  • Go 语言入门很简单:Go 实现简易Web应用

    前言 截止到目前为止 xff0c 几乎我们的 Go 入门文章都是在终端运行的 在终端运行的代码或者运用运用程序只适合自己在环境搭好的环境下使用 也就是说 xff0c 如果用户没有安装 Go 语言环境 xff0c 是根本没法运行我们所写的 G
  • 【待解决】使用su或sudo出现Segmentation fault

    一台服务器上 xff0c 使用sudo会出现Segmentation fault xff0c 见下 xff1a 使用root登录后 xff0c 使用su命令 xff0c 一样的会出现Segmentation fault 暂时还未找到答案 相
  • go换源|go更换国内源

    Windows 版本 SETX GO111MODULE on go env w GOPROXY 61 https goproxy cn direct SETX GOPROXY https goproxy cn direct Linux 版本
  • linux安装go环境并配置国内源

    linux安装go环境并配置国内源 第一步 官网下载安装包 https golang google cn go1 4 linux amd64 tar gz 第二步 解压缩 tar C usr local xzf go1 4 linux am
  • python - 获取时间戳(10位和13位)

    在python 开发web程序时 xff0c 需要调用第三方的相关接口 xff0c 在调用时 xff0c 需要对请求进行签名 需要用到unix时间戳 在python里 xff0c 在网上介绍的很多方法 xff0c 得到的时间戳是10位 而j
  • curl命令模拟post请求发送json格式数据

    以下代码可以作为测试接收请求的程序直接复制使用 xff1a from flask import Flask request app 61 Flask name 64 app route 39 service 39 methods 61 39
  • pip换源 -pip更换国内镜像源

    更换pip源到国内镜像 pip国内的一些镜像 阿里云 http mirrors aliyun com pypi simple 中国科技大学 https pypi mirrors ustc edu cn simple 豆瓣 douban ht
  • 使用python的requests 发送multipart/form-data 请求

    发送post请求 1 r 61 requests post 34 http pythontab com postTest 34 data 61 34 key 34 34 value 34 以上得知 xff0c post请求参数是以data关
  • SHELL - shell 脚本获取本机ip并将ip复制给变量待用

    bin bash VAR 61 34 eth0 34 HOST IP 61 ifconfig VAR grep 34 inet addr 34 awk 39 print 2 39 awk F 39 print 2 39 echo HOST
  • shell - sed匹配某一行开头,替换整行内容

    sed i 39 cloud server ip ccloud server ip 61 update skyeye 360safe com 39 name txt
  • caffe安装系列——安装cuda和cudnn

    博客新址 http blog xuezhisd top 邮箱 xff1a xuezhisd 64 126 com 说明 网上关于caffe的安装教程非常多 xff0c 但是关于每一步是否操作成功 xff0c 出现了什么样的错误又该如何处理没
  • caffe安装系列——安装OpenCV

    博客新址 http blog xuezhisd top 邮箱 xff1a xuezhisd 64 126 com 说明 网上关于caffe的安装教程非常多 xff0c 但是关于每一步是否操作成功 xff0c 出现了什么样的错误又该如何处理没
  • 写递归函数的正确思维方法

    什么是递归 简单的定义 当函数直接或者间接调用自己时 xff0c 则发生了递归 说起来简单 但是理解起来复杂 因为递归并不直观 也不符合我们的思维习惯 相对于递归 我们更加容易理解迭代 因为我们日常生活中的思维方式就是一步接一步的 并且能够
  • PCL系列——拼接两个点云

    博客新址 http blog xuezhisd top 邮箱 xff1a xuezhisd 64 126 com PCL系列 PCL系列 读入PCD格式文件操作PCL系列 将点云数据写入PCD格式文件PCL系列 拼接两个点云PCL系列 从深
  • PCL系列——三维重构之移动立方体算法

    博客新址 http blog xuezhisd top 邮箱 xff1a xuezhisd 64 126 com PCL系列 PCL系列 读入PCD格式文件操作PCL系列 将点云数据写入PCD格式文件PCL系列 拼接两个点云PCL系列 从深
  • 字节(Byte)与位(bit)、十进制与二进制的关系

    一 基本常识 数据存储是以 字节 xff08 Byte xff09 为单位 xff0c 数据传输大多是以 位 xff08 bit xff0c 又名 比特 xff09 为单位 xff0c 一个位就代表一个0或1 xff08 即二进制 xff0
  • ubuntu20.04配置TensorFlow-GPU版本+对应版本的cuda&cudnn

    ubuntu20 04配置TensorFlow GPU版本 43 对应版本的cuda amp cudnn 配置说明 操作系统是Ubuntu20 04 xff0c GPU是NVIDIA GeForce RTX 2080 Ti xff0c Py
  • 十四、Rust ORM 框架

    Rust 下的 orm xff0c 之前笔者介绍过 sqlx xff0c 但使用中发现 sqlx 在进行参数绑定时 xff0c 使用的是 宏 xff0c 在当前的 IDE 生态环境下 xff0c 有时不能很好的进行代码提示 xff0c 或代
  • mybatis源码之集成mybatis-plus源码

    本文将结合源码介绍mybatis plus的原理 xff0c 包括 xff1a BaseMapper APIMybatisSqlSessionFactoryBean类BaseMapper API Statement解析Wrapper查询构建