MyBatis-Plus 源码解说

作者 : admin 本文共11004个字,预计阅读时间需要28分钟 发布时间: 2024-06-17 共1人阅读

归档

  • GitHub: MyBatis-Plus-源码解说

总说明

  • 源码仓库: https://github.com/baomidou/mybatis-plus
  • 克隆:git clone https://github.com/baomidou/mybatis-plus.git
  • 切分支(tag):git checkout 3.0
  • JDK: 21

单元测试

添加方法-UT

  • 参考:com.baomidou.mybatisplus.core.MybatisXMLConfigBuilderTest
    @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
<configuration>
    <mappers>
        <mapper class="com.baomidou.mybatisplus.core.MybatisXMLConfigBuilderTest$EntityMapper"/>
    </mappers>
</configuration>

完整测试

  • 参考:com.baomidou.mybatisplus.test.MybatisTest
    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
@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);
    }
}

原理

添加方法

  • com.baomidou.mybatisplus.core.MybatisXMLConfigBuilder
    • 参考:MyBatis-创建-SqlSessionFactory sign_m_121
// 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
// 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
}
}
  • com.baomidou.mybatisplus.core.MybatisMapperRegistry
    • 参考:MyBatis-获取-Mapper sign_m_330
// 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
// 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
// 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
// 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
}
}
}
  • com.baomidou.mybatisplus.core.injector.AbstractMethod
    • 参考:MyBatis-创建-SqlSessionFactory sign_m_150
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
    • SQL 格式化参考:#SQL-格式化
// 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(), // ""
/*
${ew.sqlFirst}
*/
sqlFirst(),
/*
${ew.sqlSelect}
*
*/
sqlCount(), 
tableInfo.getTableName(), // 表名:entity
/*
id=#{ew.entity.id}
AND name=#{ew.entity.name}
AND ${ew.sqlSegment}
${ew.sqlSegment}
*/
sqlWhereEntityWrapper(true, tableInfo),
/*
${ew.sqlComment}
*/
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
    • SQL 格式化参考:#SQL-格式化
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.SELECT_COUNT;
String.format(
sqlMethod.getSql(), // ""
/*
${ew.sqlFirst}
*/
sqlFirst(), 
/*
${ew.sqlSelect}
test_id,name,age,price,test_type,`desc`,version,deleted,last_updated_dt
*/
sqlSelectColumns(tableInfo, true), 
tableInfo.getTableName(), // 表名:entity
/*
deleted=0
AND test_id=#{ew.entity.testId}
AND name=#{ew.entity.name}
AND age=#{ew.entity.age}
AND price=#{ew.entity.price}
AND test_type=#{ew.entity.testType}
AND `desc`=#{ew.entity.desc}
AND test_date=#{ew.entity.testDate}
AND version=#{ew.entity.version}
AND last_updated_dt=#{ew.entity.lastUpdatedDt}
AND ${ew.sqlSegment}
${ew.sqlSegment}
*/
sqlWhereEntityWrapper(true, tableInfo), 
sqlOrderBy(tableInfo), // 空
/*
${ew.sqlComment}
*/
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) 方法设置
  • 注解扫描:
    • 示例 @MapperScan("com.baomidou.mybatisplus.test.h2.mapper")
    • 参考:MyBatis-Spring-集成

SQL 格式化

  • org.apache.ibatis.executor.CachingExecutor #query(MS, p, RB, RH) 调用
  • org.apache.ibatis.mapping.MappedStatement #getBoundSql 主要逻辑在这里
本站无任何商业行为
个人在线分享 » MyBatis-Plus 源码解说
E-->