mybatis-plus源码分析

一、mybatis-plus简介

  MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。Mybatis是一个jdbc工具,还不是orm框架,而mybatis则是一个orm框架。
  对mybatis-plus的使用不太了解的可以去官方文档看看:https://baomidou.com/introduce/

二、源码分析

  让我们从一个很简单demo,开启mybatis的源码分析之路。本次的demo还是上一篇springboot+mybaits集成的demo的基础上,引入了mybatis-plus的依赖。改动点只要在:

  • UserDO.java使用注解映射数据库表名和字段
@TableName(value="MBP_USER")
public class UserDO {

    @TableId(value = "MBP_ID",type = IdType.ASSIGN_UUID)
    private Long id;

    @TableField(value = "MBP_NAME")
    private String name;

    @TableField(value = "MBP_AGE")
    private Integer age;

    @TableField(value = "MBP_EMAIL")
    private String email;
    //getter,setter方法省略
}
  • UserMapper.java继承了BaseMapper接口
public interface UserMapper extends BaseMapper<UserDO> {
}

  而BaseMapper是Mybatis-plus提供的MapperInterface的几个积累,里面封装了一些列基础的对数据库CRUD的方法。

package com.baomidou.mybatisplus.core.mapper;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import org.apache.ibatis.annotations.Param;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;
import java.util.Map;



/**
 * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能
 * <p>这个 Mapper 支持 id 泛型</p>
 *
 * @author hubin
 * @since 2016-01-23
 */
public interface BaseMapper<T> extends Mapper<T> {

    /**
     * 插入一条记录
     *
     * @param entity 实体对象
     */
    int insert(T entity);

    /**
     * 根据 ID 删除
     *
     * @param id 主键ID
     */
    int deleteById(Serializable id);

    /**
     * 根据 columnMap 条件,删除记录
     *
     * @param columnMap 表字段 map 对象
     */
    int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,删除记录
     *
     * @param wrapper 实体对象封装操作类(可以为 null)
     */
    int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);

    /**
     * 删除(根据ID 批量删除)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * 根据 ID 修改
     *
     * @param entity 实体对象
     */
    int updateById(@Param(Constants.ENTITY) T entity);

    /**
     * 根据 whereEntity 条件,更新记录
     *
     * @param entity        实体对象 (set 条件值,可以为 null)
     * @param updateWrapper 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句)
     */
    int update(@Param(Constants.ENTITY) T entity, @Param(Constants.WRAPPER) Wrapper<T> updateWrapper);

    /**
     * 根据 ID 查询
     *
     * @param id 主键ID
     */
    T selectById(Serializable id);

    /**
     * 查询(根据ID 批量查询)
     *
     * @param idList 主键ID列表(不能为 null 以及 empty)
     */
    List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);

    /**
     * 查询(根据 columnMap 条件)
     *
     * @param columnMap 表字段 map 对象
     */
    List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);

    /**
     * 根据 entity 条件,查询一条记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询总记录数
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录
     * <p>注意: 只返回第一个字段的值</p>
     *
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 entity 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件(可以为 RowBounds.DEFAULT)
     * @param queryWrapper 实体对象封装操作类(可以为 null)
     */
    <E extends IPage<T>> E selectPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);

    /**
     * 根据 Wrapper 条件,查询全部记录(并翻页)
     *
     * @param page         分页查询条件
     * @param queryWrapper 实体对象封装操作类
     */
    <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
}

  其它地方的配置和springboot+mybatis集成的demo是一样的。springboot+mybatis-plus是在springboot+mybatis的基础上做了增强,原理已经所使用的核心类大部分相同,我们本文只关心不一样的地方。

2.1 mybatis与springboot集成入口

  mybtatis-plus的自动装配类是:com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
在这里插入图片描述
  进入到MybatisPlusAutoConfiguration中,我们看下SqlSessionFactory的创建过程

@Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        // TODO 使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        factory.setDataSource(dataSource);
        factory.setVfs(SpringBootVFS.class);
        if (StringUtils.hasText(this.properties.getConfigLocation())) {
            factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
        }
        applyConfiguration(factory);
        if (this.properties.getConfigurationProperties() != null) {
            factory.setConfigurationProperties(this.properties.getConfigurationProperties());
        }
        if (!ObjectUtils.isEmpty(this.interceptors)) {
            factory.setPlugins(this.interceptors);
        }
        if (this.databaseIdProvider != null) {
            factory.setDatabaseIdProvider(this.databaseIdProvider);
        }
        if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
            factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
        }
        if (this.properties.getTypeAliasesSuperType() != null) {
            factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
        }
        if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
            factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
        }
        if (!ObjectUtils.isEmpty(this.typeHandlers)) {
            factory.setTypeHandlers(this.typeHandlers);
        }
        if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
            factory.setMapperLocations(this.properties.resolveMapperLocations());
        }

        // TODO 对源码做了一定的修改(因为源码适配了老旧的mybatis版本,但我们不需要适配)
        Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
        if (!ObjectUtils.isEmpty(this.languageDrivers)) {
            factory.setScriptingLanguageDrivers(this.languageDrivers);
        }
        Optional.ofNullable(defaultLanguageDriver).ifPresent(factory::setDefaultScriptingLanguageDriver);

        // TODO 自定义枚举包
        if (StringUtils.hasLength(this.properties.getTypeEnumsPackage())) {
            factory.setTypeEnumsPackage(this.properties.getTypeEnumsPackage());
        }
        // TODO 此处必为非 NULL
        GlobalConfig globalConfig = this.properties.getGlobalConfig();
        // TODO 注入填充器
        this.getBeanThen(MetaObjectHandler.class, globalConfig::setMetaObjectHandler);
        // TODO 注入主键生成器
        this.getBeanThen(IKeyGenerator.class, i -> globalConfig.getDbConfig().setKeyGenerator(i));
        // TODO 注入sql注入器
        this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
        // TODO 注入ID生成器
        this.getBeanThen(IdentifierGenerator.class, globalConfig::setIdentifierGenerator);
        // TODO 设置 GlobalConfig 到 MybatisSqlSessionFactoryBean
        factory.setGlobalConfig(globalConfig);
        return factory.getObject();
    }

  需要注意,mybatis-plus用户的使用 MybatisSqlSessionFactoryBean 而不是 SqlSessionFactoryBean。

2.2 SqlSessionFactoryBean

  让我们进一步看下创建SqlSessionFactory的SqlSessionFactoryBean.getObject()过程。

   @Override
    public SqlSessionFactory getObject() throws Exception {
        if (this.sqlSessionFactory == null) {
            //在afterPropertiesSet方法中组织sqlSessionFactory
            afterPropertiesSet();
        }

        return this.sqlSessionFactory;
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        notNull(dataSource, "Property 'dataSource' is required");
        state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
            "Property 'configuration' and 'configLocation' can not specified with together");
        //使用buildSqlSessionFactory()创建sqlSessionFactory 
        this.sqlSessionFactory = buildSqlSessionFactory();
    }

protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

        //SqlSessionFactory中是MybatisConfiguration而不是Configuration
        final MybatisConfiguration targetConfiguration;

        // TODO 使用 MybatisXmlConfigBuilder 而不是 XMLConfigBuilder
        MybatisXMLConfigBuilder xmlConfigBuilder = null;
        if (this.configuration != null) {
            targetConfiguration = this.configuration;
            if (targetConfiguration.getVariables() == null) {
                targetConfiguration.setVariables(this.configurationProperties);
            } else if (this.configurationProperties != null) {
                targetConfiguration.getVariables().putAll(this.configurationProperties);
            }
        } else if (this.configLocation != null) {
            // TODO 使用 MybatisXMLConfigBuilder
            xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
            targetConfiguration = xmlConfigBuilder.getConfiguration();
        } else {
            LOGGER.debug(() -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
            // TODO 使用 MybatisConfiguration
            targetConfiguration = new MybatisConfiguration();
            Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
        }

        // TODO 无配置启动所必须的
        this.globalConfig = Optional.ofNullable(this.globalConfig).orElseGet(GlobalConfigUtils::defaults);
        this.globalConfig.setDbConfig(Optional.ofNullable(this.globalConfig.getDbConfig()).orElseGet(GlobalConfig.DbConfig::new));

        // TODO 初始化 id-work 以及 打印骚东西
        targetConfiguration.setGlobalConfig(this.globalConfig);

        // TODO 自定义枚举类扫描处理
        if (hasLength(this.typeEnumsPackage)) {
            Set<Class<?>> classes;
            if (typeEnumsPackage.contains(StringPool.STAR) && !typeEnumsPackage.contains(StringPool.COMMA)
                && !typeEnumsPackage.contains(StringPool.SEMICOLON)) {
                classes = scanClasses(typeEnumsPackage, null);
                if (classes.isEmpty()) {
                    LOGGER.warn(() -> "Can't find class in '[" + typeEnumsPackage + "]' package. Please check your configuration.");
                }
            } else {
                classes = new HashSet<>();
                String[] typeEnumsPackageArray = tokenizeToStringArray(this.typeEnumsPackage,
                    ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
                Assert.notNull(typeEnumsPackageArray, "not find typeEnumsPackage:" + typeEnumsPackage);
                Stream.of(typeEnumsPackageArray).forEach(typePackage -> {
                    try {
                        Set<Class<?>> scanTypePackage = scanClasses(typePackage, null);
                        if (scanTypePackage.isEmpty()) {
                            LOGGER.warn(() -> "Can't find class in '[" + typePackage + "]' package. Please check your configuration.");
                        } else {
                            classes.addAll(scanTypePackage);
                        }
                    } catch (IOException e) {
                        throw new MybatisPlusException("Cannot scan class in '[" + typePackage + "]' package", e);
                    }
                });
            }
            // 取得类型转换注册器
            TypeHandlerRegistry typeHandlerRegistry = targetConfiguration.getTypeHandlerRegistry();
            classes.stream()
                .filter(Class::isEnum)
                .filter(MybatisEnumTypeHandler::isMpEnums)
                .forEach(cls -> typeHandlerRegistry.register(cls, MybatisEnumTypeHandler.class));
        }

        Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
        Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
        Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);

        if (hasLength(this.typeAliasesPackage)) {
            scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
                .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
                .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
        }

        if (!isEmpty(this.typeAliases)) {
            Stream.of(this.typeAliases).forEach(typeAlias -> {
                targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
                LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
            });
        }

        if (!isEmpty(this.plugins)) {
            Stream.of(this.plugins).forEach(plugin -> {
                targetConfiguration.addInterceptor(plugin);
                LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
            });
        }

        if (hasLength(this.typeHandlersPackage)) {
            scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
                .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
                .filter(clazz -> ClassUtils.getConstructorIfAvailable(clazz) != null)
                .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
        }

        if (!isEmpty(this.typeHandlers)) {
            Stream.of(this.typeHandlers).forEach(typeHandler -> {
                targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
                LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
            });
        }

        if (!isEmpty(this.scriptingLanguageDrivers)) {
            Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
                targetConfiguration.getLanguageRegistry().register(languageDriver);
                LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
            });
        }

        Optional.ofNullable(this.defaultScriptingLanguageDriver).ifPresent(targetConfiguration::setDefaultScriptingLanguage);

        if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
            try {
                targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
            } catch (SQLException e) {
                throw new NestedIOException("Failed getting a databaseId", e);
            }
        }

        Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);

        if (xmlConfigBuilder != null) {
            try {
                xmlConfigBuilder.parse();
                LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
            } catch (Exception ex) {
                throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
            } finally {
                ErrorContext.instance().reset();
            }
        }

        targetConfiguration.setEnvironment(new Environment(MybatisSqlSessionFactoryBean.class.getSimpleName(),
            this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
            this.dataSource));

        if (this.mapperLocations != null) {
            if (this.mapperLocations.length == 0) {
                LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
            } else {
                for (Resource mapperLocation : this.mapperLocations) {
                    if (mapperLocation == null) {
                        continue;
                    }
                    try {
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                            targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                        xmlMapperBuilder.parse();
                    } catch (Exception e) {
                        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                    } finally {
                        ErrorContext.instance().reset();
                    }
                    LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
                }
            }
        } else {
            LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
        }

        final SqlSessionFactory sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(targetConfiguration);

        // TODO SqlRunner
        SqlHelper.FACTORY = sqlSessionFactory;

        // TODO 打印骚东西 Banner
        if (globalConfig.isBanner()) {
            System.out.println(" _ _   |_  _ _|_. ___ _ |    _ ");
            System.out.println("| | |\\/|_)(_| | |_\\  |_)||_|_\\ ");
            System.out.println("     /               |         ");
            System.out.println("                        " + MybatisPlusVersion.getVersion() + " ");
        }

        return sqlSessionFactory;
    }

  从上述过程可以看出,mybatis-plus的SqlSessionFactory中是MybatisConfiguration而不是Configuration。

