mybatis源码之集成mybatis-plus源码

本文将结合源码介绍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) {

        // 
        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("

格式化后的动态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:

mybatis源码之集成mybatis-plus源码_第5张图片

查询执行过程

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

你可能感兴趣的:(mybatis,java技术,mybatis,java,数据库)