mybatis源码-解析mappers标签
解析mappers标签下的子标签(package)
<mappers>
<package name="com.example.maybatissource.dao"/>
</mappers>
package标签配置的是一个包的路径,name表示的是mapper接口路径。
public class XMLConfigBuilder extends BaseBuilder {
//省略其他代码
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//获得name属性即包名
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//省略其他代码
}
}
}
}
}
点开if条件是package的configuration.addMappers(mapperPackage);这条语句
解析包下所有资源
当前在Configuration类:
public class Configuration {
//省略其他代码
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
}
当前在MapperRegistry类:
解析包下的所有接口类,并将mapper接口类封装为MapperProxyFactory对象,并放入knownMappers中,接着对mapper接口类进行解析,如果解析失败会把刚才放入knownMappers中的值从knownMappers中移除
public class MapperRegistry {
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
//packageName(包名)
public void addMappers(String packageName) {
addMappers(packageName, Object.class);
}
//packageName(包名),superType(Object类)
public void addMappers(String packageName, Class<?> superType) {
//解析packageName下的所有class文件
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
public <T> void addMapper(Class<T> type) {
//判断这个Class是否是接口
if (type.isInterface()) {
//如果有就报错(其实判断就是knownMappers是否有该mapper接口)
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
//用变量来控制后面的解析
boolean loadCompleted = false;
try {
//key是mapper(type)接口类,value是mapper代理工厂(MapperProxyFactory)
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.
//对mapper文件进行解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
//具体的解析过程,1、先解析对应的XML映射文件,2、再解析接口方法中的注解信息
parser.parse();
loadCompleted = true;
} finally {
//如果解析出错就删除knownMappers中的值
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
//判断knownMappers中是否有当前的Class<T> type
public <T> boolean hasMapper(Class<T> type) {
return knownMappers.containsKey(type);
}
}
ResolverUtil类的解析可以看我写的这篇文章
https://blog.csdn.net/qq_40913932/article/details/113753455
当前在MapperProxyFactory类:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
}
当前在MapperAnnotationBuilder类:
public class MapperAnnotationBuilder {
private final Configuration configuration;
private final MapperBuilderAssistant assistant;
private final Class<?> type;
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
}
}
当前在MapperBuilderAssistant类:
public class MapperBuilderAssistant extends BaseBuilder {
private String currentNamespace;
private final String resource;
private Cache currentCache;
private boolean unresolvedCacheRef; // issue #676
public MapperBuilderAssistant(Configuration configuration, String resource) {
super(configuration);
//将这个资源文件(resource)存储到ErrorContext异常类的resource中
ErrorContext.instance().resource(resource);
this.resource = resource;
}
}
接着来看看对mapper文件是如何进行解析:
调用了 parser.parse();方法,
当前在MapperAnnotationBuilder类:
public class MapperAnnotationBuilder {
private final Configuration configuration;
private final MapperBuilderAssistant assistant;
private final Class<?> type;
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
}
public void parse() {
//拿到当前接口类
String resource = type.toString();
//isResourceLoaded在configuration类中是一个变量名是loadedResources的set集合
//判断是否有该mapper接口(resource),如果没有就添加
if (!configuration.isResourceLoaded(resource)) {
/*
解析和接口同名的xml文件,前提是存在该文件,
如果不存在该文件就解析接口中方法上的注解
最终要做的是把xml文件中的标签,转化为mapperStatement,
并放入mappedStatements中
*/
loadXmlResource();
//就添加resource到变量名是loadedResources的set集合
configuration.addLoadedResource(resource);
assistant.setCurrentNamespace(type.getName());
//解析接口上的@CacheNamespace注解
parseCache();
parseCacheRef();
//获得接口中的所有方法,并解析方法上的注解
Method[] methods = type.getMethods();
for (Method method : methods) {
try {
// issue #237
if (!method.isBridge()) {
//解析方法上的注解
parseStatement(method);
}
} catch (IncompleteElementException e) {
configuration.addIncompleteMethod(new MethodResolver(this, method));
}
}
}
parsePendingMethods();
}
}
上面的解析分为两个过程,首先解析对应的XML映射文件,再解析方法上的注解。
先看XML是如何解析:
public class MapperAnnotationBuilder {
private void loadXmlResource() {
//判断变量名是loadedResources的set集合是否有该mapper.xml
if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
//解析对应的XML映射文件,其名称为接口类+"."+xml,即和接口类同名且在同一个包下。
String xmlResource = type.getName().replace('.', '/') + ".xml";
// #1347
InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
if (inputStream == null) {
// Search XML mapper that is not in the module but in the classpath.
try {
inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
} catch (IOException e2) {
// ignore, resource is not required
}
}
//输入流存在就开始解析xml
if (inputStream != null) {
XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
//解析mapper.xml文件(是你写的sql的xml文件)
xmlParser.parse();
}
}
}
}
确定XML映射文件的位置,和接口类同名且在同一个包下。如下的例子:
解析xml文件:
public class XMLMapperBuilder extends BaseBuilder {
//省略其他代码
public void parse() {
//判断是否加载过该资源,根据变量名是loadedResources的set集合是否有该mapper
if (!configuration.isResourceLoaded(resource)) {
//解析mapper.xml文件中的<mapper>标签及其子标签
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
}
解析<mapper>
标签里的sql
当前在XMLMapperBuilder类中
public class XMLMapperBuilder extends BaseBuilder {
private void configurationElement(XNode context) {
try {
//获得<mapper>标签中namespace属性的值
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
//将namespace设置到currentNamespace中
builderAssistant.setCurrentNamespace(namespace);
//解析<cache-ref>标签
cacheRefElement(context.evalNode("cache-ref"));
//二级缓存标签
cacheElement(context.evalNode("cache"));
//解析<parameterMap>标签
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析<resultMap>标签
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析<sql>标签
sqlElement(context.evalNodes("/mapper/sql"));
//解析select、insert、update、delete子标签
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
}
看看如何解析我们写的select|insert|update|delete。
当前在XMLMapperBuilder类中
public class XMLMapperBuilder extends BaseBuilder {
//省略其他代码
private void buildStatementFromContext(List<XNode> list) {
//如果配置了<databaseIdProvider>,则使用自定义的,否则使用默认的
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
}
调用XMLStatementBuilder的parseStatementNode方法
当前在XMLStatementBuilder 类中
public class XMLStatementBuilder extends BaseBuilder {
//省略其他代码
public void parseStatementNode() {
//获取id
String id = context.getStringAttribute("id");
//获取databaseId
String databaseId = context.getStringAttribute("databaseId");
//验证sql中设置的databaseId是否匹配<databaseIdProvider>标签的一样
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
//不符合当前数据源对应的数据厂商信息的sql语句不加载
return;
}
//获取<select>|<insert>|<update>|<delete>的标签名
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//如果是查询语句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//查询语句默认开启一级缓存,这里默认是true
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
// 解析 <include>标签 属性
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
//参数类型;得到参数类的可以是完全限定名或别名。因为 MyBatis 可以通过 typeAliasRegistry去得到对应的参数类型,或者通过反射拿到具体的参数类型。
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
// 获取 Mapper 语法类型
String lang = context.getStringAttribute("lang");
//如果为空,就使用XMLLanguageDriver
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
// 解析 selectKey 元素
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
//通过buildAssistant将解析得到的参数设置构造成MappedStatement对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
具体的sql标签意思可以参考这篇文章:https://www.w3cschool.cn/mybatis/f4uw1ilx.html
解析mappers标签下的子标签(mapper)
<mappers>
<mapper resource="mapper/student_table/StudentTableMapper.xml"/>
</mappers>
配置mapper标签配置的是一个xml文件,该文件中存在相关的sql语句