SqlSession执行Mapper过程
概述
SqlSession执行Mapper过程可以分为四个部分,Mapper接口的注册过程、MappedStatement对象的注册过程、Mapper方法的调用过程、SqlSession执行Mapper的过程。在这里先抛下几个疑问。
- mapper为接口,并没有编写实现类,为什么可以直接调用?
- mappeer的方法怎么跟xml上的方法对应上的?
Mapper接口注册
在Mybatis框架启动时,会调用MapperRegistry
的addMapper
接口将mapper进行注册,核心代码如下:
public <T> void addMapper(Class<T> 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);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
knownMappers是一个Map,key为传入的class类型,value是MapperProxyFactory对象,查看MapperProxyFactory相关逻辑,关键方法如下:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
MapperProxy使用JDK的动态代理,实现了InvocationHandler接口,定义方法执行拦截逻辑后,还需要调用java.lang.reflect.Proxy类的newProxyInstance()方法创建代理对象。
MappedStatement注册
MappedStatement类描述Mapper的SQL配置信息。SQL配置有两种方式:一种是通过XML文件配置;另一种是通过Java注解。
在Configuration类中有一个mappedStatements属性,该属性用于注册MyBatis中所有的MappedStatement对象,声明代码如下:
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
Key为Mapper SQL配置的Id,如果SQL是通过XML配置的,则Id为命名空间加上<select|update|delete|insert>标签的Id,如果SQL通过Java注解配置,则Id为Mapper接口的完全限定名(包括包名)加上方法名称。
MappedStatement对象的生成是通过org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
方法实现,
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
//通过package指定包名
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
//通过resource属性指定XML文件路径
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);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
//通过URL属性指定XML文件路径
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
// 通过class属性指定接口的完全限定名
} 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 = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
查看parse方法
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
//解析之前出现异常的的ResultMap对象
parsePendingResultMaps();
//继续解析之前出现异常的CacheRef对象
parsePendingCacheRefs();
//继续解析之前出现异常的<select|update|delete|insert>标签
parsePendingStatements();
}
configurationElement方法如下:
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>标签
cacheRefElement(context.evalNode("cache-ref"));
//解析<cache>标签
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);
}
}
Mapper方法调用过程
为了执行Mapper接口中定义的方法,我们首先需要调用SqlSession对象的getMapper()方法获取一个动态代理对象,然后通过代理对象调用方法,为了执行Mapper接口中定义的方法,我们首先需要调用SqlSession对象的getMapper()方法获取一个动态代理对象,然后通过代理对象调用方法,MyBatis中的MapperProxy实现了InvocationHandler接口,用于实现动态代理相关逻辑。当我们调用动态代理对象方法的时候,会执行MapperProxy类的invoke()方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//从Object类继承的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}