2.3 MybatisConfiguration

  让我们进一步看看MybatisConfiguration相对于Configuration有什么不同。MybatisConfiguration.java的类定义如下:

public class MybatisConfiguration extends Configuration {
    private static final Log logger = LogFactory.getLog(MybatisConfiguration.class);
    /**
     * Mapper 注册
     */
    protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);

    public MybatisConfiguration(Environment environment) {
        this();
        this.environment = environment;

}


    /**
     * 使用自己的 MybatisMapperRegistry
     */
    @Override
    public MapperRegistry getMapperRegistry() {
        return mybatisMapperRegistry;
    }

    /**
     * 使用自己的 MybatisMapperRegistry
     */
    @Override
    public <T> void addMapper(Class<T> type) {
        mybatisMapperRegistry.addMapper(type);
    }

    /**
     * 使用自己的 MybatisMapperRegistry
     */
    @Override
    public void addMappers(String packageName, Class<?> superType) {
        mybatisMapperRegistry.addMappers(packageName, superType);
    }

    /**
     * 使用自己的 MybatisMapperRegistry
     */
    @Override
    public void addMappers(String packageName) {
        mybatisMapperRegistry.addMappers(packageName);
    }

    /**
     * 使用自己的 MybatisMapperRegistry
     */
    @Override
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mybatisMapperRegistry.getMapper(type, sqlSession);
    }

    /**
     * 使用自己的 MybatisMapperRegistry
     */
    @Override
    public boolean hasMapper(Class<?> type) {
        return mybatisMapperRegistry.hasMapper(type);
    }

    //其它方法先忽略

  从类中可以看出,MybatisConfiguration是Configuration的子类,使用mybatisMapperRegistry来代替代Configuration中的mapperRegistry。addMapper和getMapper等方法都是重写了,从mybatisMapperRegistry中去操作。

2.4 MybatisMapperRegistry

  在之前mybatis源码学习章节中,我们知道mybatis是为MapperInterface生成了动态代理类。而在Mybatis-plus中,所有的MapperInterface是集成了BaseMapper的。这里我们主要看下MybatisMapperRegistry是如何为BaseMapper的子类生成代理对象的。


/**
 * 继承至MapperRegistry
 *
 * @author Caratacus hubin
 * @since 2017-04-19
 */
public class MybatisMapperRegistry extends MapperRegistry {

    private final Map<Class<?>, MybatisMapperProxyFactory<?>> knownMappers = new HashMap<>();
    private final MybatisConfiguration config;

    public MybatisMapperRegistry(MybatisConfiguration config) {
        super(config);
        this.config = config;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        // TODO 这里换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
        final MybatisMapperProxyFactory<T> mapperProxyFactory = (MybatisMapperProxyFactory<T>) 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);
        }
    }

    @Override
    public <T> boolean hasMapper(Class<T> type) {
        return knownMappers.containsKey(type);
    }

    @Override
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            if (hasMapper(type)) {
                // TODO 如果之前注入 直接返回
                return;
                // TODO 这里就不抛异常了
//                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                // TODO 这里也换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
                knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
                // It's important that the type is added before the parser is run
                // otherwise the binding may automatically be attempted by the
                // mapper parser. If the type is already known, it won't try.
                // TODO 这里也换成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }

    /**
     * 使用自己的 knownMappers
     */
    @Override
    public Collection<Class<?>> getMappers() {
        return Collections.unmodifiableCollection(knownMappers.keySet());
    }
}

  从MybatisMapperRegistry的代码中可以看出:

  • mybatis-plus中mapperInterface的代理对象工厂类是MybatisMapperProxyFactory,而不是mybatis中的MapperProxyFactory。而生成的代理对象是MybatisMapperProxy,而不是mybatis中的ProxyFactory。
  • 这里也换成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder

2.5 MybatisMapperAnnotationBuilder


/**
 * 继承
 * <p>
 * 只重写了 {@link MapperAnnotationBuilder#parse}
 * 没有XML配置文件注入基础CRUD方法
 * </p>
 *
 * @author Caratacus
 * @since 2017-01-04
 */
