In-depth analysis of Mybatis Plus dynamic SQL statement implementation mechanism

Mybatis-Plus (MP for short) is a Mybatis enhancement tool, so how is it enhanced? The fact is that it already encapsulates some CRUD methods, so development doesn’t need to write XML, just call these methods, similar to JPA. This article will look at the following implementation of MP to see how these enhancements are implemented.

Entrance class: MybatisSqlSessionFactoryBuilder

Through the entrance class MybatisSqlSessionFactoryBuilder# build method, the application starts up, the mybatis plus (MP) custom dynamic configuration XML file into mybatis.

public class MybatisSqlSessionFactoryBuilder extends SqlSessionFactoryBuilder {
    public SqlSessionFactory build(Configuration configuration) {
            / /... Omit several lines
            if (globalConfig.isEnableSqlRunner()) {
                new SqlRunnerInjector().inject(configuration);
            }
            / /... Omit several lines
            returnsqlSessionFactory; }}Copy the code

There are two MP2 function classes involved

  1. MybatisConfiguration extends MybatisConfiguration class inherited from Mybatis: MP dynamic script building, registration, and other logical judgments.
  2. SqlRunnerInjector: MP XML script method that inserts some dynamic methods by default.

MybatisConfiguration class

Here we focus on the analysis of MybatisConfiguration class, in MybatisConfiguration, MP initializes its own MybatisMapperRegistry, MybatisMapperRegistry is the registry for MP to load custom SQL methods.

Many of the methods in MybatisConfiguration are implemented using the MybatisMapperRegistry rewrite

Three overloaded methods, addMapper, implement the function of registering MP dynamic scripts.

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

    /** * initializes the call */
    public MybatisConfiguration(a) {
        super(a);this.mapUnderscoreToCamelCase = true;
        languageRegistry.setDefaultDriverClass(MybatisXMLLanguageDriver.class);
    }

    /** * MybatisPlus; SQL 

*

2. SQL

*

3. XmlSql and SqlProvider cannot contain the same SQL

*

after adjustment SQL priority: XmlSql > sqlProvider > CurdSql

*/
@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 } Copy the code

In MybatisMapperRegistry, MP will mybatis MapperAnnotationBuilder replaced with MP own MybatisMapperAnnotationBuilder

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}}Copy the code

In MybatisMapperRegistry addMapper method of a class, real MybatisMapperAnnotationBuilder into MP at the core of the class, MybatisMapperAnnotationBuilder this class is the key to achieve dynamic script MP classes.

MybatisMapperAnnotationBuilder dynamically constructed

Core classes in MP MybatisMapperAnnotationBuilder parser method, MP one traversal Mapper classes to be loaded, loaded method includes the following

public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
    @Override
    public void parse(a) {
        / /... Omit several lines
        for (Method method : type.getMethods()) {
            /** for loop code, MP check method is not@Select @InsertEtc mybatis annotation method **/
            parseStatement(method);
            InterceptorIgnoreHelper.initSqlParserInfoCache(cache, mapperName, method);
            SqlParserHelper.initSqlParserInfoCache(mapperName, method);
        }
        /** These two 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 to inject custom methodsmethodList.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
            newSelectPage() ).collect(toList()); }}Copy the code

In MybatisMapperAnnotationBuilder, MP real frame custom dynamic SQL statements to registration to Mybatis engine. AbstractMethod, on the other hand, implements SQL statement construction for concrete methods.

Concrete AbstractMethod instance classes that construct concrete method SQL statements

Take the SelectById class as an example

/** * Query data by ID */
public class SelectById extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class
        mapperClass, Class
        modelClass, TableInfo tableInfo) {
        /** define mybatis XML method id as 
      
        **/
      ="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 mybatis MappedStatement **/
        return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo); }}Copy the code

Mybatis ${variable} #{variable} dynamic replacement and precompilation, has entered mybatis own function.

To summarize

MP rewrites and replaces more than ten classes of Mybatis in total, as shown in the following figure:

The increase of the overall, MP implementation mybatis means slightly complicated and not intuitive, but according to MybatisMapperAnnotationBuilder structural approach from the definition of the XML file, transform it into mybatis Resource resources, Mybatis class: SqlSessionFactoryBean

public class YourSqlSessionFactoryBean extends SqlSessionFactoryBean implements ApplicationContextAware {

    private Resource[] mapperLocations;

    @Override
    public void setMapperLocations(Resource... mapperLocations) {
        super.setMapperLocations(mapperLocations);
        /** Mapper XML file path **/
        this.mapperLocations = mapperLocations;
    }

    / * * * {@inheritDoc} * /
    @Override
    public void afterPropertiesSet(a) throws Exception {
        ConfigurableListableBeanFactory beanFactory = getBeanFactory();
        /** Mybatis can implement the symbiotic relationship between MP's custom dynamic SQL and native SQL **/
        this.setMapperLocations(InjectMapper.getMapperResource(this.dbType, beanFactory, this.mapperLocations));
        super.afterPropertiesSet(); }}Copy the code

In this article, the realization process of MP dynamic statement is briefly introduced, and a possible more convenient method is given. In the next article, we will introduce fluent Mybatis, another myBatis enhancement framework, how to achieve dynamic statements without rewriting any system methods of Mybatis.

Fluent Mybatis introduction and source code

Using FluentMybatis to achieve MyBatis dynamic SQL assembly and Fluent API syntax

Complex nested queries using Fluent Mybatis zero XML configuration

Fluent Mybatis source code, Github