MyBatis-Plus 源码解说

归档

总说明

单元测试

添加方法-UT

  • 参考:com.baomidou.mybatisplus.core.MybatisXMLConfigBuilderTest
java 复制代码
    @Test
    void parse() throws IOException {
        ResourceLoader loader = new DefaultResourceLoader();
        Resource resource = loader.getResource("classpath:/MybatisXMLConfigBuilderTest.xml");
        MybatisXMLConfigBuilder builder = new MybatisXMLConfigBuilder(resource.getInputStream(), null); // ref: sign_c_110 | sign_cm_110
        Configuration configuration = builder.parse(); // ref: sign_m_110
        MappedStatement mappedStatement = configuration.getMappedStatement(..."EntityMapper.selectCount");
    }

    interface EntityMapper extends BaseMapper<Entity> { }

    @TableName(autoResultMap = true)
    static class Entity {
        private Long id;
        private String name;
    }
  • MybatisXMLConfigBuilderTest.xml
xml 复制代码
<configuration>
    <mappers>
        <mapper class="com.baomidou.mybatisplus.core.MybatisXMLConfigBuilderTest$EntityMapper"/>
    </mappers>
</configuration>

完整测试

  • 参考:com.baomidou.mybatisplus.test.MybatisTest
java 复制代码
    private static SqlSessionFactory sqlSessionFactory;

    @BeforeAll
    public static void init() throws IOException, SQLException {
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
        sqlSessionFactory = new MybatisSqlSessionFactoryBuilder().build(reader);    // 关键部分

        Configuration configuration = sqlSessionFactory.getConfiguration();
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        typeHandlerRegistry.register(AgeEnum.class, MybatisEnumTypeHandler.class);  // 注册枚举类型处理器

        DataSource dataSource = sqlSessionFactory.getConfiguration().getEnvironment().getDataSource();
        Connection connection = dataSource.getConnection();                         // 获取连接执行脚本
        ScriptRunner scriptRunner = new ScriptRunner(connection);
        scriptRunner.runScript(Resources.getResourceAsReader("h2/user.ddl.sql"));
    }

    @Test
    void test() {
        try (SqlSession sqlSession = sqlSessionFactory.openSession(true)) {
            H2UserMapper mapper = sqlSession.getMapper(H2UserMapper.class); // 添加 Mapper, ref: sign_m_130
            mapper.myInsertWithNameVersion("test", 2);
            H2User h2User = new H2User(...);
            mapper.insert(h2User);
        }
    }

Spring 测试

  • 参考:com.baomidou.mybatisplus.test.h2.H2StudentMapperTest
java 复制代码
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@ExtendWith(SpringExtension.class)
@ContextConfiguration(locations = {"classpath:h2/spring-test-h2.xml"})
class H2StudentMapperTest {
    @Resource
    protected H2StudentMapper studentMapper;

    @Test
    void testIn() {
        LambdaQueryWrapper<H2Student> wrapper = Wrappers.<H2Student>lambdaQuery()
            .in(H2Student::getName, Arrays.asList("a", "b"));
        List<H2Student> list = studentMapper.selectList(wrapper);
    }
}

原理

添加方法

java 复制代码
// sign_c_110
// 从 MyBatis 的 XMLConfigBuilder 复制过来的, 使用自己的 MybatisConfiguration 而不是 Configuration
public class MybatisXMLConfigBuilder extends BaseBuilder {
    // sign_cm_110
    private MybatisXMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new MybatisConfiguration()); // 传自己的 MybatisConfiguration 实例,ref: sign_c_120
        ...
    }

    // sign_m_110
    public Configuration parse() {
        ...
        parseConfiguration(parser.evalNode("/configuration")); // ref: sign_m_111
        return configuration;
    }

    // sign_m_111
    private void parseConfiguration(XNode root) {
        try {
            // (类似的) 参考:[MyBatis-创建-SqlSessionFactory sign_m_121]
            ...
            mapperElement(root.evalNode("mappers")); // ref: sign_m_112
        } ... // catch
    }

    // sign_m_112  解析 Mapper 配置
    private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
            for (XNode child : parent.getChildren()) {
                if ("package".equals(child.getName())) {
                    ...
                } else {
                    String resource = child.getStringAttribute("resource");
                    String url = child.getStringAttribute("url");
                    String mapperClass = child.getStringAttribute("class");
                    if (resource != null && url == null && mapperClass == null) {
                        ...
                    } else if (resource == null && url != null && mapperClass == null) {
                        ...
                    } else if (resource == null && url == null && mapperClass != null) {
                        Class<?> mapperInterface = Resources.classForName(mapperClass);
                        configuration.addMapper(mapperInterface); // 注册 Mappger, ref: sign_m_120
                    } ... // else {}
                }
            }
        }
    }
}
  • com.baomidou.mybatisplus.core.MybatisConfiguration