public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
@Override
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            loadXmlResource();
            configuration.addLoadedResource(resource);
            final String typeName = type.getName();
            assistant.setCurrentNamespace(typeName);
            parseCache();
            parseCacheRef();
            SqlParserHelper.initSqlParserInfoCache(type);
            Method[] methods = type.getMethods();
            for (Method method : methods) {
                try {
                    // issue #237
                    if (!method.isBridge()) {
                        parseStatement(method);
                        SqlParserHelper.initSqlParserInfoCache(typeName, method);
                    }
                } catch (IncompleteElementException e) {
                    // TODO 使用 MybatisMethodResolver 而不是 MethodResolver
                    configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
                }
            }
            // TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql
            if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
                GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
            }
        }
        parsePendingMethods();
    }

}

  从上面代码可以看出GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type)这一句是完成对BaseMapper类中CRUD操作的解析注入
  GlobalConfigUtils.getSqlInjector(configuration)获取的是DefaultSqlInjector,我们着重看下DefaultSqlInjector.inspectInject(assistant, type)

2.6 DefaultSqlInjector


/**
 * SQL 自动注入器
 *
 * @author hubin
 * @since 2018-04-07
 */
public abstract class AbstractSqlInjector implements ISqlInjector {

    private static final Log logger = LogFactory.getLog(AbstractSqlInjector.class);

    @Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        //BaseMapper<T>是一个泛型接口,此处是获取T的具体Model,在本demo中就是UserDO
        Class<?> modelClass = extractModelClass(mapperClass);
        if (modelClass != null) {
            String className = mapperClass.toString();
            Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
            if (!mapperRegistryCache.contains(className)) {
                //从DefaultSqlInjector.getMethodList获取所有的接口定义
                List<AbstractMethod> methodList = this.getMethodList(mapperClass);
                if (CollectionUtils.isNotEmpty(methodList)) {
                    //此处是解析Model元数据信息,并且将信息存储到TableInfoHelper。注意:TableInfoHelper是一个容器类,它存储着项目中所有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);
            }
        }
    }

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

    /**
     * 提取泛型模型,多泛型的时候请将泛型T放在第一位
     *
     * @param mapperClass mapper 接口
     * @return mapper 泛型
     */
    protected Class<?> extractModelClass(Class<?> mapperClass) {
        Type[] types = mapperClass.getGenericInterfaces();
        ParameterizedType target = null;
        for (Type type : types) {
            if (type instanceof ParameterizedType) {
                Type[] typeArray = ((ParameterizedType) type).getActualTypeArguments();
                if (ArrayUtils.isNotEmpty(typeArray)) {
                    for (Type t : typeArray) {
                        if (t instanceof TypeVariable || t instanceof WildcardType) {
                            break;
                        } else {
                            target = (ParameterizedType) type;
                            break;
                        }
                    }
                }
                break;
            }
        }
        return target == null ? null : (Class<?>) target.getActualTypeArguments()[0];
    }
}


/**
 * SQL 默认注入器
 *
 * @author hubin
 * @since 2018-04-10
 */
public class DefaultSqlInjector extends AbstractSqlInjector {

    @Override
    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());
    }
}

  从上面的代码可以看出,inspectInject方法有2个作用:

  • 解析Model元信息,并且注入到容器类TableInfoHelper中;
  • getMethod()中后去的内置的方法,都是AbstractMethod的子类,通过循环getMethod()获取的结果,将BaseMapper中的CRUD方法注入到StatementMap中。

2.7 AbstractMethod

/**
 * 抽象的注入方法类
 *
 * @author hubin
 * @since 2018-04-06
 */
public abstract class AbstractMethod implements Constants {

    /**
     * 注入自定义方法
     */
    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 mapperClass mapper 接口
     * @param modelClass  mapper 泛型
     * @param tableInfo   数据库表反射信息
     * @return MappedStatement
     */
    public abstract MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo);

   // 其它方法省略
}

  每个CRUD方法的注入,可以去查看AbstractMethod子类中的injectMappedStatement方法。
至此,Mybatis-plus与Springboot集成的原理大概是讲清楚了。

