一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務(wù)器之家:專注于服務(wù)器技術(shù)及軟件下載分享
分類導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務(wù)器之家 - 編程語言 - Java教程 - 通過源代碼分析Mybatis的功能流程詳解

通過源代碼分析Mybatis的功能流程詳解

2020-08-03 14:51逆流而上 Java教程

這篇文章主要介紹了通過源代碼分析Mybatis的功能,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

SQL解析

Mybatis在初始化的時候,會讀取xml中的SQL,解析后會生成SqlSource對象,SqlSource對象分為兩種。

  • DynamicSqlSource,動態(tài)SQL,獲取SQL(getBoundSQL方法中)的時候生成參數(shù)化SQL。
  • RawSqlSource,原始SQL,創(chuàng)建對象時直接生成參數(shù)化SQL。

因為RawSqlSource不會重復(fù)去生成參數(shù)化SQL,調(diào)用的時候直接傳入?yún)?shù)并執(zhí)行,而DynamicSqlSource則是每次執(zhí)行的時候參數(shù)化SQL,所以RawSqlSourceDynamicSqlSource的性能要好的。

解析的時候會先解析include標簽和selectkey標簽,然后判斷是否是動態(tài)SQL,判斷取決于以下兩個條件:

  • SQL中有動態(tài)拼接字符串,簡單來說就是是否使用了${}表達式。注意這種方式存在SQL注入,謹慎使用。
  • SQL中有trimwheresetforeachifchoosewhenotherwisebind標簽

相關(guān)代碼如下:

?
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) {
 // 創(chuàng)建 SqlNode 數(shù)組
 List<SqlNode> contents = new ArrayList<>();
 // 遍歷 SQL 節(jié)點的所有子節(jié)點
 NodeList children = node.getNode().getChildNodes();
 for (int i = 0; i < children.getLength(); i++) {
  // 當前子節(jié)點
  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) {
   // 獲得內(nèi)容
   String data = child.getStringBody("");
   // 創(chuàng)建 TextSqlNode 對象
   TextSqlNode textSqlNode = new TextSqlNode(data);
   // 如果是動態(tài)的 TextSqlNode 對象(是否使用了${}表達式)
   if (textSqlNode.isDynamic()) {
    // 添加到 contents 中
    contents.add(textSqlNode);
    // 標記為動態(tài) SQL
    isDynamic = true;
    // 如果是非動態(tài)的 TextSqlNode 對象
   } else {
    // 創(chuàng)建 StaticTextSqlNode 添加到 contents 中
    contents.add(new StaticTextSqlNode(data));
   }
   // 如果類型是 Node.ELEMENT_NODE,其實就是XMl中<where>等那些動態(tài)標簽
  } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
   // 根據(jù)子節(jié)點的標簽,獲得對應(yīng)的 NodeHandler 對象
   String nodeName = child.getNode().getNodeName();
   NodeHandler handler = nodeHandlerMap.get(nodeName);
   if (handler == null) { // 獲得不到,說明是未知的標簽,拋出 BuilderException 異常
    throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
   }
   // 執(zhí)行 NodeHandler 處理
   handler.handleNode(child, contents);
   // 標記為動態(tài) SQL
   isDynamic = true;
  }
 }
 // 創(chuàng)建 MixedSqlNode 對象
 return new MixedSqlNode(contents);
}

參數(shù)解析

Mybais中用于解析Mapper方法的參數(shù)的類是ParamNameResolver,它主要做了這些事情:

  • 每個Mapper方法第一次運行時會去創(chuàng)建ParamNameResolver,之后會緩存
  • 創(chuàng)建時會根據(jù)方法簽名,解析出參數(shù)名,解析的規(guī)則順序是

如果參數(shù)類型是RowBounds或者ResultHandler類型或者他們的子類,則不處理。

如果參數(shù)中有Param注解,則使用Param中的值作為參數(shù)名