java 复制代码
// sign_c_120 替换默认的 Configuration 类
public class MybatisConfiguration extends Configuration {
    // Mapper 注册
    protected MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this); // ref: sign_c_130 | sign_cm_130

    // sign_m_120  注册 Mappger
    @Override
    public <T> void addMapper(Class<T> type) {
        mybatisMapperRegistry.addMapper(type); // ref: sign_m_130
    }
}
java 复制代码
// sign_c_130  继承自 MapperRegistry
public class MybatisMapperRegistry extends MapperRegistry {
    private Configuration config; // 实际是 MybatisConfiguration 实例,ref: sign_c_120
    private Map<Class<?>, MybatisMapperProxyFactory<?>> knownMappers = new ConcurrentHashMap<>();

    // sign_cm_130
    public MybatisMapperRegistry(Configuration config) {
        super(config);
        this.config = config;
    }

    // sign_m_130
    @Override
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            ...
            try {
                knownMappers.put(type, new MybatisMapperProxyFactory<>(type)); // 会在 getMapper(T) 时调用,参考:[MyBatis-获取-Mapper sign_m_330]
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type); // ref: sign_c_140 | sign_cm_140
                parser.parse(); // ref: sign_m_140
            } ... // finally {}
        }
    }
}
  • com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder
java 复制代码
// sign_c_140
public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
    private static Set<Class<? extends Annotation>> statementAnnotationTypes = 
        Stream.of(
            Select.class, Update.class, Insert.class, Delete.class, 
            SelectProvider.class, UpdateProvider.class, InsertProvider.class, DeleteProvider.class
        ).collect(Collectors.toSet());
    
    // sign_cm_140
    public MybatisMapperAnnotationBuilder(Configuration configuration, Class<?> type) {
        super(configuration, type); // configuration 为 MybatisConfiguration 实例,ref: sign_c_120
        this.assistant = new MybatisMapperBuilderAssistant(configuration, resource);
        ...
    }

    // sign_m_140
    @Override
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            ... // MyBatis 那套解析逻辑
            try {
                if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) { // 判断 type 是不是继承 Mapper 接口
                    // 自己继承 BaseMapper,就会进入此逻辑
                    parserInjector(); // 注入基础方法,ref: sign_m_141
                }
            } ... // catch
        }
        ...
    }

    // sign_m_141 注入基础方法
    void parserInjector() {
        GlobalConfigUtils.getSqlInjector(configuration) // 返回 DefaultSqlInjector 实例, ref: sign_c_150
            .inspectInject(assistant, type); // 解析 BaseMapper 基础方法,ref: sign_m_160
    }
}
  • com.baomidou.mybatisplus.core.injector.DefaultSqlInjector
java 复制代码
// sign_c_150  SQL 默认注入器
public class DefaultSqlInjector extends AbstractSqlInjector {
    // sign_m_150
    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass, TableInfo tableInfo) {
        Stream.Builder<AbstractMethod> builder = Stream.<AbstractMethod>builder()
            .add(new Insert())
            .add(new Delete())
            .add(new Update())
            .add(new SelectCount()) // ref: sign_c_180
            .add(new SelectMaps())
            .add(new SelectObjs())
            .add(new SelectList());
        if (tableInfo.havePK()) {
            builder.add(new DeleteById())
                .add(new DeleteBatchByIds())
                .add(new UpdateById())
                .add(new SelectById())
                .add(new SelectBatchByIds());
        } ... // else
        return builder.build().collect(toList());
    }
}
  • com.baomidou.mybatisplus.core.injector.AbstractSqlInjector