三、LambdaQueryWrapper

  在mybatis-plus中还有一个比较吸引人的是支持 Lambda 形式调用,通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错。

  • lambda写法的查询条件
    @Test
    public void testSelect2() {
        //Lambda形式写法
        LambdaQueryWrapper<UserDO> queryWrapper = Wrappers.lambdaQuery(UserDO.class).le(UserDO::getId, 10);
        List<UserDO> userList = (List)userMapper.selectObjs(queryWrapper);
        Assert.assertEquals(userList.size(),5);
    }
  • 普通QueryWrapper写法
    @Test
    public void testSelect3() {
        QueryWrapper<UserDO> queryWrapper = Wrappers.query(new UserDO()).le("MBP_ID",10);
        List<UserDO> userList = (List)userMapper.selectObjs(queryWrapper);
        Assert.assertEquals(userList.size(),5);
    }

  上述2中写法的效果是等价的,都是查出User表中id小于等于10的记录;

3.1 LambdaQueryWrapper和QueryWrapper的对比

  QueryWrapper和LambdaQueryWrapper都是MyBatis-Plus中用于构建查询条件的工具,但它们之间存在一些重要的区别:

  • 构建方式:
    • QueryWrapper:使用字符串表示列名,通过字符串拼接的方式构建查询条件,类似于传统的SQL查询。这种方式灵活但不够类型安全。
    • LambdaQueryWrapper:使用Lambda表达式表示列名,通过Lambda表达式的方式构建查询条件。这种方式更加类型安全,可以在编译时捕获拼写错误。
  • 类型安全:
    • QueryWrapper:由于使用字符串表示列名,编译器无法检查列名的正确性,因此存在拼写错误或不合法列名的风险。
    • LambdaQueryWrapper:使用Lambda表达式,可以在编译时捕获属性和字段名的拼写错误,提高了类型安全性。
  • 可读性:
    • QueryWrapper:由于使用字符串,查询条件的构建可能不够清晰和直观,可能需要更多的注释来解释查询条件的含义。
    • LambdaQueryWrapper:Lambda表达式更加直观和清晰,使代码更易于理解,通常不需要额外的注释。
  • 智能提示:
    • QueryWrapper:不同于一些集成开发环境(IDE)提供的智能提示,因为列名是字符串,难以提供准确的自动完成和错误检查。
    • LambdaQueryWrapper:常见的IDE通常能够提供Lambda表达式的智能提示,帮助您更快地编写查询条件。
  • 链式操作:
    • QueryWrapper和LambdaQueryWrapper都支持链式操作,使得构建复杂的查询条件变得更加清晰。

  总的来说,LambdaQueryWrapper相对于QueryWrapper更加类型安全、清晰易读,因此在实际开发中,特别是需要在编译时捕获错误和提高代码可维护性的情况下,更常用。但如果您对查询条件的灵活性有更高的要求,QueryWrapper仍然是一个有效的选择,因为它允许您以字符串的形式构建复杂的查询条件。

3.2 LambdaQueryWrapper的lambda表达式如何转为字段

  testSelect2单元测试中,使用UserDO::getId()来代替MBP_ID字段,它们具体是如何转化的?我们以le(UserDO::getId, 10) 这处代码为切入点,一起看看。
UserDO::getId()是一个SFunction的函数式接口,它集成了Function,Serializable接口,可以认为是一个可序列化的Function

package com.baomidou.mybatisplus.core.toolkit.support;

import java.io.Serializable;
import java.util.function.Function;

/**
 * 支持序列化的 Function
 *
 * @author miemie
 * @since 2018-05-12
 */
@FunctionalInterface
public interface SFunction<T, R> extends Function<T, R>, Serializable {
}

  通过看代码,有一段
在这里插入图片描述

