- ubuntu12.04环境下使用kvm ioctl接口实现最简单的虚拟机
- Ubuntu 通过无线网络安装Ubuntu Server启动系统后连接无线网络的方法
- 在Ubuntu上搭建网桥的方法
- ubuntu 虚拟机上网方式及相关配置详解
CFSDN坚持开源创造价值,我们致力于搭建一个资源共享平台,让每一个IT人在这里找到属于你的精彩世界.
这篇CFSDN的博客文章通过源代码分析Mybatis的功能流程详解由作者收集整理,如果你对这篇文章有兴趣,记得点赞哟.
SQL解析 。
Mybatis在初始化的时候,会读取xml中的SQL,解析后会生成SqlSource对象,SqlSource对象分为两种.
DynamicSqlSource
,动态SQL,获取SQL(getBoundSQL
方法中)的时候生成参数化SQL。RawSqlSource
,原始SQL,创建对象时直接生成参数化SQL。因为RawSqlSource不会重复去生成参数化SQL,调用的时候直接传入参数并执行,而DynamicSqlSource则是每次执行的时候参数化SQL,所以RawSqlSource是DynamicSqlSource的性能要好的.
解析的时候会先解析include标签和selectkey标签,然后判断是否是动态SQL,判断取决于以下两个条件:
${}
表达式。注意这种方式存在SQL注入,谨慎使用。trim
、where
、set
、foreach
、if
、choose
、when
、otherwise
、bind
标签相关代码如下:
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
34
35
36
37
38
39
40
41
42
|
protected
MixedSqlNode parseDynamicTags(XNode node) {
// 创建 SqlNode 数组
List<SqlNode> contents =
new
ArrayList<>();
// 遍历 SQL 节点的所有子节点
NodeList children = node.getNode().getChildNodes();
for
(
int
i =
0
; i < children.getLength(); i++) {
// 当前子节点
XNode child = node.newXNode(children.item(i));
// 如果类型是 Node.CDATA_SECTION_NODE 或者 Node.TEXT_NODE 时
if
(child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
// 获得内容
String data = child.getStringBody(
""
);
// 创建 TextSqlNode 对象
TextSqlNode textSqlNode =
new
TextSqlNode(data);
// 如果是动态的 TextSqlNode 对象(是否使用了${}表达式)
if
(textSqlNode.isDynamic()) {
// 添加到 contents 中
contents.add(textSqlNode);
// 标记为动态 SQL
isDynamic =
true
;
// 如果是非动态的 TextSqlNode 对象
}
else
{
// 创建 StaticTextSqlNode 添加到 contents 中
contents.add(
new
StaticTextSqlNode(data));
}
// 如果类型是 Node.ELEMENT_NODE,其实就是XMl中<where>等那些动态标签
}
else
if
(child.getNode().getNodeType() == Node.ELEMENT_NODE) {
// issue #628
// 根据子节点的标签,获得对应的 NodeHandler 对象
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if
(handler ==
null
) {
// 获得不到,说明是未知的标签,抛出 BuilderException 异常
throw
new
BuilderException(
"Unknown element <"
+ nodeName +
"> in SQL statement."
);
}
// 执行 NodeHandler 处理
handler.handleNode(child, contents);
// 标记为动态 SQL
isDynamic =
true
;
}
}
// 创建 MixedSqlNode 对象
return
new
MixedSqlNode(contents);
}
|
参数解析 。
Mybais中用于解析Mapper方法的参数的类是ParamNameResolver,它主要做了这些事情:
ParamNameResolver
,之后会缓存如果参数类型是RowBounds或者ResultHandler类型或者他们的子类,则不处理.
如果参数中有Param注解,则使用Param中的值作为参数名 。
如果配置项useActualParamName=true,argn(n>=0)标作为参数名,如果你是Java8以上并且开启了-parameters`,则是实际的参数名 。
如果配置项useActualParamName=false,则使用n(n>=0)作为参数名 。
相关源代码:
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
34
35
|
public
ParamNameResolver(Configuration config, Method method) {
final
Class<?>[] paramTypes = method.getParameterTypes();
final
Annotation[][] paramAnnotations = method.getParameterAnnotations();
final
SortedMap<Integer, String> map =
new
TreeMap<Integer, String>();
int
paramCount = paramAnnotations.length;
// 获取方法中每个参数在SQL中的参数名
for
(
int
paramIndex =
0
; paramIndex < paramCount; paramIndex++) {
// 跳过RowBounds、ResultHandler类型
if
(isSpecialParameter(paramTypes[paramIndex])) {
continue
;
}
String name =
null
;
// 遍历参数上面的所有注解,如果有Param注解,使用它的值作为参数名
for
(Annotation annotation : paramAnnotations[paramIndex]) {
if
(annotation
instanceof
Param) {
hasParamAnnotation =
true
;
name = ((Param) annotation).value();
break
;
}
}
// 如果没有指定注解
if
(name ==
null
) {
// 如果开启了useActualParamName配置,则参数名为argn(n>=0),如果是Java8以上并且开启-parameters,则为实际的参数名
if
(config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
// 否则为下标
if
(name ==
null
) {
name = String.valueOf(map.size());
}
}
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
|
而在使用这个names构建xml中参数对象和值的映射时,还进行了进一步的处理.
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
|
public
Object getNamedParams(Object[] args) {
final
int
paramCount = names.size();
// 无参数,直接返回null
if
(args ==
null
|| paramCount ==
0
) {
return
null
;
}
else
if
(!hasParamAnnotation && paramCount ==
1
) {
// 一个参数,并且没有注解,直接返回这个对象
return
args[names.firstKey()];
}
else
{
// 其他情况则返回一个Map对象
final
Map<String, Object> param =
new
ParamMap<Object>();
int
i =
0
;
for
(Map.Entry<Integer, String> entry : names.entrySet()) {
// 先直接放入name的键和对应位置的参数值,其实就是构造函数中存入的值
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final
String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i +
1
);
// 防止覆盖 @Param 的参数值
if
(!names.containsValue(genericParamName)) {
// 然后放入GENERIC_NAME_PREFIX + index + 1,其实就是param1,params2,paramn
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return
param;
}
}
|
另外值得一提的是,对于集合类型,最后还有一个特殊处理 。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
private
Object wrapCollection(
final
Object object) {
// 如果对象是集合属性
if
(object
instanceof
Collection) {
StrictMap<Object> map =
new
StrictMap<Object>();
// 加入一个collection参数
map.put(
"collection"
, object);
// 如果是一个List集合
if
(object
instanceof
List) {
// 额外加入一个list属性使用
map.put(
"list"
, object);
}
return
map;
}
else
if
(object !=
null
&& object.getClass().isArray()) {
// 数组使用array
StrictMap<Object> map =
new
StrictMap<Object>();
map.put(
"array"
, object);
return
map;
}
return
object;
}
|
由此我们可以得出使用参数的结论:
@Param
注解,则使用注解的值作为参数collection
参数,如果是List
对象,可以额外使用list
参数。array
参数如果有多个参数,没有加@Param
注解的可以使用argn
或者n
(n>=0,取决于useActualParamName
配置项)作为参数,加了注解的使用注解的值。@Param
中的值覆盖,都可以使用paramn
(n>=1)延迟加载 。
Mybatis是支持延迟加载的,具体的实现方式根据resultMap创建返回对象时,发现fetchType=“lazy”,则使用代理对象,默认使用Javassist(MyBatis 3.3 以上,可以修改为使用CgLib)。代码处理逻辑在处理返回结果集时,具体代码调用关系如下:
PreparedStatementHandler.query=> handleResultSets =>handleResultSet=>handleRowValues=>handleRowValuesForNestedResultMap=>getRowValue 。
在getRowValue中,有一个方法createResultObject创建返回对象,其中的关键代码创建了代理对象:
1
2
3
|
if
(propertyMapping.getNestedQueryId() !=
null
&& propertyMapping.isLazy()) {
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}
|
另一方面,getRowValue会调用applyPropertyMappings方法,其内部会调用getPropertyMappingValue,继续追踪到getNestedQueryMappingValue方法,在这里,有几行关键代码:
1
2
3
4
5
6
7
8
9
10
|
// 如果要求延迟加载,则延迟加载
if
(propertyMapping.isLazy()) {
// 如果该属性配置了延迟加载,则将其添加到 `ResultLoader.loaderMap` 中,等待真正使用时再执行嵌套查询并得到结果对象。
lazyLoader.addLoader(property, metaResultObject, resultLoader);
// 返回已定义
value = DEFERED;
// 如果不要求延迟加载,则直接执行加载对应的值
}
else
{
value = resultLoader.loadResult();
}
|
这几行的目的是跳过属性值的加载,等真正需要值的时候,再获取值.
Executor 。
Executor是一个接口,其直接实现的类是BaseExecutor和CachingExecutor,BaseExecutor又派生了BatchExecutor、ReuseExecutor、SimpleExecutor、ClosedExecutor。其继承结构如图:
其中ClosedExecutor是一个私有类,用户不直接使用它.
BaseExecutor
:模板类,里面有各个Executor的公用的方法。SimpleExecutor
:最常用的Executor
,默认是使用它去连接数据库,执行SQL语句,没有特殊行为。ReuseExecutor
:SQL语句执行后会进行缓存,不会关闭Statement
,下次执行时会复用,缓存的key
值是BoundSql
解析后SQL,清空缓存使用doFlushStatements
。其他与SimpleExecutor
相同。BatchExecutor
:当有连续的Insert
、Update
、Delete
的操作语句,并且语句的BoundSql
相同,则这些语句会批量执行。使用doFlushStatements
方法获取批量操作的返回值。CachingExecutor
:当你开启二级缓存的时候,会使用CachingExecutor
装饰SimpleExecutor
、ReuseExecutor
和BatchExecutor
,Mybatis通过CachingExecutor
来实现二级缓存。缓存 。
一级缓存 。
Mybatis一级缓存的实现主要是在BaseExecutor中,在它的查询方法里,会优先查询缓存中的值,如果不存在,再查询数据库,查询部分的代码如下,关键代码在17-24行:
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
34
35
36
37
38
39
40
41
42
43
44
|
@Override
public
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws
SQLException {
ErrorContext.instance().resource(ms.getResource()).activity(
"executing a query"
).object(ms.getId());
// 已经关闭,则抛出 ExecutorException 异常
if
(closed) {
throw
new
ExecutorException(
"Executor was closed."
);
}
// 清空本地缓存,如果 queryStack 为零,并且要求清空本地缓存。
if
(queryStack ==
0
&& ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try
{
// queryStack + 1
queryStack++;
// 从一级缓存中,获取查询结果
list = resultHandler ==
null
? (List<E>) localCache.getObject(key) :
null
;
// 获取到,则进行处理
if
(list !=
null
) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
// 获得不到,则从数据库中查询
}
else
{
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
}
finally
{
// queryStack - 1
queryStack--;
}
if
(queryStack ==
0
) {
// 执行延迟加载
for
(DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
// 清空 deferredLoads
deferredLoads.clear();
// 如果缓存级别是 LocalCacheScope.STATEMENT ,则进行清理
if
(configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return
list;
}
|
而在queryFromDatabase中,则会将查询出来的结果放到缓存中.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// 从数据库中读取操作
private
<E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws
SQLException {
List<E> list;
// 在缓存中,添加占位对象。此处的占位符,和延迟加载有关,可见 `DeferredLoad#canLoad()` 方法
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try
{
// 执行读操作
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
}
finally
{
// 从缓存中,移除占位对象
localCache.removeObject(key);
}
// 添加到缓存中
localCache.putObject(key, list);
// 暂时忽略,存储过程相关
if
(ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return
list;
}
|
而一级缓存的Key,从方法的参数可以看出,与调用方法、参数、rowBounds分页参数、最终生成的sql有关.
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
34
35
36
37
38
39
40
|
@Override
public
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if
(closed) {
throw
new
ExecutorException(
"Executor was closed."
);
}
// 创建 CacheKey 对象
CacheKey cacheKey =
new
CacheKey();
// 设置 id、offset、limit、sql 到 CacheKey 对象中
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
// 设置 ParameterMapping 数组的元素对应的每个 value 到 CacheKey 对象中
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic 这块逻辑,和 DefaultParameterHandler 获取 value 是一致的。
for
(ParameterMapping parameterMapping : parameterMappings) {
if
(parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if
(boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
}
else
if
(parameterObject ==
null
) {
value =
null
;
}
else
if
(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
}
else
{
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
// 设置 Environment.id 到 CacheKey 对象中
if
(configuration.getEnvironment() !=
null
) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return
cacheKey;
}
|
通过查看一级缓存类的实现,可以看出一级缓存是通过HashMap结构存储的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/**
* 一级缓存的实现类,部分源代码
*/
public
class
PerpetualCache
implements
Cache {
/**
* 缓存容器
*/
private
Map<Object, Object> cache =
new
HashMap<>();
@Override
public
void
putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public
Object getObject(Object key) {
return
cache.get(key);
}
@Override
public
Object removeObject(Object key) {
return
cache.remove(key);
}
}
|
通过配置项,我们可以控制一级缓存的使用范围,默认是Session级别的,也就是SqlSession的范围内有效。也可以配制成Statement级别,当本次查询结束后立即清除缓存.
当进行插入、更新、删除操作时,也会在执行SQL之前清空以及缓存.
二级缓存 。
Mybatis二级缓存的实现是依靠CachingExecutor装饰其他的Executor实现。原理是在查询的时候先根据CacheKey查询缓存中是否存在值,如果存在则返回缓存的值,没有则查询数据库.
在CachingExecutor中query方法中,就有缓存的使用:
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
|
public
<E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws
SQLException {
Cache cache = ms.getCache();
if
(cache !=
null
) {
// 如果需要清空缓存,则进行清空
flushCacheIfRequired(ms);
if
(ms.isUseCache() && resultHandler ==
null
) {
// 暂时忽略,存储过程相关
ensureNoOutParams(ms, boundSql);
@SuppressWarnings
(
"unchecked"
)
// 从二级缓存中,获取结果
List<E> list = (List<E>) tcm.getObject(cache, key);
if
(list ==
null
) {
// 如果不存在,则从数据库中查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 缓存结果到二级缓存中
tcm.putObject(cache, key, list);
// issue #578 and #116
}
// 如果存在,则直接返回结果
return
list;
}
}
// 不使用缓存,则从数据库中查询
return
delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
|
那么这个Cache是在哪里创建的呢?通过调用的追溯,可以找到它的创建:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
Cache useNewCache(Class<?
extends
Cache> typeClass,
Class<?
extends
Cache> evictionClass,
Long flushInterval,
Integer size,
boolean
readWrite,
boolean
blocking,
Properties props) {
// 创建 Cache 对象
Cache cache =
new
CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.
class
))
.addDecorator(valueOrDefault(evictionClass, LruCache.
class
))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
// 添加到 configuration 的 caches 中
configuration.addCache(cache);
// 赋值给 currentCache
currentCache = cache;
return
cache;
}
|
从方法的第一行可以看出,Cache对象的范围是namespace,同一个namespace下的所有mapper方法共享Cache对象,也就是说,共享这个缓存.
另一个创建方法是通过CacheRef里面的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public
Cache useCacheRef(String namespace) {
if
(namespace ==
null
) {
throw
new
BuilderException(
"cache-ref element requires a namespace attribute."
);
}
try
{
unresolvedCacheRef =
true
;
// 标记未解决
// 获得 Cache 对象
Cache cache = configuration.getCache(namespace);
// 获得不到,抛出 IncompleteElementException 异常
if
(cache ==
null
) {
throw
new
IncompleteElementException(
"No cache for namespace '"
+ namespace +
"' could be found."
);
}
// 记录当前 Cache 对象
currentCache = cache;
unresolvedCacheRef =
false
;
// 标记已解决
return
cache;
}
catch
(IllegalArgumentException e) {
throw
new
IncompleteElementException(
"No cache for namespace '"
+ namespace +
"' could be found."
, e);
}
}
|
这里的话会通过CacheRef中的参数namespace,找到那个Cache对象,且这里使用了unresolvedCacheRef,因为Mapper文件的加载是有顺序的,可能当前加载时引用的那个namespace的Mapper文件还没有加载,所以用这个标记一下,延后加载.
二级缓存通过TransactionalCache来管理,内部使用的是一个HashMap。Key是Cache对象,默认的实现是PerpetualCache,一个namespace下共享这个对象。Value是另一个Cache的对象,默认实现是TransactionalCache,是前面那个Key值的装饰器,扩展了事务方面的功能.
通过查看TransactionalCache的源码我们可以知道,默认查询后添加的缓存保存在待提交对象里.
1
2
3
4
|
public
void
putObject(Object key, Object object) {
// 暂存 KV 到 entriesToAddOnCommit 中
entriesToAddOnCommit.put(key, object);
}
|
只有等到commit的时候才会去刷入缓存.
1
2
3
4
5
6
7
8
9
10
|
public
void
commit() {
// 如果 clearOnCommit 为 true ,则清空 delegate 缓存
if
(clearOnCommit) {
delegate.clear();
}
// 将 entriesToAddOnCommit、entriesMissedInCache 刷入 delegate 中
flushPendingEntries();
// 重置
reset();
}
|
查看clear代码,只是做了标记,并没有真正释放对象。在查询时根据标记直接返回空,在commit才真正释放对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
public
void
clear() {
// 标记 clearOnCommit 为 true
clearOnCommit =
true
;
// 清空 entriesToAddOnCommit
entriesToAddOnCommit.clear();
}
public
Object getObject(Object key) {
// issue #116
// 从 delegate 中获取 key 对应的 value
Object object = delegate.getObject(key);
// 如果不存在,则添加到 entriesMissedInCache 中
if
(object ==
null
) {
entriesMissedInCache.add(key);
}
// issue #146
// 如果 clearOnCommit 为 true ,表示处于持续清空状态,则返回 null
if
(clearOnCommit) {
return
null
;
// 返回 value
}
else
{
return
object;
}
}
|
rollback会清空这些临时缓存:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public
void
rollback() {
// 从 delegate 移除出 entriesMissedInCache
unlockMissedEntries();
// 重置
reset();
}
private
void
reset() {
// 重置 clearOnCommit 为 false
clearOnCommit =
false
;
// 清空 entriesToAddOnCommit、entriesMissedInCache
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
|
根据二级缓存代码可以看出,二级缓存是基于namespace的,可以跨SqlSession。也正是因为基于namespace,如果在不同的namespace中修改了同一个表的数据,会导致脏读的问题.
插件 。
Mybatis的插件是通过代理对象实现的,可以代理的对象有:
Executor
:执行器,执行器是执行过程中第一个代理对象,它内部调用StatementHandler
返回SQL结果。StatementHandler
:语句处理器,执行SQL前调用ParameterHandler
处理参数,执行SQL后调用ResultSetHandler
处理返回结果ParameterHandler
:参数处理器ResultSetHandler
:返回对象处理器这四个对象的接口的所有方法都可以用插件拦截.
插件的实现代码如下:
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
// 创建 ParameterHandler 对象
public
ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
// 创建 ParameterHandler 对象
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
// 应用插件
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return
parameterHandler;
}
// 创建 ResultSetHandler 对象
public
ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
// 创建 DefaultResultSetHandler 对象
ResultSetHandler resultSetHandler =
new
DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// 应用插件
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return
resultSetHandler;
}
// 创建 StatementHandler 对象
public
StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 创建 RoutingStatementHandler 对象
StatementHandler statementHandler =
new
RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 应用插件
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return
statementHandler;
}
/**
* 创建 Executor 对象
*
* @param transaction 事务对象
* @param executorType 执行器类型
* @return Executor 对象
*/
public
Executor newExecutor(Transaction transaction, ExecutorType executorType) {
// 获得执行器类型
executorType = executorType ==
null
? defaultExecutorType : executorType;
// 使用默认
executorType = executorType ==
null
? ExecutorType.SIMPLE : executorType;
// 使用 ExecutorType.SIMPLE
// 创建对应实现的 Executor 对象
Executor executor;
if
(ExecutorType.BATCH == executorType) {
executor =
new
BatchExecutor(
this
, transaction);
}
else
if
(ExecutorType.REUSE == executorType) {
executor =
new
ReuseExecutor(
this
, transaction);
}
else
{
executor =
new
SimpleExecutor(
this
, transaction);
}
// 如果开启缓存,创建 CachingExecutor 对象,进行包装
if
(cacheEnabled) {
executor =
new
CachingExecutor(executor);
}
// 应用插件
executor = (Executor) interceptorChain.pluginAll(executor);
return
executor;
}
|
可以很明显的看到,四个方法内都有interceptorChain.pluginAll()方法的调用,继续查看这个方法:
1
2
3
4
5
6
7
8
9
10
11
12
|
/**
* 应用所有插件
*
* @param target 目标对象
* @return 应用结果
*/
public
Object pluginAll(Object target) {
for
(Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return
target;
}
|
这个方法比较简单,就是遍历interceptors列表,然后调用器plugin方法。interceptors是在解析XML配置文件是通过反射创建的,而创建后会立即调用setProperties方法 。
我们通常配置插件时,会在interceptor.plugin调用Plugin.wrap,这里面通过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
29
30
31
32
33
34
35
36
37
38
39
40
|
/**
* 创建目标类的代理对象
*
* @param target 目标类
* @param interceptor 拦截器对象
* @return 代理对象
*/
public
static
Object wrap(Object target, Interceptor interceptor) {
// 获得拦截的方法映射
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 获得目标类的类型
Class<?> type = target.getClass();
// 获得目标类的接口集合
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 若有接口,则创建目标对象的 JDK Proxy 对象
if
(interfaces.length >
0
) {
return
Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new
Plugin(target, interceptor, signatureMap));
// 因为 Plugin 实现了 InvocationHandler 接口,所以可以作为 JDK 动态代理的调用处理器
}
// 如果没有,则返回原始的目标对象
return
target;
}
@Override
public
Object invoke(Object proxy, Method method, Object[] args)
throws
Throwable {
try
{
// 获得目标方法是否被拦截
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if
(methods !=
null
&& methods.contains(method)) {
// 如果是,则拦截处理该方法
return
interceptor.intercept(
new
Invocation(target, method, args));
}
// 如果不是,则调用原方法
return
method.invoke(target, args);
}
catch
(Exception e) {
throw
ExceptionUtil.unwrapThrowable(e);
}
}
|
而拦截的参数传了Plugin对象,Plugin本身是实现了InvocationHandler接口,其invoke方法里面调用了interceptor.intercept,这个方法就是我们实现拦截处理的地方.
注意到里面有个getSignatureMap方法,这个方法实现的是查找我们自定义拦截器的注解,通过注解确定哪些方法需要被拦截:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
private
static
Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.
class
);
// issue #251
if
(interceptsAnnotation ==
null
) {
throw
new
PluginException(
"No @Intercepts annotation was found in interceptor "
+ interceptor.getClass().getName());
}
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap =
new
HashMap<>();
for
(Signature sig : sigs) {
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k ->
new
HashSet<>());
try
{
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
}
catch
(NoSuchMethodException e) {
throw
new
PluginException(
"Could not find method on "
+ sig.type() +
" named "
+ sig.method() +
". Cause: "
+ e, e);
}
}
return
signatureMap;
}
|
通过源代码我们可以知道,创建一个插件需要做以下事情:
Interceptor
接口。@Intercepts
、@Signature
来表明要拦截哪个对象的哪些方法。plugin
方法中调用Plugin.wrap(target, this)
。setProperties
方法设置一些参数。<plugins>
节点配置<plugin interceptor="你的自定义类的全名称"></plugin>
。可以在第三点中根据具体的业务情况不进行本次SQL操作的代理,毕竟动态代理还是有性能损耗的.
到此这篇关于通过源代码分析Mybatis的功能的文章就介绍到这了,更多相关源代码分析Mybatis内容请搜索我以前的文章或继续浏览下面的相关文章希望大家以后多多支持我! 。
原文链接:https://www.cnblogs.com/Weilence/p/13416986.html 。
最后此篇关于通过源代码分析Mybatis的功能流程详解的文章就讲到这里了,如果你想了解更多关于通过源代码分析Mybatis的功能流程详解的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
全称“Java Virtual Machine statistics monitoring tool”(statistics 统计;monitoring 监控;tool 工具) 用于监控虚拟机的各种运
主要是讲下Mongodb的索引的查看、创建、删除、类型说明,还有就是Explain执行计划的解释说明。 可以转载,但请注明出处。  
1>单线程或者单进程 相当于短链接,当accept之后,就开始数据的接收和数据的发送,不接受新的连接,即一个server,一个client 不存在并发。 2>循环服务器和并发服务器
详解 linux中的关机和重启命令 一 shutdown命令 shutdown [选项] 时间 选项: ?
首先,将json串转为一个JObject对象: ? 1
matplotlib官网 matplotlib库默认英文字体 添加黑体(‘SimHei')为绘图字体 代码: plt.rcParams['font.sans-serif']=['SimHei'
在并发编程中,synchronized关键字是常出现的角色。之前我们都称呼synchronized关键字为重量锁,但是在jdk1.6中对synchronized进行了优化,引入了偏向锁、轻量锁。本篇
一般我们的项目中会使用1到2个数据库连接配置,同程艺龙的数据库连接配置被收拢到统一的配置中心,由DBA统一配置和维护,业务方通过某个字符串配置拿到的是Connection对象。  
实例如下: ? 1
1. MemoryCahe NetCore中的缓存和System.Runtime.Caching很相似,但是在功能上做了增强,缓存的key支持object类型;提供了泛型支持;可以读缓存和单个缓存
argument是javascript中函数的一个特殊参数,例如下文,利用argument访问函数参数,判断函数是否执行 复制代码 代码如下: <script
一不小心装了一个Redis服务,开了一个全网的默认端口,一开始以为这台服务器没有公网ip,结果发现之后悔之莫及啊 某天发现cpu load高的出奇,发现一个minerd进程 占了大量cpu,googl
今天写这个是为了 提醒自己 编程过程 不仅要有逻辑 思想 还有要规范 代码 这样可读性 1、PHP 编程规范与编码习惯最主要的有以下几点: 1 文件说明 2 funct
摘要:虚拟机安装时一般都采用最小化安装,默认没有lspci工具。一台测试虚拟网卡性能的虚拟机,需要lspci工具来查看网卡的类型。本文描述了在一个虚拟机中安装lspci工具的具体步骤。 由于要测试
1、修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统
目录 算术运算符 基本四则运算符 增量赋值运算符 自增/自减运算符 关系运算符 逻
如下所示: ? 1
MapperScannerConfigurer之sqlSessionFactory注入方式讲解 首先,Mybatis中的有一段配置非常方便,省去我们去写DaoImpl(Dao层实现类)的时间,这个
Linux的网络虚拟化是LXC项目中的一个子项目,LXC包括文件系统虚拟化,进程空间虚拟化,用户虚拟化,网络虚拟化,等等,这里使用LXC的网络虚拟化来模拟多个网络环境。 本文从基本的网络设备讲
? 1
我是一名优秀的程序员,十分优秀!