Mybatis是怎么工作的(二)

2次阅读

共计 5568 个字符,预计需要花费 14 分钟才能阅读完成。

目标:

理清 mybatis 加载解析 mapper 文件的过程;
理清 mybatis 执行 SQL 的过程。

上一篇文章分析 mybatis 加载配置的源码时提到了 org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration 方法,现在继续分析其中的 mapperElement 方法。先看源码:
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if (“package”.equals(child.getName())) {
String mapperPackage = child.getStringAttribute(“name”);
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute(“resource”);
String url = child.getStringAttribute(“url”);
String mapperClass = child.getStringAttribute(“class”);
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
// 生成 XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析配置文件
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException(“A mapper element may only specify a url, resource or class, but not more than one.”);
}
}
}
}
}
考虑到项目的配置,看下生成 XMLMapperBuilder 和 mapperParser.parse() 的代码。在生成 XMLMapperBuilder 的过程中,使用了 MapperBuilderAssistant,这个类继承了 BaseBuilder。在该类的构造器中加载了 TypeAliasRegistry 和 TypeHandlerRegistry。下面重点看 mapperParserparse()
public void parse() {
// 判断是否已经加载资源
if (!configuration.isResourceLoaded(resource)) {
// 配置 /mapper 节点下的子节点
configurationElement(parser.evalNode(“/mapper”));
// 加载 resource 资源
configuration.addLoadedResource(resource);
// 绑定命名空间
bindMapperForNamespace();
}
// 加载未加载完成的资源
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}

下面主要看下 configurationElement 的代码:
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute(“namespace”);
if (namespace == null || namespace.equals(“”)) {
throw new BuilderException(“Mapper’s namespace cannot be empty”);
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode(“cache-ref”));
cacheElement(context.evalNode(“cache”));
parameterMapElement(context.evalNodes(“/mapper/parameterMap”));
resultMapElements(context.evalNodes(“/mapper/resultMap”));
sqlElement(context.evalNodes(“/mapper/sql”));
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);
}
}
可以看出主要是解析不同的节点,并放进 builderAssistant 里面去。下面看下执行 SQL 的过程。
ClipsDAO clipsDAO = session.getMapper(ClipsDAO.class);
ClipsEntity clipsEntity = clipsDAO.selectById(1);
查看 session.getMapper() 的实现:
// org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}

// org.apache.ibatis.session.Configuration#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}

// org.apache.ibatis.binding.MapperRegistry#getMapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException(“Type ” + type + ” is not known to the MapperRegistry.”);
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException(“Error getting mapper instance. Cause: ” + e, e);
}
}

可以看出,mybatis 通过动态代理为接口生成了代理类,我们知道在加载配置时,bindMapperForNamespace 方法调用了 configuration.addMapper() 方法把 Class 映射到 org.apache.ibatis.binding.MapperRegistry#knownMappers 中去的。下面看一下 MapperProxy 代码:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 从缓存中获取 MapperMethod 对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 执行 SQL
return mapperMethod.execute(sqlSession, args);
}

下面是 MapperMethod.execute() 方法:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException(“Unknown execution method for: ” + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException(“Mapper method ‘” + command.getName()
+ ” attempted to return null from a method with a primitive return type (” + method.getReturnType() + “).”);
}
return result;
}
至此,SQL 执行完成。

正文完
 0