java 复制代码
// sign_c_160  SQL 自动注入器
public abstract class AbstractSqlInjector implements ISqlInjector {
    // sign_m_160  将 BaseMapper 的基础方法转成 SQL 语句注入到 configuration (MyBatis) 中去
    @Override
    public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
        Class<?> modelClass = ReflectionKit.getSuperClassGenericType(mapperClass, Mapper.class, 0); // 获取实体类
        if (modelClass != null) {
            ... // 是否已注册判断处理

                // 初始化表信息(读取:表名、主键、字段等)
                TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
                List<AbstractMethod> methodList = this.getMethodList(mapperClass, tableInfo); // ref: sign_m_150
                if (CollectionUtils.isNotEmpty(methodList)) {
                    // 循环注入自定义方法
                    methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo)); // ref: sign_m_170
                } ... // else
        }
    }
}
java 复制代码
public abstract class AbstractMethod implements Constants {
    // sign_m_170 注入自定义方法
    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); // 以 SelectCount 为例,ref: sign_c_180 | sign_m_180
    }

    // sign_m_171
    protected MappedStatement addSelectMappedStatementForOther(Class<?> mapperClass, String id, SqlSource sqlSource,
                                                               Class<?> resultType) {
        return addMappedStatement( // ref: sign_m_172
            mapperClass, id, sqlSource, SqlCommandType.SELECT, ...
        ); 
    }

    // sign_m_172  添加 MappedStatement 到 Mybatis 容器
    protected MappedStatement addMappedStatement(
        Class<?> mapperClass, String id, SqlSource sqlSource,
        SqlCommandType sqlCommandType, ...
    ) {
        ... // 判断是否已添加处理
        // 构建 MappedStatement 并添加到 configuration
        return builderAssistant.addMappedStatement(...); // 参考:[MyBatis-创建-SqlSessionFactory sign_m_150]
    }
}
  • com.baomidou.mybatisplus.core.injector.methods.SelectCount
java 复制代码
// sign_c_180
public class SelectCount extends AbstractMethod {
    // sign_m_180  拼接 SQL 语句注入到 configuration (MyBatis) 中去
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        SqlMethod sqlMethod = SqlMethod.SELECT_COUNT;
        String sql = String.format(
            sqlMethod.getSql(), // "<script>%s SELECT COUNT(%s) AS total FROM %s %s %s\n</script>"
            /*
            <if test="ew != null and ew.sqlFirst != null">
                ${ew.sqlFirst}
            </if>
            */
            sqlFirst(),
            /*
            <choose>
                <when test="ew != null and ew.sqlSelect != null">
                    ${ew.sqlSelect}
                </when>
                <otherwise>*</otherwise>
            </choose>
            */
            sqlCount(), 
            tableInfo.getTableName(), // 表名:entity
            /*
            <if test="ew != null">
                <bind name="_sgEs_" value="ew.sqlSegment != null and ew.sqlSegment != ''"/>
                <where>
                    <if test="ew.entity != null">
                        <if test="ew.entity.id != null">id=#{ew.entity.id}</if>
                        <if test="ew.entity['name'] != null"> AND name=#{ew.entity.name}</if>
                    </if>
                    <if test="_sgEs_ and ew.nonEmptyOfNormal">
                        AND ${ew.sqlSegment}
                    </if>
                </where>
                <if test="_sgEs_ and ew.emptyOfNormal">
                    ${ew.sqlSegment}
                </if>
            </if>
            */
            sqlWhereEntityWrapper(true, tableInfo),
            /*
            <if test="ew != null and ew.sqlComment != null">
                ${ew.sqlComment}
            </if>
            */
            sqlComment()
        );
        SqlSource sqlSource = super.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatementForOther(mapperClass, methodName, sqlSource, Long.class); // ref: sign_m_171
    }
}
  • com.baomidou.mybatisplus.core.injector.methods.SelectList