如果配置項useActualParamName=true,argn(n>=0)標作為參數(shù)名,如果你是Java8以上并且開啟了-parameters`,則是實際的參數(shù)名

如果配置項useActualParamName=false,則使用n(n>=0)作為參數(shù)名

相關(guān)源代碼

?
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;
 // 獲取方法中每個參數(shù)在SQL中的參數(shù)名
 for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
  // 跳過RowBounds、ResultHandler類型
  if (isSpecialParameter(paramTypes[paramIndex])) {
   continue;
  }
  String name = null;
  // 遍歷參數(shù)上面的所有注解,如果有Param注解,使用它的值作為參數(shù)名
  for (Annotation annotation : paramAnnotations[paramIndex]) {
   if (annotation instanceof Param) {
    hasParamAnnotation = true;
    name = ((Param) annotation).value();
    break;
   }
  }
  // 如果沒有指定注解
  if (name == null) {
   // 如果開啟了useActualParamName配置,則參數(shù)名為argn(n>=0),如果是Java8以上并且開啟-parameters,則為實際的參數(shù)名
   if (config.isUseActualParamName()) {
    name = getActualParamName(method, paramIndex);
   }
   // 否則為下標
   if (name == null) {
    name = String.valueOf(map.size());
   }
  }
  map.put(paramIndex, name);
 }
 names = Collections.unmodifiableSortedMap(map);
}

而在使用這個names構(gòu)建xml中參數(shù)對象和值的映射時,還進行了進一步的處理。

?
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();
 // 無參數(shù),直接返回null
 if (args == null || paramCount == 0) {
  return null;
 } else if (!hasParamAnnotation && paramCount == 1) {
  // 一個參數(shù),并且沒有注解,直接返回這個對象
  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的鍵和對應(yīng)位置的參數(shù)值,其實就是構(gòu)造函數(shù)中存入的值
   param.put(entry.getValue(), args[entry.getKey()]);
   // add generic param names (param1, param2, ...)
   final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
   // 防止覆蓋 @Param 的參數(shù)值
   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參數(shù)
  map.put("collection", object);
  // 如果是一個List集合
  if (object instanceof List) {
   // 額外加入一個list屬性使用
   map.put("list", object);
  }
  return map;
 } else if (object != null && object.getClass().isArray()) {
  // 數(shù)組使用array
  StrictMap<Object> map = new StrictMap<Object>();
  map.put("array", object);
  return map;
 }
 return object;
}

由此我們可以得出使用參數(shù)的結(jié)論:

  • 如果參數(shù)加了@Param注解,則使用注解的值作為參數(shù)
  • 如果只有一個參數(shù),并且不是集合類型和數(shù)組,且沒有加注解,則使用對象的屬性名作為參數(shù)如果只有一個參數(shù),并且是集合類型,則使用collection參數(shù),如果是List對象,可以額外使用list參數(shù)。
  • 如果只有一個參數(shù),并且是數(shù)組,則可以使用array參數(shù)如果有多個參數(shù),沒有加@Param注解的可以使用argn或者n(n>=0,取決于useActualParamName配置項)作為參數(shù),加了注解的使用注解的值。
  • 如果有多個參數(shù),任意參數(shù)只要不是和@Param中的值覆蓋,都可以使用paramn(n>=1)

延遲加載

Mybatis是支持延遲加載的,具體的實現(xiàn)方式根據(jù)resultMap創(chuàng)建返回對象時,發(fā)現(xiàn)fetchType=“lazy”,則使用代理對象,默認使用Javassist(MyBatis 3.3 以上,可以修改為使用CgLib)。代碼處理邏輯在處理返回結(jié)果集時,具體代碼調(diào)用關(guān)系如下:

PreparedStatementHandler.query=> handleResultSets =>handleResultSet=>handleRowValues=>handleRowValuesForNestedResultMap=>getRowValue

getRowValue中,有一個方法createResultObject創(chuàng)建返回對象,其中的關(guān)鍵代碼創(chuàng)建了代理對象:

?
1
2
3
if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
}

另一方面,getRowValue會調(diào)用applyPropertyMappings方法,其內(nèi)部會調(diào)用getPropertyMappingValue,繼續(xù)追蹤到getNestedQueryMappingValue方法,在這里,有幾行關(guān)鍵代碼:

?
1
2
3
4
5
6
7
8
9
10
// 如果要求延遲加載,則延遲加載
if (propertyMapping.isLazy()) {
 // 如果該屬性配置了延遲加載,則將其添加到 `ResultLoader.loaderMap` 中,等待真正使用時再執(zhí)行嵌套查詢并得到結(jié)果對象。
 lazyLoader.addLoader(property, metaResultObject, resultLoader);
 // 返回已定義
 value = DEFERED;
 // 如果不要求延遲加載,則直接執(zhí)行加載對應(yīng)的值
} else {
 value = resultLoader.loadResult();
}

這幾行的目的是跳過屬性值的加載,等真正需要值的時候,再獲取值。

Executor

Executor是一個接口,其直接實現(xiàn)的類是BaseExecutorCachingExecutorBaseExecutor又派生了BatchExecutorReuseExecutorSimpleExecutorClosedExecutor。其繼承結(jié)構(gòu)如圖:

通過源代碼分析Mybatis的功能流程詳解

其中ClosedExecutor是一個私有類,用戶不直接使用它。

  • BaseExecutor:模板類,里面有各個Executor的公用的方法。
  • SimpleExecutor:最常用的Executor,默認是使用它去連接數(shù)據(jù)庫,執(zhí)行SQL語句,沒有特殊行為。ReuseExecutor:SQL語句執(zhí)行后會進行緩存,不會關(guān)閉Statement,下次執(zhí)行時會復(fù)用,緩存的key值是BoundSql解析后SQL,清空緩存使用doFlushStatements。其他與SimpleExecutor相同。
  • BatchExecutor:當有連續(xù)的InsertUpdateDelete的操作語句,并且語句的BoundSql相同,則這些語句會批量執(zhí)行。使用doFlushStatements方法獲取批量操作的返回值。
  • CachingExecutor:當你開啟二級緩存的時候,會使用CachingExecutor裝飾SimpleExecutorReuseExecutorBatchExecutor,Mybatis通過CachingExecutor來實現(xiàn)二級緩存。

緩存

一級緩存

Mybatis一級緩存的實現(xiàn)主要是在BaseExecutor中,在它的查詢方法里,會優(yōu)先查詢緩存中的值,如果不存在,再查詢數(shù)據(jù)庫,查詢部分的代碼如下,關(guān)鍵代碼在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());
 // 已經(jīng)關(guān)閉,則拋出 ExecutorException 異常
 if (closed) {
  throw new ExecutorException("Executor was closed.");
 }
 // 清空本地緩存,如果 queryStack 為零,并且要求清空本地緩存。
 if (queryStack == 0 && ms.isFlushCacheRequired()) {
  clearLocalCache();
 }
 List<E> list;
 try {
  // queryStack + 1
  queryStack++;
  // 從一級緩存中,獲取查詢結(jié)果
  list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
  // 獲取到,則進行處理
  if (list != null) {
   handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
   // 獲得不到,則從數(shù)據(jù)庫中查詢
  } else {
   list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }
 } finally {
  // queryStack - 1
  queryStack--;
 }
 if (queryStack == 0) {
  // 執(zhí)行延遲加載
  for (DeferredLoad deferredLoad : deferredLoads) {
   deferredLoad.load();
  }
  // issue #601
  // 清空 deferredLoads
  deferredLoads.clear();
  // 如果緩存級別是 LocalCacheScope.STATEMENT ,則進行清理
  if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
   // issue #482
   clearLocalCache();
  }
 }
 return list;
}

而在queryFromDatabase中,則會將查詢出來的結(jié)果放到緩存中。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 從數(shù)據(jù)庫中讀取操作
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
 List<E> list;
 // 在緩存中,添加占位對象。此處的占位符,和延遲加載有關(guān),可見 `DeferredLoad#canLoad()` 方法
 localCache.putObject(key, EXECUTION_PLACEHOLDER);
 try {
  // 執(zhí)行讀操作
  list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
 } finally {
  // 從緩存中,移除占位對象
  localCache.removeObject(key);
 }
 // 添加到緩存中
 localCache.putObject(key, list);
 // 暫時忽略,存儲過程相關(guān)
 if (ms.getStatementType() == StatementType.CALLABLE) {
  localOutputParameterCache.putObject(key, parameter);
 }
 return list;
}

