Principle of Mybatis Plus dynamic SQL generation

Posted by servo on Mon, 06 Dec 2021 03:23:46 +0100

         Mybatis plus (MP) is an enhancement tool for mybatis, so how is it enhanced? In fact, it has encapsulated some crud methods, so there is no need to write xml for development. Just call these methods directly, which is similar to JPA. The structure diagram of mybatis plus is as follows:

Entry class: MybatisSqlSessionFactoryBuilder
         In the MybatisSqlSessionFactoryBuilder#build method of the entry class, the dynamic configuration xml file customized by Mybatis plus (MP) is injected into Mybatis when the application starts.

public class MybatisSqlSessionFactoryBuilder extends SqlSessionFactoryBuilder {
    public SqlSessionFactory build(Configuration configuration) {
            // ... omit several lines
            if (globalConfig.isEnableSqlRunner()) {
                new SqlRunnerInjector().inject(configuration);
            }
            // ... omit several lines
            return sqlSessionFactory;
        }
}

Two MP2 function classes are involved here

  • Extend the MybatisConfiguration class inherited from Mybatis:
    MP dynamic script construction, registration, and other logical judgment. Search for official account, Internet architect reply "2T", send you a surprise gift bag.
  • SqlRunnerInjector: MP inserts some xml script methods of dynamic methods by default.

MybatisConfiguration class
         Here, we focus on the MybatisConfiguration class. In MybatisConfiguration, MP initializes its own MybatisMapperRegistry, which is the registrar for MP to load custom SQL methods.
         Many methods in MybatisConfiguration are rewritten using MybatisMapperRegistry.
         Three overloaded methods addMapper implement the function of registering MP dynamic scripts.

public class MybatisConfiguration extends Configuration {
    /**
     * Mapper register
     */
    protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
    // ....

    /**
     * Initialization call
     */
    public MybatisConfiguration() {
        super();
        this.mapUnderscoreToCamelCase = true;
        languageRegistry.setDefaultDriverClass(MybatisXMLLanguageDriver.class);
    }
    /**
     * MybatisPlus Load SQL sequence:
     * <p> 1,Load SQL in XML</p>
     * <p> 2,Load SQL in SqlProvider</p>
     * <p> 3,XmlSql Cannot contain the same SQL as SqlProvider</p>
     * <p>Adjusted SQL priority: XMLSQL > sqlprovider > curdsql</p>
     */
    @Override
    public void addMappedStatement(MappedStatement ms) {
        // ...
    }
    // ... omit several lines
    /**
     * Use your own MybatisMapperRegistry
     */
    @Override
    public <T> void addMapper(Class<T> type) {
        mybatisMapperRegistry.addMapper(type);
    }
    // ... omit several lines
}

         In mybatismappperregistry, MP replaces mybatis's MapperAnnotationBuilder with MP's own mybatismappperannotationbuilder

public class MybatisMapperRegistry extends MapperRegistry {
    @Override
    public <T> void addMapper(Class<T> type) {
        // ... omit several lines
        MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
        parser.parse();
        // ... omit several lines
    }
}

         In the addMapper method of the MybatisMapperRegistry class, you can really enter the core class of MP, mybatismappperannotationbuilder, which is the key class for MP to implement dynamic scripts.

Dynamic construction of mybatismappperannotationbuilder
         In the parser method of MP's core class mybatismappperannotationbuilder, MP traverses the Mapper classes to be loaded one by one. The loading methods include the following

public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
    @Overrde
    public void parse() {
        //... omit several lines
        for (Method method : type.getMethods()) {
            /** for Loop code, and MP judges whether the method method is a mybatis annotation method such as @ Select @Insert**/
            parseStatement(method);
            InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);
            SqlParserHelper.initSqlParserInfoCache(mapperName, method);
        }
        /** In these 2 lines of code, MP injects the default method list**/
        if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
            GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
        }
        //... omit several lines
    }

    @Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        Class<?> modelClass = extractModelClass(mapperClass);
        //... omit several lines
        List<AbstractMethod> methodList = this.getMethodList(mapperClass);
        TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
        // Loop injection custom method
        methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
        mapperRegistryCache.add(className);
    }
}
public class DefaultSqlInjector extends AbstractSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        return Stream.of(
            new Insert(),
            //... omit several lines
            new SelectPage()
        ).collect(toList());
    }
}

         In Mybatis mapperannotationbuilder, MP really registers the dynamic SQL statements customized by the framework into the Mybatis engine. AbstractMethod implements the SQL statement construction of specific methods.

Specific AbstractMethod instance class to construct specific method SQL statements
         Take the SelectById class as an example

/**
 * Query a piece of data by ID
 */
public class SelectById extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        /** Define mybatis xml method id, corresponding to < id = "XYZ" >**/
        SqlMethod sqlMethod = SqlMethod.SELECT_BY_ID;
        /** Construct the specific xml fragment corresponding to the id**/
        SqlSource sqlSource = new RawSqlSource(configuration, String.format(sqlMethod.getSql(),
            sqlSelectColumns(tableInfo, false),
            tableInfo.getTableName(), tableInfo.getKeyColumn(), tableInfo.getKeyProperty(),
            tableInfo.getLogicDeleteSql(true, true)), Object.class);
        /** Add the xml method method to the MappedStatement in mybatis**/
        return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
    }
}

         So far, MP has completed the process of loading custom method xml configuration at startup, followed by the dynamic replacement and precompiling of mybatis ${variable} #{variable}, which has entered mybatis's own functions.

To sum up
         MP rewrites and replaces more than ten classes of mybatis, as shown in the following figure:

         Generally speaking, MP implements the enhancement of mybatis, which is a little cumbersome and not intuitive. In fact, according to mybatismappperannotationbuilder, it constructs the xml file of custom method and converts it into mybatis Resource resources. You can inherit and rewrite only one mybatis class: SqlSessionFactoryBean, for example:

public class YourSqlSessionFactoryBean extends SqlSessionFactoryBean implements ApplicationContextAware {

    private Resource[] mapperLocations;
    
    @Override
    public void setMapperLocations(Resource... mapperLocations) {
        super.setMapperLocations(mapperLocations);
        /** Save the mapper xml file path defined natively by mybatis**/
        this.mapperLocations = mapperLocations;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        /** You only need to construct a custom method into xml resource and inject it into mybatis together with the native defined Resource. In this way, you can realize the symbiotic relationship between MP's custom dynamic SQL and native SQL**/
        this.setMapperLocations(InjectMapper.getMapperResource(this.dbType, beanFactory, this.mapperLocations));
        super.afterPropertiesSet();
    }
}

         In this article, the implementation process of MP dynamic statement is briefly introduced, and a possible more convenient method is given.

Topics: Java SQL Spring Cloud mybatis-plus