轉: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的加載,以及通過動態代理來進行接口的執行的過程。
總結
以上就是這篇文章的全部內容,希望本文的內容對大家的學習或者工作能帶來一定的幫助,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