而一級緩存的Key,從方法的參數(shù)可以看出,與調(diào)用方法、參數(shù)、rowBounds分頁參數(shù)、最終生成的sql有關(guān)。

?
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.");
 }
 // 創(chuàng)建 CacheKey 對象
 CacheKey cacheKey = new CacheKey();
 // 設(shè)置 id、offset、limit、sql 到 CacheKey 對象中
 cacheKey.update(ms.getId());
 cacheKey.update(rowBounds.getOffset());
 cacheKey.update(rowBounds.getLimit());
 cacheKey.update(boundSql.getSql());
 // 設(shè)置 ParameterMapping 數(shù)組的元素對應(yīng)的每個 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);
  }
 }
 // 設(shè)置 Environment.id 到 CacheKey 對象中
 if (configuration.getEnvironment() != null) {
  // issue #176
  cacheKey.update(configuration.getEnvironment().getId());
 }
 return cacheKey;
}

通過查看一級緩存類的實現(xiàn),可以看出一級緩存是通過HashMap結(jié)構(gòu)存儲的:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
 * 一級緩存的實現(xiàn)類,部分源代碼
 */
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的范圍內(nèi)有效。也可以配制成Statement級別,當本次查詢結(jié)束后立即清除緩存。

當進行插入、更新、刪除操作時,也會在執(zhí)行SQL之前清空以及緩存。

