IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> mybatis源码之集成mybatis-plus源码 -> 正文阅读

[Java知识库]mybatis源码之集成mybatis-plus源码

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

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

系列文档:


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:

在这里插入图片描述

查询执行过程

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

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-08-19 18:50:24  更:2022-08-19 18:53:33 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 5:24:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码