Mybatis是一种半ORM框架,需要我们手动编写SQL语句。

在启动时,它会将SQL语句等信息读取到内存中,便于操作数据库时进行参数解析、执行SQL和结果封装。

使用过Mybatis的都知道,它有两种方式编写SQL语句:

xml映射文件

映射接口方法上的注解

在启动Mybatis时,可以通过Configuration的addMappers(basePackage)方法添加映射接口和映射文件,读取上述两种方式编写的SQL语句等信息。

实际上,该方法对MapperRegistry#addMappers(basePackage, superType)进行了代理,执行逻辑如下:

读取指定包路径下的.class文件

加载类对象

过滤出superType的子类

解析所有子类:子类对应的xml文件和子类方法上的注解

MapperRegistry#addMappers(basePackage, superType)源码如下,包括上述所有执行逻辑:

public void addMappers(String packageName, Class superType) {

ResolverUtil> resolverUtil = new ResolverUtil<>();

// 读取指定包路径下的.class文件,过滤出superType的子类

resolverUtil.find(new ResolverUtil.IsA(superType), packageName);

Set>> mapperSet = resolverUtil.getClasses();

// 解析所有子类

for (Class mapperClass : mapperSet) {

addMapper(mapperClass);

}

}

具体解析逻辑位于MapperRegistry#addMapper,它会将解析完的映射接口添加到knownMappers中,值是MapperProxyFactory,可以用来创建映射接口的代理对象(需要注意的是,SQL语句等信息并不保存在这里):

public void addMapper(Class type) {

if (type.isInterface()) {

if (hasMapper(type)) {

throw new BindingException("Type " + type + " is already known to the MapperRegistry.");

}

boolean loadCompleted = false;

try {

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);

// 解析xml和注解

parser.parse();

loadCompleted = true;

} finally {

if (!loadCompleted) {

knownMappers.remove(type);

}

}

}

}

MapperAnnotationBuilder#parse会解析该映射接口对应的xml映射文件,以及映射接口方法上的注解:

public void parse() {

String resource = type.toString();

if (!configuration.isResourceLoaded(resource)) {

// 解析xml映射文件

loadXmlResource();

configuration.addLoadedResource(resource);

// 解析注解

assistant.setCurrentNamespace(type.getName());

parseCache();

parseCacheRef();

for (Method method : type.getMethods()) {

if (!canHaveStatement(method)) {

continue;

}

if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()

&& method.getAnnotation(ResultMap.class) == null) {

parseResultMap(method);

}

try {

parseStatement(method);

} catch (IncompleteElementException e) {

configuration.addIncompleteMethod(new MethodResolver(this, method));

}

}

}

parsePendingMethods();

}

解析xml映射文件时,会读取全限定类名对应的.xml文件,将SQL语句等信息分别添加到Configuration的ResultMap和MappedStatement等成员变量缓存中:

public void parse() {

if (!configuration.isResourceLoaded(resource)) {

// 解析XML节点

configurationElement(parser.evalNode("/mapper"));

configuration.addLoadedResource(resource);

bindMapperForNamespace();

}

parsePendingResultMaps();

parsePendingCacheRefs();

parsePendingStatements();

}

private void configurationElement(XNode context) {

try {

// 获取namespace

String namespace = context.getStringAttribute("namespace");

if (namespace == null || namespace.isEmpty()) {

throw new BuilderException("Mapper's namespace cannot be empty");

}

builderAssistant.setCurrentNamespace(namespace);

// 解析cache-ref到Configuration的cacheRefMap

cacheRefElement(context.evalNode("cache-ref"));

// 解析cache到Configuration的caches

cacheElement(context.evalNode("cache"));

// 解析parameterMap到Configuration的parameterMaps

parameterMapElement(context.evalNodes("/mapper/parameterMap"));

// 解析resultMap到Configuration的resultMaps

resultMapElements(context.evalNodes("/mapper/resultMap"));

// 解析sql到XMLMapperBuilder的sqlFragments

sqlElement(context.evalNodes("/mapper/sql"));

// 解析select|insert|update|delete到Configuration的mappedStatements

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);

}

}

解析映射接口方法上的注解时,会读取每个方法上的注解信息,将SQL语句等信息分别添加到Configuration的ResultMap和MappedStatement等成员变量缓存中:

for (Method method : type.getMethods()) {

if (!canHaveStatement(method)) {

continue;

}

if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()

&& method.getAnnotation(ResultMap.class) == null) {

// 解析Arg、Result和TypeDiscriminator等注解到Configuration的resultMaps

parseResultMap(method);

}

try {

// 解析Select、Update、Insert、Delete、SelectKey和ResultMap等注解到Configuration的mappedStatements

parseStatement(method);

} catch (IncompleteElementException e) {

configuration.addIncompleteMethod(new MethodResolver(this, method));

}

}

综上我们可以发现,Mybatis添加映射接口和映射文件主要做了两件事:

缓存SQL语句等基本信息,包括SQL语句、参数映射和结果集映射等。

缓存映射接口代理工厂,用于创建映射接口代理对象,便于通过调用对象方法直接操作数据库。

查看原文