二級緩存

Mybatis二級緩存的實現(xiàn)是依靠CachingExecutor裝飾其他的Executor實現(xiàn)。原理是在查詢的時候先根據(jù)CacheKey查詢緩存中是否存在值,如果存在則返回緩存的值,沒有則查詢數(shù)據(jù)庫。

CachingExecutorquery方法中,就有緩存的使用:

?
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) {
   // 暫時忽略,存儲過程相關(guān)
   ensureNoOutParams(ms, boundSql);
   @SuppressWarnings("unchecked")
   // 從二級緩存中,獲取結(jié)果
   List<E> list = (List<E>) tcm.getObject(cache, key);
   if (list == null) {
    // 如果不存在,則從數(shù)據(jù)庫中查詢
    list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    // 緩存結(jié)果到二級緩存中
    tcm.putObject(cache, key, list); // issue #578 and #116
   }
   // 如果存在,則直接返回結(jié)果
   return list;
  }
 }
 // 不使用緩存,則從數(shù)據(jù)庫中查詢
 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

那么這個Cache是在哪里創(chuàng)建的呢?通過調(diào)用的追溯,可以找到它的創(chuàng)建:

?
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) {
 // 創(chuàng)建 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對象,也就是說,共享這個緩存。

另一個創(chuàng)建方法是通過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中的參數(shù)namespace,找到那個Cache對象,且這里使用了unresolvedCacheRef,因為Mapper文件的加載是有順序的,可能當前加載時引用的那個namespace的Mapper文件還沒有加載,所以用這個標記一下,延后加載。

二級緩存通過TransactionalCache來管理,內(nèi)部使用的是一個HashMap。Key是Cache對象,默認的實現(xiàn)是PerpetualCache,一個namespace下共享這個對象。Value是另一個Cache的對象,默認實現(xiàn)是TransactionalCache,是前面那個Key值的裝飾器,擴展了事務(wù)方面的功能。

通過查看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代碼,只是做了標記,并沒有真正釋放對象。在查詢時根據(jù)標記直接返回空,在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 對應(yīng)的 value
 Object object = delegate.getObject(key);
 // 如果不存在,則添加到 entriesMissedInCache 中
 if (object == null) {
  entriesMissedInCache.add(key);
 }
 // issue #146
 // 如果 clearOnCommit 為 true ,表示處于持續(xù)清空狀態(tài),則返回 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();
}

根據(jù)二級緩存代碼可以看出,二級緩存是基于namespace的,可以跨SqlSession。也正是因為基于namespace,如果在不同的namespace中修改了同一個表的數(shù)據(jù),會導(dǎo)致臟讀的問題。

插件

Mybatis的插件是通過代理對象實現(xiàn)的,可以代理的對象有:

  • Executor:執(zhí)行器,執(zhí)行器是執(zhí)行過程中第一個代理對象,它內(nèi)部調(diào)用StatementHandler返回SQL結(jié)果。
  • StatementHandler:語句處理器,執(zhí)行SQL前調(diào)用ParameterHandler處理參數(shù),執(zhí)行SQL后調(diào)用ResultSetHandler處理返回結(jié)果
  • ParameterHandler:參數(shù)處理器
  • ResultSetHandler:返回對象處理器

這四個對象的接口的所有方法都可以用插件攔截。

插件的實現(xiàn)代碼如下:

?
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
// 創(chuàng)建 ParameterHandler 對象
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
 // 創(chuàng)建 ParameterHandler 對象
 ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
 // 應(yīng)用插件
 parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
 return parameterHandler;
}
 
// 創(chuàng)建 ResultSetHandler 對象
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
           ResultHandler resultHandler, BoundSql boundSql) {
 // 創(chuàng)建 DefaultResultSetHandler 對象
 ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
 // 應(yīng)用插件
 resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
 return resultSetHandler;
}
 
// 創(chuàng)建 StatementHandler 對象
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
 // 創(chuàng)建 RoutingStatementHandler 對象
 StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
 // 應(yīng)用插件
 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
 return statementHandler;
}
 
/**
  * 創(chuàng)建 Executor 對象
  *
  * @param transaction 事務(wù)對象
  * @param executorType 執(zhí)行器類型
  * @return Executor 對象
  */
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
 // 獲得執(zhí)行器類型
 executorType = executorType == null ? defaultExecutorType : executorType; // 使用默認
 executorType = executorType == null ? ExecutorType.SIMPLE : executorType; // 使用 ExecutorType.SIMPLE
 // 創(chuàng)建對應(yīng)實現(xiàn)的 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);
 }
 // 如果開啟緩存,創(chuàng)建 CachingExecutor 對象,進行包裝
 if (cacheEnabled) {
  executor = new CachingExecutor(executor);
 }
 // 應(yīng)用插件
 executor = (Executor) interceptorChain.pluginAll(executor);
 return executor;
}

