Mybatis加载mapper
首先说明一下, 我是在Springboot中使用的Mybatis所以并没有使用xml配置
这里我整合了一份脑图, 需要的自取思维导图
正好给我赚点积分下载资源
MapperScan
MapperScan这个注解的作用就是将我们的mapper包中的所有mapper接口注册入mybatis,
所以我从这里开始入手全局搜索这个MapperScan
上图中可以看见MapperScannerRegistrar这个类是唯一一个有使用到这个注解类的类, 这个类的作用很明显就是一个Mapper注册连接器
这个类中只有三个方法和一个内部类
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
// 获取了MapperScan注解中的值, 我们上面的mapper包的位置就在这个注解的value中
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if (mapperScanAttrs != null) {
// 解析注解
registerBeanDefinitions(mapperScanAttrs, registry);
}
}
在解析过程中主要是获取mapper
解析方法中的最后一步就是使用了ClassPathMapperScanner的doScan方法扫描
scanner.doScan(StringUtils.toStringArray(basePackages));
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
// 使用的父类的doScan方法
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
// 扫描到的类在这里做进一步的设置
processBeanDefinitions(beanDefinitions);
}
// 返回设置好的mapper
return beanDefinitions;
}
这里是spring的内容, 粗略看一看
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
// 先new一个结果Set
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
// 对传入的packages进行遍历
for (String basePackage : basePackages) {
// findCandidateComponents这个方法就是扫描我们配置的mapper包, 获得class
// 里面将com.nit.lab.mapper转换为classpath*:com/nit/lab/mapper/**/*.class
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
// 对获取来的class进行遍历
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
// 设置candidate的作用域, 这里的scopeName是singleton
candidate.setScope(scopeMetadata.getScopeName());
// 获取class的名称
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
// 对这个实例进行更多的设置
if (candidate instanceof AbstractBeanDefinition) {
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
// 检查这个类名是否需要定义或者是否冲突
if (checkCandidate(beanName, candidate)) {
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
上面把mapper类扫描完毕后需要加入mybatis的Configuration中
可以得出一个结论, 一个mapper接口会通过MapperFactoryBean加载进Configuration
public <T> void addMapper(Class<T> type) {
// 判断传入的class是否为接口, 所以我们的mapper必须是接口, 如果是类也不报错, 但是不加载
if (type.isInterface()) {
// 这里判断这个接口是否已经注册过, 所以返回这是一个已知的mapper
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 将mapper注入knownMappers, 这是一个hashMap用于存储已经注册过的mapper
knownMappers.put(type, new MapperProxyFactory<>(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.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
// 进入parse关键步骤
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
parse解析方法
public void parse() {
// 获取type的名称
String resource = type.toString();
// 判断Configuration中是否已经加载过这个类名
if (!configuration.isResourceLoaded(resource)) {
loadXmlResource();
// loadedResources中加入这个类
configuration.addLoadedResource(resource);
// 设置assistant的当前命名空间为type的name
assistant.setCurrentNamespace(type.getName());
// 这里判断了是否有CacheNamespace, 也就是是否开启二级缓存
// 我们没开二级缓存, 不需要管这里
parseCache();
// 同上
parseCacheRef();
// 获取mapper中的方法
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
// 这里开始解析statement
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
解析statement
void parseStatement(Method method) {
// 这里获取方法的参数类型, 如果是多个参数就返回数组
Class<?> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
// 获取sql语句, 我这边使用注解写的sql, 所以这里获得的就是注解中的value
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
// 生成一个唯一的statementId
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = null;
// 这里获取该方法的sql类型(SELECT之类的)
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 如果不是select就更新缓存
boolean flushCache = !isSelect;
// 如果是select就使用缓存
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
// mybatis的@Options注解能够设置缓存时间,能够为对象生成自增的key
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();
}
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
// 我这边使用注解sql的时候并没有使用到ResultMap
if (resultMapAnnotation != null) {
resultMapId = String.join(",", resultMapAnnotation.value());
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
// 这里设置了assistant的returnType, 我这边是用自定义的DTO接收的
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
后记
至此Mybatis的mapper加载流程结束了, 总结一下就是先找到mapper接口, 然后加载注册, 然后扫描mapper接口中的方法, 获取sql, 根据sql的类型来做不同的设置, 并且在assistant中注入一个mapperStatement, 这个statemen中有mapper的各种信息, 后面直接用这个创建Statement对象
因为Mybatis其实就是在JDBC的基础上封装的, 所以流程都基本一致