归档
总说明
- 源码仓库: 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);
Configuration configuration = builder.parse();
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.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
public class MybatisXMLConfigBuilder extends BaseBuilder {
private MybatisXMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new MybatisConfiguration());
...
}
public Configuration parse() {
...
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
private void parseConfiguration(XNode root) {
try {
...
mapperElement(root.evalNode("mappers"));
} ...
}
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);
} ...
}
}
}
}
}
com.baomidou.mybatisplus.core.MybatisConfiguration
public class MybatisConfiguration extends Configuration {
protected MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
@Override
public <T> void addMapper(Class<T> type) {
mybatisMapperRegistry.addMapper(type);
}
}
com.baomidou.mybatisplus.core.MybatisMapperRegistry
public class MybatisMapperRegistry extends MapperRegistry {
private Configuration config;
private Map<Class<?>, MybatisMapperProxyFactory<?>> knownMappers = new ConcurrentHashMap<>();
public MybatisMapperRegistry(Configuration config) {
super(config);
this.config = config;
}
@Override
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
...
try {
knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
} ...
}
}
}
com.baomidou.mybatisplus.core.MybatisMapperAnnotationBuilder
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());
public MybatisMapperAnnotationBuilder(Configuration configuration, Class<?> type) {
super(configuration, type);
this.assistant = new MybatisMapperBuilderAssistant(configuration, resource);
...
}
@Override
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
...
try {
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
parserInjector();
}
} ...
}
...
}
void parserInjector() {
GlobalConfigUtils.getSqlInjector(configuration)
.inspectInject(assistant, type);
}
}
com.baomidou.mybatisplus.core.injector.DefaultSqlInjector
public class DefaultSqlInjector extends AbstractSqlInjector {
@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())
.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());
} ...
return builder.build().collect(toList());
}
}
com.baomidou.mybatisplus.core.injector.AbstractSqlInjector
public abstract class AbstractSqlInjector implements ISqlInjector {
@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);
if (CollectionUtils.isNotEmpty(methodList)) {
methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
} ...
}
}
}
com.baomidou.mybatisplus.core.injector.AbstractMethod
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);
}
protected MappedStatement addSelectMappedStatementForOther(Class<?> mapperClass, String id, SqlSource sqlSource,
Class<?> resultType) {
return addMappedStatement(
mapperClass, id, sqlSource, SqlCommandType.SELECT, ...
);
}
protected MappedStatement addMappedStatement(
Class<?> mapperClass, String id, SqlSource sqlSource,
SqlCommandType sqlCommandType, ...
) {
...
return builderAssistant.addMappedStatement(...);
}
}
com.baomidou.mybatisplus.core.injector.methods.SelectCount
public class SelectCount extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.SELECT_COUNT;
String sql = String.format(
sqlMethod.getSql(),
sqlFirst(),
sqlCount(),
tableInfo.getTableName(),
sqlWhereEntityWrapper(true, tableInfo),
sqlComment()
);
SqlSource sqlSource = super.createSqlSource(configuration, sql, modelClass);
return this.addSelectMappedStatementForOther(mapperClass, methodName, sqlSource, Long.class);
}
}
com.baomidou.mybatisplus.core.injector.methods.SelectList
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
SqlMethod sqlMethod = SqlMethod.SELECT_COUNT;
String.format(
sqlMethod.getSql(),
sqlFirst(),
sqlSelectColumns(tableInfo, true),
tableInfo.getTableName(),
sqlWhereEntityWrapper(true, tableInfo),
sqlOrderBy(tableInfo),
sqlComment()
);
SqlSource sqlSource = super.createSqlSource(configuration, sql, modelClass);
return this.addSelectMappedStatementForOther(mapperClass, methodName, sqlSource, Long.class);
}
总结
- 大部分扩展自 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
主要逻辑在这里