public abstract class AbstractLambdaWrapper<T, Children extends AbstractLambdaWrapper<T, Children>>
    extends AbstractWrapper<T, SFunction<T, ?>, Children> {
 @Override
    protected String columnToString(SFunction<T, ?> column) {
        return columnToString(column, true);
    }

    protected String columnToString(SFunction<T, ?> column, boolean onlyColumn) {
        //使用LambdaUtils.resolve(column),将SFunction转换为SerializedLambda
        return getColumn(LambdaUtils.resolve(column), onlyColumn);
    }

    /**
     * 获取 SerializedLambda 对应的列信息,从 lambda 表达式中推测实体类
     * <p>
     * 如果获取不到列信息,那么本次条件组装将会失败
     *
     * @param lambda     lambda 表达式
     * @param onlyColumn 如果是,结果: "name", 如果否: "name" as "name"
     * @return 列
     * @throws com.baomidou.mybatisplus.core.exceptions.MybatisPlusException 获取不到列信息时抛出异常
     * @see SerializedLambda#getImplClass()
     * @see SerializedLambda#getImplMethodName()
     */
    private String getColumn(SerializedLambda lambda, boolean onlyColumn) throws MybatisPlusException {
        //根据方法名得到Model方法名称得到属性名称
        String fieldName = PropertyNamer.methodToProperty(lambda.getImplMethodName());
        Class<?> aClass = lambda.getInstantiatedType();
        if (!initColumnMap) {
            //LambdaUtils中使用COLUMN_CACHE_MAP全局维护了数据库列定义
            columnMap = LambdaUtils.getColumnMap(aClass);
        }
        Assert.notNull(columnMap, "can not find lambda cache for this entity [%s]", aClass.getName());
        ColumnCache columnCache = columnMap.get(LambdaUtils.formatKey(fieldName));
        Assert.notNull(columnCache, "can not find lambda cache for this property [%s] of entity [%s]",
            fieldName, aClass.getName());
        return onlyColumn ? columnCache.getColumn() : columnCache.getColumnSelect();
    }
}

  从上面的代码中可以看出,大致思路是:
(1)使用LambdaUtils.resolve(column),将SFunction转换为SerializedLambda;
(2)再从SerializedLambda获取方法名称,根据方法名称得到属性名称,最后得到属性对应的列名。

3.2.1 LambdaUtils


/**
 * Lambda 解析工具类
 *
 * @author HCL, MieMie
 * @since 2018-05-10
 */
public final class LambdaUtils {

    /**
     * 字段映射
     */
    private static final Map<String, Map<String, ColumnCache>> COLUMN_CACHE_MAP = new ConcurrentHashMap<>();

    /**
     * SerializedLambda 反序列化缓存
     */
    private static final Map<Class<?>, WeakReference<SerializedLambda>> FUNC_CACHE = new ConcurrentHashMap<>();

    /**
     * 解析 lambda 表达式, 该方法只是调用了 {@link SerializedLambda#resolve(SFunction)} 中的方法,在此基础上加了缓存。
     * 该缓存可能会在任意不定的时间被清除
     *
     * @param func 需要解析的 lambda 对象
     * @param <T>  类型,被调用的 Function 对象的目标类型
     * @return 返回解析后的结果
     * @see SerializedLambda#resolve(SFunction)
     */
    public static <T> SerializedLambda resolve(SFunction<T, ?> func) {
        Class<?> clazz = func.getClass();
        return Optional.ofNullable(FUNC_CACHE.get(clazz))
                .map(WeakReference::get)
                .orElseGet(() -> {
                    SerializedLambda lambda = SerializedLambda.resolve(func);
                    FUNC_CACHE.put(clazz, new WeakReference<>(lambda));
                    return lambda;
                });
    }
    //其它方法省略
}

  LambdaUtils将SerializedLambda存放在缓存FUNC_CACHE中,如果没有,在调用SerializedLambda.resolve()获取。

3.2.2 SerializedLambda

/**
 * 这个类是从 {@link java.lang.invoke.SerializedLambda} 里面 copy 过来的,
 * 字段信息完全一样
 * <p>负责将一个支持序列的 Function 序列化为 SerializedLambda</p>
 *
 * @author HCL
 * @since 2018/05/10
 */
@SuppressWarnings("unused")
public class SerializedLambda implements Serializable {

    private static final long serialVersionUID = 8025925345765570181L;

    private Class<?> capturingClass;
    private String functionalInterfaceClass;
    private String functionalInterfaceMethodName;
    private String functionalInterfaceMethodSignature;
    private String implClass;
    private String implMethodName;
    private String implMethodSignature;
    private int implMethodKind;
    private String instantiatedMethodType;
    private Object[] capturedArgs;