java 复制代码
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        SqlMethod sqlMethod = SqlMethod.SELECT_COUNT;
        String.format(
            sqlMethod.getSql(), // "<script>%s SELECT %s FROM %s %s %s %s\n</script>"
            /*
            <if test="ew != null and ew.sqlFirst != null">
                ${ew.sqlFirst}
            </if>
            */
            sqlFirst(), 
            /*
            <choose>
                <when test="ew != null and ew.sqlSelect != null">
                    ${ew.sqlSelect}
                </when>
                <otherwise>test_id,name,age,price,test_type,`desc`,version,deleted,last_updated_dt</otherwise>
            </choose>
            */
            sqlSelectColumns(tableInfo, true), 
            tableInfo.getTableName(), // 表名:entity
            /*
            <where>
                deleted=0
                <if test="ew != null">
                    <bind name="_sgEs_" value="ew.sqlSegment != null and ew.sqlSegment != ''"/>
                    <if test="ew.entity != null">
                        <if test="ew.entity.testId != null"> AND test_id=#{ew.entity.testId}</if>
                        <if test="ew.entity['name'] != null"> AND name=#{ew.entity.name}</if>
                        <if test="ew.entity['age'] != null"> AND age=#{ew.entity.age}</if>
                        <if test="ew.entity['price'] != null"> AND price=#{ew.entity.price}</if>
                        <if test="ew.entity['testType'] != null"> AND test_type=#{ew.entity.testType}</if>
                        <if test="ew.entity['desc'] != null"> AND `desc`=#{ew.entity.desc}</if>
                        <if test="ew.entity['testDate'] != null"> AND test_date=#{ew.entity.testDate}</if>
                        <if test="ew.entity['version'] != null"> AND version=#{ew.entity.version}</if>
                        <if test="ew.entity['lastUpdatedDt'] != null"> AND last_updated_dt=#{ew.entity.lastUpdatedDt}</if>
                    </if>
                    <if test="_sgEs_ and ew.nonEmptyOfNormal">
                        AND ${ew.sqlSegment}
                    </if>
                    <if test="_sgEs_ and ew.emptyOfNormal">
                        ${ew.sqlSegment}
                    </if>
                </if>
            </where>
            */
            sqlWhereEntityWrapper(true, tableInfo), 
            sqlOrderBy(tableInfo), // 空
            /*
            <if test="ew != null and ew.sqlComment != null">
                ${ew.sqlComment}
            </if>
            */
            sqlComment()
        );
        SqlSource sqlSource = super.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatementForOther(mapperClass, methodName, sqlSource, Long.class); // ref: sign_m_171
    }
总结
  • 大部分扩展自 MyBatis 已有类,扩展的类使用 Mybatis 前缀

Spring 集成

  • 单测关键类:
    • com.baomidou.mybatisplus.test.h2.config.DBConfig
    • com.baomidou.mybatisplus.test.h2.config.MybatisPlusConfig
  • Spring-Boot 关键类:
    • com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
    • 数据源(连接池)通过 # sqlSessionFactory(DataSource) 方法设置
  • 注解扫描:

SQL 格式化

  • org.apache.ibatis.executor.CachingExecutor #query(MS, p, RB, RH) 调用
  • org.apache.ibatis.mapping.MappedStatement #getBoundSql 主要逻辑在这里
相关推荐
听见~1 小时前
MyBatisPlus
mybatis
向阳12186 小时前
mybatis SqlSessionFactory
java·mybatis
Suwg2096 小时前
《手写Mybatis渐进式源码实践》实践笔记(第七章 SQL执行器的创建和使用)
java·数据库·笔记·后端·sql·mybatis·模板方法模式
秋恬意8 小时前
MyBatis动态 SQL 的执行原理
mybatis
青年有志11 小时前
深入浅出 MyBatis | CRUD 操作、配置解析
数据库·tomcat·mybatis
00Allen0011 小时前
mybatis/mybatisplus
java·spring·mybatis
Echo flower11 小时前
mybatis-plus自动填充时间的配置类实现
java·数据库·mybatis
编码浪子12 小时前
Springboot3国际化
java·spring·mybatis
Yan.love13 小时前
【MyBatis 核心工作机制】注解式开发与动态代理原理
java·mybatis
油丶酸萝卜别吃1 天前
MyBatis中XML文件的模板
xml·数据库·mybatis