可以很明顯的看到,四個方法內(nèi)都有interceptorChain.pluginAll()方法的調(diào)用,繼續(xù)查看這個方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
/**
 * 應(yīng)用所有插件
 *
 * @param target 目標對象
 * @return 應(yīng)用結(jié)果
 */
public Object pluginAll(Object target) {
 for (Interceptor interceptor : interceptors) {
  target = interceptor.plugin(target);
 }
 return target;
}

這個方法比較簡單,就是遍歷interceptors列表,然后調(diào)用器plugin方法。interceptors是在解析XML配置文件是通過反射創(chuàng)建的,而創(chuàng)建后會立即調(diào)用setProperties方法

我們通常配置插件時,會在interceptor.plugin調(diào)用Plugin.wrap,這里面通過Java的動態(tài)代理,攔截方法的實現(xiàn):

?
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
/**
 * 創(chuàng)建目標類的代理對象
 *
 * @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);
 // 若有接口,則創(chuàng)建目標對象的 JDK Proxy 對象
 if (interfaces.length > 0) {
  return Proxy.newProxyInstance(
   type.getClassLoader(),
   interfaces,
   new Plugin(target, interceptor, signatureMap)); // 因為 Plugin 實現(xiàn)了 InvocationHandler 接口,所以可以作為 JDK 動態(tài)代理的調(diào)用處理器
 }
 // 如果沒有,則返回原始的目標對象
 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));
  }
  // 如果不是,則調(diào)用原方法
  return method.invoke(target, args);
 } catch (Exception e) {
  throw ExceptionUtil.unwrapThrowable(e);
 }
}

而攔截的參數(shù)傳了Plugin對象,Plugin本身是實現(xiàn)了InvocationHandler接口,其invoke方法里面調(diào)用了interceptor.intercept,這個方法就是我們實現(xiàn)攔截處理的地方。

注意到里面有個getSignatureMap方法,這個方法實現(xiàn)的是查找我們自定義攔截器的注解,通過注解確定哪些方法需要被攔截:

?
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;
}

通過源代碼我們可以知道,創(chuàng)建一個插件需要做以下事情:

  • 創(chuàng)建一個類,實現(xiàn)Interceptor接口。
  • 這個類必須使用@Intercepts@Signature來表明要攔截哪個對象的哪些方法。
  • 這個類的plugin方法中調(diào)用Plugin.wrap(target, this)
  • (可選)這個類的setProperties方法設(shè)置一些參數(shù)。
  • XML中<plugins>節(jié)點配置<plugin interceptor="你的自定義類的全名稱"></plugin>

可以在第三點中根據(jù)具體的業(yè)務(wù)情況不進行本次SQL操作的代理,畢竟動態(tài)代理還是有性能損耗的。

到此這篇關(guān)于通過源代碼分析Mybatis的功能的文章就介紹到這了,更多相關(guān)源代碼分析Mybatis內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!

原文鏈接:https://www.cnblogs.com/Weilence/p/13416986.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 丁香六月色| 国产一区二 | 国产麻豆网 | 成人综合婷婷国产精品久久免费 | 香蕉视频久久 | 日韩欧美一区二区在线观看 | 亚洲成人黄色 | 青青操在线播放 | 亚洲欧美一区二区三区不卡 | 美女禁区视频免费观看精选 | 亚洲第一网色综合久久 | 日本大片免a费观看在线 | 青草久久网 | 99资源在线观看 | 国产农村乱子伦精品视频 | 天天夜夜草草久久伊人天堂 | 亚洲国产综合精品 | 国产成人精品在线观看 | 日本动漫打扑克动画片樱花动漫 | 男人狂躁女人下半身 | 日本在线小视频 | 免费观看在线永久免费xx视频 | 免看一级一片一在线看 | 日本中文字幕永久在线 | 日本视频在线观看播放 | 精灵之森高清在线 | 人与动人物aaaa | 男女拍拍拍免费视频网站 | 精灵之森高清在线 | 日本免费精品 | 青青青国产手机在线播放 | 海派甜心完整版在线观看 | 国产精品合集一区二区 | 欧美作爱福利免费观看视频 | 国产自拍啪啪 | 女色在线观看免费视频 | 成人网址大全 | 精品久久久久久国产 | 日本小视频网站 | 欧美久久一区二区三区 | 国产成人精品一区二区阿娇陈冠希 |