    /**
     * 通过反序列化转换 lambda 表达式,该方法只能序列化 lambda 表达式,不能序列化接口实现或者正常非 lambda 写法的对象
     *
     * @param lambda lambda对象
     * @return 返回解析后的 SerializedLambda
     */
    public static SerializedLambda resolve(SFunction<?, ?> lambda) {
        if (!lambda.getClass().isSynthetic()) {
            throw ExceptionUtils.mpe("该方法仅能传入 lambda 表达式产生的合成类");
        }
        try (ObjectInputStream objIn = new ObjectInputStream(new ByteArrayInputStream(SerializationUtils.serialize(lambda))) {
            @Override
            protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
                //这个类是从 {@link java.lang.invoke.SerializedLambda} 里面 copy 过来的,字段信息完全一样
                Class<?> clazz = super.resolveClass(objectStreamClass);
                return clazz == java.lang.invoke.SerializedLambda.class ? SerializedLambda.class : clazz;
            }
        }) {
            return (SerializedLambda) objIn.readObject();
        } catch (ClassNotFoundException | IOException e) {
            throw ExceptionUtils.mpe("This is impossible to happen", e);
        }
    }
   //其它方法省略
}

  SerializedLambda.resolve()仅能处理lambda 表达式产生的合成类,主要是先将SFunction序列化,然后在反序列化的时候将java.lang.invoke.SerializedLambda转换为com.baomidou.mybatisplus.core.toolkit.support.SerializedLambda。其实这里直接使用jdk本身提供的java.lang.invoke.SerializedLambda也可以,作者此处如此处理,估计是为了加强自己的控制。

3.2.3 SFcuntion转为SerializedLambda的原理

  我们知道实现了序列化接口的java对象是可以被序列化的(用于IO传输、持久化等),但是真正被序列化的其实只有对象的属性,而方法(即函数)不能被序列化,可lamba表达式实际上是一个函数(函数式编程),那么“函数”通过什么方式来序列化呢? Java提供了一种机制,会将实现了Serializable接口的lambda表达式转换成 SerializedLambda 对象之后再去做序列化。
  虚拟机在调用write(obj)序列化对象前,如果被序列化的对象有writeReplace方法,则会先调用该方法,用该方法返回的SerializedLambda对象去做序列化,即被序列化的对象被替换了。

  根据这个原理,lambda表达式UserDO::getId在序列化前也会调用writeReplace(),然后返回一个SerializedLambda 对象(真正的被序列化的对象),该对象中包含了lambda表达式的所有信息,比如函数名implMethodName、函数签名implMethodSignature等等,由于这些信息都是以字段形式存在的,因此可以被序列化,这样就解决了函数无法被序列化的问题。
既然在序列化对象时虚拟机可以调用writeReplace()方法,那么我们也可以通过反射的方式来手动调用writeReplace()方法,返回 SerializedLambda对象,然后再调用serializedLambda.getImplMethodName()得到表达式中的方法名getId,从而实现了根据表达式UserDO::getId得到字段名"id"的转换.

@Test
    public void testLambda(){
        SampleTest.getPropertyName(UserDO::getName);
    }


    public static <T,R> String getPropertyName(SFunction<T,R> lambda) {
        try {
            Class lambdaClass=lambda.getClass();
            System.out.println("-------------分割线1-----------");
            //打印类名:
            System.out.print("类名:");
            System.out.println(lambdaClass.getName());
            //打印接口名:
            System.out.print("接口名:");
            Arrays.stream(lambdaClass.getInterfaces()).forEach(System.out::print);
            System.out.println();
            //打印方法名:
            System.out.print("方法名:");
            for (Method method : lambdaClass.getDeclaredMethods()) {
                System.out.print(method.getName()+"  ");
            }

            System.out.println();
            System.out.println("-------------分割线2-----------");
            System.out.println();

            Method method = lambdaClass.getDeclaredMethod("writeReplace");
            method.setAccessible(Boolean.TRUE);
            SerializedLambda serializedLambda = (SerializedLambda) method.invoke(lambda);
            String getterMethod = serializedLambda.getImplMethodName();
            System.out.println("lambda表达式调用的方法名:"+getterMethod);
            String fieldName = Introspector.decapitalize(getterMethod.replace("get", ""));
            System.out.println("根据方法名得到的字段名:"+fieldName);

            return fieldName;

        } catch (ReflectiveOperationException e) {
            throw new RuntimeException(e);
        }
    }

测试结果

-------------分割线1-----------
类名:com.wp.mybatis.plus.test.SampleTest$$Lambda$669/736888459
接口名:interface com.baomidou.mybatisplus.core.toolkit.support.SFunction
方法名:apply  writeReplace  
-------------分割线2-----------

lambda表达式调用的方法名:getId
根据方法名得到的字段名:id
  • 27
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值