Mybatis Plus 中 BaseMapper 实现
在使用 mybatis plus 的时候,我们只要继承 BaseMapper 就可以使用其中的一些方法了
public interface BaseMapper<T> extends Mapper<T> {
int insert(T entity);
int deleteById(Serializable id);
//...
}
怎么实现的呢?
MybatisSqlSessionFactoryBean
mybatis plus 解析入口
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
if (this.mapperLocations != null) {
if (this.mapperLocations.length == 0) {
LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
} else {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
}
}
和 原生mybatis 中唯一不同的是 使用 MybatisConfiguration
代替 Configuration , MybatisConfiguraion 扩展了 Configuration
xmlMapperBuilder.parse()
方法走的是 原生mybatis 的解析
//XMLMapperBuilder.java
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//扩展入口!!
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// 这里交给了 MybatisConfiguration
configuration.addMapper(boundType);
}
}
}
}
MybatisConfiguration
public class MybatisConfiguration extends Configuration {
protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mybatisMapperRegistry.addMapper(type);
}
//...
}
MybatisMapperRegistry
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
// TODO 如果之前注入 直接返回
return;
// TODO 这里就不抛异常了
// throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// TODO 这里也换成 MybatisMapperProxyFactory 而不是 MapperProxyFactory
knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
// TODO 这里也换成 MybatisMapperAnnotationBuilder 而不是 MapperAnnotationBuilder
MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
主要注意 MybatisMapperAnnotationBuilder
的 parse 方法
MybatisMapperAnnotationBuilder
public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {
@Override
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
configuration.addLoadedResource(resource);
final String typeName = type.getName();
assistant.setCurrentNamespace(typeName);
parseCache();
parseCacheRef();
SqlParserHelper.initSqlParserInfoCache(type);
Method[] methods = type.getMethods();
// @Insert
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
parseStatement(method);
SqlParserHelper.initSqlParserInfoCache(typeName, method);
}
} catch (IncompleteElementException e) {
// TODO 使用 MybatisMethodResolver 而不是 MethodResolver
configuration.addIncompleteMethod(new MybatisMethodResolver(this, method));
}
}
//BaseMapper 实现
// TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql
if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
}
}
parsePendingMethods();
}
}
这里有两大块:
- @Insert ,@Select 的实现
- BaseMapper 的实现
@Insert ,@Select …的实现
- 解析注解
- 构造 SqlSource: mapperNameSpace+methodName
- MapperBuilderAssistant…addMappedStatement(mappedStatementId,sqlSource…)
BaseMapper 的实现
- 判断是不是 Mapper的子类
- 如果是Mapper子类,则自动为这个类注入 Mapper子类的方法
GlobalConfigUtils.getSqlInjector(configuration) 为 DefaultSqlInjector
DefaultSqlInjector
@Override
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) {
Class<?> modelClass = extractModelClass(mapperClass);
if (modelClass != null) {
String className = mapperClass.toString();
Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration());
if (!mapperRegistryCache.contains(className)) {
List<AbstractMethod> methodList = this.getMethodList(mapperClass);
if (CollectionUtils.isNotEmpty(methodList)) {
TableInfo tableInfo = TableInfoHelper.initTableInfo(builderAssistant, modelClass);
// 循环注入自定义方法
methodList.forEach(m -> m.inject(builderAssistant, mapperClass, modelClass, tableInfo));
} else {
logger.debug(mapperClass.toString() + ", No effective injection method was found.");
}
mapperRegistryCache.add(className);
}
}
}
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
return Stream.of(
new Insert(),
new Delete(),
new DeleteByMap(),
new DeleteById(),
new DeleteBatchByIds(),
new Update(),
new UpdateById(),
new SelectById(),
new SelectBatchByIds(),
new SelectByMap(),
new SelectOne(),
new SelectCount(),
new SelectMaps(),
new SelectMapsPage(),
new SelectObjs(),
new SelectList(),
new SelectPage()
).collect(toList());
}
可以看出,为这个 mapperClass 遍历注入 getMethodList
中的所有方法
所以只要我们继承 BaseMapper 就可以实现 这些方法的使用啦
good luck!