mybatis如何通过接口查找对应的mapper.xml及方法执行详解

转:http://www.jb51.net/article/116402.htm

本文主要介绍的是关于mybatis通过接口查找对应mapper.xml及方法执行的相关内容,下面话不多说,来看看详细的介绍:

在使用mybatis的时候,有一种方式是

?
1
BookMapper bookMapper = SqlSession().getMapper(BookMapper.
class
)

获取接口,然后调用接口的方法。只要方法名和对应的mapper.xml中的id名字相同,就可以执行sql。

那么接口是如何与mapper.xml对应的呢?

首先看下,在getMapper()方法是如何操作的。

在DefaultSqlSession.Java中调用了configuration.getMapper()

?
1
2
3
public
<T> T getMapper(Class<T> type) {
 
return
configuration.<T>getMapper(type,
this
);
 
}

在Configuration.java中调用了mapperRegistry.getMapper(type, sqlSession);

?
1
2
3
public
<T> T getMapper(Class<T> type, SqlSession sqlSession) {
 
return
mapperRegistry.getMapper(type, sqlSession);
 
}

下面重点来了,在MapperRegistry.java中实现了动态代理

?
1
2
3
4
5
6
7
8
9
10
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);
 
}
 
}

这个函数分两部分来看,首先是从map集合中获取接口代理,map集合的来源,第二部分获取代理后实例化,获取接口的方法,执行sql。

对于第一部分:集合的来源。

这个MapperRegistry.java中有个方法是addMappers();共有两个重载。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public
void
addMappers(String packageName, Class<?> superType) {
 
ResolverUtil<Class<?>> resolverUtil =
new
ResolverUtil<Class<?>>();
 
//通过包名,查找该包下所有的接口进行遍历,放入集合中
 
resolverUtil.find(
new
ResolverUtil.IsA(superType), packageName);
 
Set<Class<?
extends
Class<?>>> mapperSet = resolverUtil.getClasses();
 
for
(Class<?> mapperClass : mapperSet) {
  
addMapper(mapperClass);
 
}
 
}
 
 
//解析包名下的接口
 
public
void
addMappers(String packageName) {
 
addMappers(packageName, Object.
class
);
 
}

往上追溯该方法的调用是在SqlSessionFactory.build();时对配置文件的解析,其中对节点mappers的解析,这里先不赘述,

?
1
mapperElement(root.evalNode(
"mappers"
));

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
{
  
//使用mapper节点
   
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 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."
);
   
}
  
}
  
}
 
}
 
}

这是调用addMapper()的顺序。

同时在改方法中还有一个方法很重要

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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<T>(type));
 
//根据接口名寻找同包下同名的xml或者mapper的namespace是该接口的xml
 
//找到对用的xml后进行解析mapper节点里面的节点
 
MapperAnnotationBuilder parser =
new
MapperAnnotationBuilder(config, type);
 
parser.parse();
 
loadCompleted =
true
;
 
}
finally
{
 
if
(!loadCompleted) {
  
knownMappers.remove(type);
 
}
 
}
}
}

这是通过接口的全路径来查找对应的xml。这里有两种方式解析,也就是我们平常xml文件放置位置的两种写法。

第一种是不加namespace,把xml文件放在和接口相同的路径下,同时xml的名字与接口名字相同,如接口名为Student.java,xml文件为Student.xml。在相同的包下。这种当时可以不加namespace.

第二种是加namespace,通过namespace来查找对应的xml.

到这就是接口名和xml的全部注册流程。

下面再说下第二部分就是通过动态代理获取接口名字来对应xml中的id。

主要有两个类MapperProxyFactory.java和MapperProxy.java

对于MapperProxyFactory.java

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public
class
MapperProxyFactory<T> {
 
 
private
final
Class<T> mapperInterface;
 
private
Map<Method, MapperMethod> methodCache =
new
ConcurrentHashMap<Method, MapperMethod>();
 
//构造函数,获取接口类
 
public
MapperProxyFactory(Class<T> mapperInterface) {
 
this
.mapperInterface = mapperInterface;
 
}
 
 
public
Class<T> getMapperInterface() {
 
return
mapperInterface;
 
}
 
 
public
Map<Method, MapperMethod> getMethodCache() {
 
return
methodCache;
 
}
 
 
@SuppressWarnings
(
"unchecked"
)
 
protected
T newInstance(MapperProxy<T> mapperProxy) {
 
return
(T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
new
Class[] { mapperInterface }, mapperProxy);
 
}
//供外部调用
 
public
T newInstance(SqlSession sqlSession) {
 
final
MapperProxy<T> mapperProxy =
new
MapperProxy<T>(sqlSession, mapperInterface, methodCache);
 
return
newInstance(mapperProxy);
 
}
 
}

在MapperProxy.java中进行方法的执行

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public
Object invoke(Object proxy, Method method, Object[] args)
throws
Throwable {
 
if
(Object.
class
.equals(method.getDeclaringClass())) {
  
try
{
  
return
method.invoke(
this
, args);
  
}
catch
(Throwable t) {
  
throw
ExceptionUtil.unwrapThrowable(t);
  
}
 
}
 
final
MapperMethod mapperMethod = cachedMapperMethod(method);
 
//方法的执行
 
return
mapperMethod.execute(sqlSession, args);
 
}
 
 
private
MapperMethod cachedMapperMethod(Method method) {
 
MapperMethod mapperMethod = methodCache.get(method);
 
if
(mapperMethod ==
null
) {
  
mapperMethod =
new
MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
  
methodCache.put(method, mapperMethod);
 
}
 
return
mapperMethod;
 
}

至此,就是mybatis所有接口和xml的加载,以及通过动态代理来进行接口的执行的过程。

总结

以上就是这篇文章的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。