日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【深入浅出MyBatis系列十一】缓存源码分析

發布時間:2024/4/17 编程问答 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【深入浅出MyBatis系列十一】缓存源码分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

為什么80%的碼農都做不了架構師?>>> ??

#0 系列目錄#

  • 深入淺出MyBatis系列
  • 【深入淺出MyBatis系列一】MyBatis入門
  • 【深入淺出MyBatis系列二】配置簡介(MyBatis源碼篇)
  • 【深入淺出MyBatis系列三】Mapper映射文件配置
  • 【深入淺出MyBatis系列四】強大的動態SQL
  • 【深入淺出MyBatis系列五】SQL執行流程分析(源碼篇)
  • 【深入淺出MyBatis系列六】插件原理
  • 【深入淺出MyBatis系列七】分頁插件
  • 【深入淺出MyBatis系列八】SQL自動生成插件
  • 【深入淺出MyBatis系列九】改造Cache插件
  • 【深入淺出MyBatis系列十】與Spring集成
  • 【深入淺出MyBatis系列十一】緩存源碼分析
  • 【深入淺出MyBatis系列十二】終結篇:MyBatis原理深入解析

#1 緩存介紹# MyBatis支持聲明式數據緩存(declarative data caching)。當一條SQL語句被標記為“可緩存”后,首次執行它時從數據庫獲取的所有數據會被存儲在一段高速緩存中,今后執行這條語句時就會從高速緩存中讀取結果,而不是再次命中數據庫。MyBatis提供了默認下基于Java HashMap的緩存實現,以及用于與OSCache、Ehcache、Hazelcast和Memcached連接的默認連接器。MyBatis還提供API供其他緩存實現使用。

重點的那句話就是:MyBatis執行SQL語句之后,這條語句就是被緩存,以后再執行這條語句的時候,會直接從緩存中拿結果,而不是再次執行SQL。

這也就是大家常說的MyBatis一級緩存,一級緩存的作用域scope是SqlSession。MyBatis同時還提供了一種全局作用域global scope的緩存,這也叫做二級緩存,也稱作全局緩存。

MyBatis將數據緩存設計成兩級結構,分為一級緩存、二級緩存:

一級緩存是Session會話級別的緩存,位于表示一次數據庫會話的SqlSession對象之中,又被稱之為本地緩存。一級緩存是MyBatis內部實現的一個特性,用戶不能配置,默認情況下自動支持的緩存,用戶沒有定制它的權利(不過這也不是絕對的,可以通過開發插件對它進行修改);

二級緩存是Application應用級別的緩存,它的是生命周期很長,跟Application的聲明周期一樣,也就是說它的作用范圍是整個Application應用。

MyBatis中一級緩存和二級緩存的組織如下圖所示:

#2 一級緩存# 一級緩存的工作機制:

一級緩存是Session會話級別的,一般而言,一個SqlSession對象會使用一個Executor對象來完成會話操作,Executor對象會維護一個Cache緩存,以提高查詢性能。關于一級緩存的詳細實現,可參見MyBatis一級緩存實現。

##2.1 緩存測試## 同個session進行兩次相同查詢:

@Test public void test() {SqlSession sqlSession = sqlSessionFactory.openSession();try {User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);log.debug(user);User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);log.debug(user2);} finally {sqlSession.close();} }

MyBatis只進行1次數據庫查詢:

==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}

同個session進行兩次不同的查詢:

@Test public void test() {SqlSession sqlSession = sqlSessionFactory.openSession();try {User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);log.debug(user);User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 2);log.debug(user2);} finally {sqlSession.close();} }

MyBatis進行兩次數據庫查詢:

==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 2(Integer) <== Total: 1 User{id=2, name='FFF', age=50, birthday=Sat Dec 06 17:12:01 CST 2014}

不同session,進行相同查詢:

@Test public void test() {SqlSession sqlSession = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();try {User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);log.debug(user);User user2 = (User)sqlSession2.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);log.debug(user2);} finally {sqlSession.close();sqlSession2.close();} }

MyBatis進行了兩次數據庫查詢:

==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}

同個session,查詢之后更新數據,再次查詢相同的語句:

@Test public void test() {SqlSession sqlSession = sqlSessionFactory.openSession();try {User user = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);log.debug(user);user.setAge(100);sqlSession.update("org.format.mybatis.cache.UserMapper.update", user);User user2 = (User)sqlSession.selectOne("org.format.mybatis.cache.UserMapper.getById", 1);log.debug(user2);sqlSession.commit();} finally {sqlSession.close();} }

更新操作之后緩存會被清除:

==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} ==> Preparing: update USERS SET NAME = ? , AGE = ? , BIRTHDAY = ? where ID = ? ==> Parameters: format(String), 23(Integer), 2014-10-12 23:20:13.0(Timestamp), 1(Integer) <== Updates: 1 ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}

很明顯,結果驗證了一級緩存的概念,在同個SqlSession中,查詢語句相同的sql會被緩存,但是一旦執行新增或更新或刪除操作,緩存就會被清除

##2.2 源碼分析## 在分析MyBatis的一級緩存之前,我們先簡單看下MyBatis中幾個重要的類和接口:

org.apache.ibatis.session.Configuration類:MyBatis全局配置信息類

org.apache.ibatis.session.SqlSessionFactory接口:操作SqlSession的工廠接口,具體的實現類是DefaultSqlSessionFactory

org.apache.ibatis.session.SqlSession接口:執行sql,管理事務的接口,具體的實現類是DefaultSqlSession

org.apache.ibatis.executor.Executor接口:sql執行器,SqlSession執行sql最終是通過該接口實現的,常用的實現類有SimpleExecutor和CachingExecutor,這些實現類都使用了裝飾者設計模式

一級緩存的作用域是SqlSession,那么我們就先看一下SqlSession的select過程:

  • 這是DefaultSqlSession(SqlSession接口實現類,MyBatis默認使用這個類)的selectList源碼(我們例子上使用的是selectOne方法,調用selectOne方法最終會執行selectList方法):
  • public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms = configuration.getMappedStatement(statement);List<E> result = executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);return result;} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);} finally {ErrorContext.instance().reset();} }
  • 我們看到SqlSession最終會調用Executor接口的方法。接下來我們看下DefaultSqlSession中的executor接口屬性具體是哪個實現類。DefaultSqlSession的構造過程(DefaultSqlSessionFactory內部):
  • private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType, autoCommit);return new DefaultSqlSession(configuration, executor);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();} }
  • 我們看到DefaultSqlSessionFactory構造DefaultSqlSession的時候,Executor接口的實現類是由Configuration構造的:
  • public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {executorType = executorType == null ? defaultExecutorType : executorType;executorType = executorType == null ? ExecutorType.SIMPLE : executorType;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);}if (cacheEnabled) {executor = new CachingExecutor(executor, autoCommit);}executor = (Executor) interceptorChain.pluginAll(executor);return executor; }

    Executor根據ExecutorType的不同而創建,最常用的是SimpleExecutor,本文的例子也是創建這個實現類。 最后我們發現如果cacheEnabled這個屬性為true的話,那么executor會被包一層裝飾器,這個裝飾器是 CachingExecutor。其中cacheEnabled這個屬性是mybatis總配置文件中settings節點中cacheEnabled子節點的值,默認就是true,也就是說我們在mybatis總配置文件中不配cacheEnabled的話,它也是默認為打開的。

  • 現在,問題就剩下一個了,CachingExecutor執行sql的時候到底做了什么?帶著這個問題,我們繼續走下去(CachingExecutor的query方法):
  • 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, parameterObject, boundSql);if (!dirty) {cache.getReadWriteLock().readLock().lock();try {@SuppressWarnings("unchecked")List<E> cachedList = (List<E>) cache.getObject(key);if (cachedList != null) return cachedList;} finally {cache.getReadWriteLock().readLock().unlock();}}List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocksreturn list;}}return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }

    其中Cache cache = ms.getCache();這句代碼中,這個cache實際上就是個二級緩存,由于我們沒有開啟二級緩存(二級緩存的內容下面會分析),因此這里執行了最后一句話。這里的delegate也就是SimpleExecutor,SimpleExecutor沒有Override父類的query方法,因此最終執行了SimpleExecutor的父類BaseExecutor的query方法。

  • 所以一級緩存最重要的代碼就是BaseExecutor的query方法!
  • 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());if (closed) throw new ExecutorException("Executor was closed.");if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {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--;}if (queryStack == 0) {for (DeferredLoad deferredLoad : deferredLoads) {deferredLoad.load();}deferredLoads.clear(); // issue #601if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {clearLocalCache(); // issue #482}}return list; }

    BaseExecutor的屬性localCache是個PerpetualCache類型的實例,PerpetualCache 類是實現了MyBatis的Cache緩存接口的實現類之一,內部有個Map 類型的屬性用來存儲緩存數據。 這個localCache的類型在BaseExecutor內部是寫死的。 這個localCache就是一級緩存!

  • 接下來我們看下為何執行新增或更新或刪除操作,一級緩存就會被清除這個問題。首先MyBatis處理新增或刪除的時候,最終都是調用update方法,也就是說新增或者刪除操作在MyBatis眼里都是一個更新操作。我們看下DefaultSqlSession的update方法:
  • public int update(String statement, Object parameter) {try {dirty = true;MappedStatement ms = configuration.getMappedStatement(statement);return executor.update(ms, wrapCollection(parameter));} catch (Exception e) {throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);} finally {ErrorContext.instance().reset();} }

    很明顯,這里調用了CachingExecutor的update方法:

    public int update(MappedStatement ms, Object parameterObject) throws SQLException {flushCacheIfRequired(ms);return delegate.update(ms, parameterObject); }

    這里的flushCacheIfRequired方法清除的是二級緩存,我們之后會分析。 CachingExecutor委托給了(之前已經分析過)SimpleExecutor的update方法,SimpleExecutor沒有 Override父類BaseExecutor的update方法,因此我們看BaseExecutor的update方法:

    public int update(MappedStatement ms, Object parameter) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());if (closed) throw new ExecutorException("Executor was closed.");clearLocalCache();return doUpdate(ms, parameter); }
  • 我們看到了關鍵的一句代碼: clearLocalCache(); 進去看看:
  • public void clearLocalCache() {if (!closed) {localCache.clear();localOutputParameterCache.clear();} }

    沒錯,就是這條,sqlsession沒有關閉的話,進行新增、刪除、修改操作的話就是清除一級緩存,也就是SqlSession的緩存。

    #3 二級緩存# 二級緩存的作用域是全局,換句話說,二級緩存已經脫離SqlSession的控制了。二級緩存的作用域是全局的,二級緩存在SqlSession關閉或提交之后才會生效。

    在分析MyBatis的二級緩存之前,我們先簡單看下MyBatis中一個關于二級緩存的類(其他相關的類和接口之前已經分析過):

    org.apache.ibatis.mapping.MappedStatement:

    MappedStatement類在Mybatis框架中用于表示XML文件中一個sql語句節點,即一個<select />、<update />或者<insert />標簽。Mybatis框架在初始化階段會對XML配置文件進行讀取,將其中的sql語句節點對象化為一個個MappedStatement對象。

    二級緩存的工作機制:

    一個SqlSession對象會使用一個Executor對象來完成會話操作,MyBatis的二級緩存機制的關鍵就是對這個Executor對象做文章。如果用戶配置了"cacheEnabled=true",那么MyBatis在為SqlSession對象創建Executor對象時,會對Executor對象加上一個裝飾者:CachingExecutor,這時SqlSession使用CachingExecutor對象來完成操作請求。CachingExecutor對于查詢請求,會先判斷該查詢請求在Application級別的二級緩存中是否有緩存結果,如果有查詢結果,則直接返回緩存結果;如果緩存中沒有,再交給真正的Executor對象來完成查詢操作,之后CachingExecutor會將真正Executor返回的查詢結果放置到緩存中,然后在返回給用戶。

    MyBatis的二級緩存設計得比較靈活,你可以使用MyBatis自己定義的二級緩存實現;你也可以通過實現org.apache.ibatis.cache.Cache接口自定義緩存;也可以使用第三方內存緩存庫,如Memcached等。

    ##3.1 緩存配置## 二級緩存跟一級緩存不同,一級緩存不需要配置任何東西,且默認打開。 二級緩存就需要配置一些東西。本文就說下最簡單的配置,在mapper文件上加上這句配置即可。其實二級緩存跟3個配置有關:

  • mybatis全局配置文件中的setting中的cacheEnabled需要為true(默認為true,不設置也行)
  • mapper配置文件中需要加入<cache>節點
  • mapper配置文件中的select節點需要加上屬性useCache需要為true(默認為true,不設置也行)
  • ##3.2 緩存測試## 不同SqlSession,查詢相同語句,第一次查詢之后commit SqlSession:

    @Test public void testCache2() {SqlSession sqlSession = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();try {String sql = "org.format.mybatis.cache.UserMapper.getById";User user = (User)sqlSession.selectOne(sql, 1);log.debug(user);// 注意,這里一定要提交。 不提交還是會查詢兩次數據庫sqlSession.commit();User user2 = (User)sqlSession2.selectOne(sql, 1);log.debug(user2);} finally {sqlSession.close();sqlSession2.close();} }

    MyBatis僅進行了一次數據庫查詢:

    ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}

    不同SqlSession,查詢相同語句,第一次查詢之后close SqlSession:

    @Test public void testCache2() {SqlSession sqlSession = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();try {String sql = "org.format.mybatis.cache.UserMapper.getById";User user = (User)sqlSession.selectOne(sql, 1);log.debug(user);sqlSession.close();User user2 = (User)sqlSession2.selectOne(sql, 1);log.debug(user2);} finally {sqlSession2.close();} }

    MyBatis僅進行了一次數據庫查詢:

    ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}

    不同SqlSesson,查詢相同語句。 第一次查詢之后SqlSession不提交:

    @Test public void testCache2() {SqlSession sqlSession = sqlSessionFactory.openSession();SqlSession sqlSession2 = sqlSessionFactory.openSession();try {String sql = "org.format.mybatis.cache.UserMapper.getById";User user = (User)sqlSession.selectOne(sql, 1);log.debug(user);User user2 = (User)sqlSession2.selectOne(sql, 1);log.debug(user2);} finally {sqlSession.close();sqlSession2.close();} }

    MyBatis執行了兩次數據庫查詢:

    ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014} ==> Preparing: select * from USERS WHERE ID = ? ==> Parameters: 1(Integer) <== Total: 1 User{id=1, name='format', age=23, birthday=Sun Oct 12 23:20:13 CST 2014}

    ##3.3 源碼分析##

  • XMLMappedBuilder(解析每個mapper配置文件的解析類,每一個mapper配置都會實例化一個XMLMapperBuilder類)的解析方法:
  • private void configurationElement(XNode context) {try {String namespace = context.getStringAttribute("namespace");if (namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);cacheRefElement(context.evalNode("cache-ref"));cacheElement(context.evalNode("cache"));parameterMapElement(context.evalNodes("/mapper/parameterMap"));resultMapElements(context.evalNodes("/mapper/resultMap"));sqlElement(context.evalNodes("/mapper/sql"));buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);} }
  • 我們看到了解析cache的那段代碼:
  • private void cacheElement(XNode context) throws Exception {if (context != null) {String type = context.getStringAttribute("type", "PERPETUAL");Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);String eviction = context.getStringAttribute("eviction", "LRU");Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);Long flushInterval = context.getLongAttribute("flushInterval");Integer size = context.getIntAttribute("size");boolean readWrite = !context.getBooleanAttribute("readOnly", false);Properties props = context.getChildrenAsProperties();builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);} }
  • 解析完cache標簽之后會使用builderAssistant的userNewCache方法,這里的builderAssistant是一個MapperBuilderAssistant類型的幫助類,每個XMLMappedBuilder構造的時候都會實例化這個屬性,MapperBuilderAssistant類內部有個Cache類型的currentCache屬性,這個屬性也就是mapper配置文件中 cache節點所代表的值:
  • public Cache useNewCache(Class<? extends Cache> typeClass,Class<? extends Cache> evictionClass,Long flushInterval,Integer size,boolean readWrite,Properties props) {typeClass = valueOrDefault(typeClass, PerpetualCache.class);evictionClass = valueOrDefault(evictionClass, LruCache.class);Cache cache = new CacheBuilder(currentNamespace).implementation(typeClass).addDecorator(evictionClass).clearInterval(flushInterval).size(size).readWrite(readWrite).properties(props).build();configuration.addCache(cache);currentCache = cache;return cache; }

    OK,現在mapper配置文件中的cache節點被解析到了XMLMapperBuilder實例中的builderAssistant屬性中的currentCache值里。

  • 接下來XMLMapperBuilder會解析select節點,解析select節點的時候使用XMLStatementBuilder進行解析(也包括其他insert,update,delete節點):
  • public void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return;Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String resultMap = context.getStringAttribute("resultMap");String resultType = context.getStringAttribute("resultType");String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);Class<?> resultTypeClass = resolveClass(resultType);String resultSetType = context.getStringAttribute("resultSetType");StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);String nodeName = context.getNode().getNodeName();SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));boolean isSelect = sqlCommandType == SqlCommandType.SELECT;boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);boolean useCache = context.getBooleanAttribute("useCache", isSelect);boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);includeParser.applyIncludes(context.getNode());// Parse selectKey after includes and remove them.processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);String resultSets = context.getStringAttribute("resultSets");String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? new Jdbc3KeyGenerator() : new NoKeyGenerator();}builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }

    這段代碼前面都是解析一些標簽的屬性,我們看到了最后一行使用builderAssistant添加MappedStatement,其中builderAssistant屬性是構造XMLStatementBuilder的時候通過XMLMappedBuilder傳入的,我們繼續看builderAssistant的addMappedStatement方法:

  • 進入setStatementCache:
  • private void setStatementCache(boolean isSelect,boolean flushCache,boolean useCache,Cache cache,MappedStatement.Builder statementBuilder) {flushCache = valueOrDefault(flushCache, !isSelect);useCache = valueOrDefault(useCache, isSelect);statementBuilder.flushCacheRequired(flushCache);statementBuilder.useCache(useCache);statementBuilder.cache(cache); }

    最終mapper配置文件中的<cache/>被設置到了XMLMapperBuilder的builderAssistant屬性中,XMLMapperBuilder中使用XMLStatementBuilder遍歷CRUD節點,遍歷CRUD節點的時候將這個cache節點設置到這些CRUD節點中,這個cache就是所謂的二級緩存!

  • 接下來我們回過頭來看查詢的源碼,CachingExecutor的query方法:
  • 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, parameterObject, boundSql);@SuppressWarnings("unchecked")List<E> list = (List<E>) tcm.getObject(cache, key);if (list == null) {list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks}return list;}}return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
  • 進入TransactionalCacheManager的putObject方法:
  • public void putObject(Cache cache, CacheKey key, Object value) {getTransactionalCache(cache).putObject(key, value); }private TransactionalCache getTransactionalCache(Cache cache) {TransactionalCache txCache = transactionalCaches.get(cache);if (txCache == null) {txCache = new TransactionalCache(cache);transactionalCaches.put(cache, txCache);}return txCache; }
  • TransactionalCache的putObject方法:
  • public void putObject(Object key, Object object) {entriesToRemoveOnCommit.remove(key);entriesToAddOnCommit.put(key, new AddEntry(delegate, key, object)); }

    我們看到,數據被加入到了entriesToAddOnCommit中,這個entriesToAddOnCommit是什么東西呢,它是TransactionalCache的一個Map屬性:

    private Map<Object, AddEntry> entriesToAddOnCommit;

    AddEntry是TransactionalCache內部的一個類:

    private static class AddEntry {private Cache cache;private Object key;private Object value;public AddEntry(Cache cache, Object key, Object value) {this.cache = cache;this.key = key;this.value = value;}public void commit() {cache.putObject(key, value);} }

    好了,現在我們發現使用二級緩存之后:查詢數據的話,先從二級緩存中拿數據,如果沒有的話,去一級緩存中拿,一級緩存也沒有的話再查詢數據庫。有了數據之后在丟到TransactionalCache這個對象的entriesToAddOnCommit屬性中。

    接下來我們來驗證為什么SqlSession commit或close之后,二級緩存才會生效這個問題。

  • DefaultSqlSession的commit方法:
  • public void commit(boolean force) {try {executor.commit(isCommitOrRollbackRequired(force));dirty = false;} catch (Exception e) {throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);} finally {ErrorContext.instance().reset();} }
  • CachingExecutor的commit方法:
  • public void commit(boolean required) throws SQLException {delegate.commit(required);tcm.commit();dirty = false; }
  • tcm.commit即 TransactionalCacheManager的commit方法:
  • public void commit() {for (TransactionalCache txCache : transactionalCaches.values()) {txCache.commit();} }
  • TransactionalCache的commit方法:
  • public void commit() {delegate.getReadWriteLock().writeLock().lock();try {if (clearOnCommit) {delegate.clear();} else {for (RemoveEntry entry : entriesToRemoveOnCommit.values()) {entry.commit();}}for (AddEntry entry : entriesToAddOnCommit.values()) {entry.commit();}reset();} finally {delegate.getReadWriteLock().writeLock().unlock();} }
  • 發現調用了AddEntry的commit方法:
  • public void commit() {cache.putObject(key, value); }

    發現了! AddEntry的commit方法會把數據丟到cache中,也就是丟到二級緩存中!

    關于為何調用close方法后,二級緩存才會生效,因為close方法內部會調用commit方法。本文就不具體說了。 讀者有興趣的話看一看源碼就知道為什么了。

    #4 Cache接口# org.apache.ibatis.cache.Cache是MyBatis的緩存接口,想要實現自定義的緩存需要實現這個接口。MyBatis中關于Cache接口的實現類也使用了裝飾者設計模式。我們看下它的一些實現類:

    簡單說明:

    LRU – 最近最少使用的:移除最長時間不被使用的對象。

    FIFO – 先進先出:按對象進入緩存的順序來移除它們。

    SOFT – 軟引用:移除基于垃圾回收器狀態和軟引用規則的對象。

    WEAK – 弱引用:更積極地移除基于垃圾收集器狀態和弱引用規則的對象。

    <cacheeviction="FIFO" <!-- 可以通過cache節點的eviction屬性設置,也可以設置其他的屬性。-->flushInterval="60000"size="512"readOnly="true"/>

    **cache-ref節點:**mapper配置文件中還可以加入cache-ref節點,它有個屬性namespace。如果每個mapper文件都是用cache-ref,且namespace都一樣,那么就代表著真正意義上的全局緩存。如果只用了cache節點,那僅代表這個這個mapper內部的查詢被緩存了,其他mapper文件的不起作用,這并不是所謂的全局緩存。

    轉載于:https://my.oschina.net/xianggao/blog/552272

    總結

    以上是生活随笔為你收集整理的【深入浅出MyBatis系列十一】缓存源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    99久久激情视频 | 天天天天天天天天操 | 亚洲精品视频在线观看免费视频 | 国产 字幕 制服 中文 在线 | 毛片在线网 | 亚洲三级网 | 99久久夜色精品国产亚洲96 | 久久成人在线视频 | 成人综合婷婷国产精品久久免费 | 亚洲精品午夜国产va久久成人 | 久久天天操 | 色欧美88888久久久久久影院 | 丁香六月激情 | 久久久久久久久久伊人 | 免费不卡中文字幕视频 | 国产麻豆精品一区 | 福利电影一区二区 | 亚洲精品视频中文字幕 | 色婷婷激情 | 美女黄频视频大全 | 91高清完整版在线观看 | 色综合久久久 | 国产精品久久毛片 | 西西www4444大胆视频 | 亚洲精品三级 | 狠狠做深爱婷婷综合一区 | 日本成人中文字幕在线观看 | 成人久久精品视频 | 亚洲国产小视频在线观看 | 免费h视频 | 久久亚洲福利视频 | 天天综合色天天综合 | 国产精品麻豆三级一区视频 | 久草爱视频| 看毛片网站 | 日韩一区二区免费视频 | 国产精品入口a级 | 色视频一区 | 欧美日韩免费一区二区 | 欧美精品一区二区免费 | 2022久久国产露脸精品国产 | 在线日韩视频 | 99视频一区 | 国产精品视频在线观看 | 91女人18片女毛片60分钟 | 久久久久久久久久久影视 | 国产黄a三级三级三级三级三级 | 91在线国产观看 | 日批在线看 | 在线亚洲精品 | 午夜久久久影院 | 国产精品初高中精品久久 | 免费福利片2019潦草影视午夜 | 久久国产影院 | 激情电影影院 | 久草热久草视频 | 在线观看日韩视频 | 国产精品www | 玖玖视频免费在线 | 久久久免费看 | 999久久国产 | 国产成本人视频在线观看 | 亚洲综合色视频在线观看 | 在线观看黄色大片 | 国产成人精品久久亚洲高清不卡 | 高清视频一区二区三区 | 国产一级二级三级在线观看 | 久久伊人色综合 | 91九色国产在线 | 国产成人精品一二三区 | 色婷婷激情电影 | 麻豆传媒电影在线观看 | av电影在线不卡 | 亚洲综合在线观看视频 | 97福利 | 国产 日韩 在线 亚洲 字幕 中文 | 27xxoo无遮挡动态视频 | 99久久超碰中文字幕伊人 | 91精品毛片| 超碰免费观看 | 毛片无卡免费无播放器 | 18国产精品福利片久久婷 | 91久久久久久久一区二区 | 天天射天天操天天色 | 国产五月天婷婷 | 久久精品国产精品亚洲精品 | av短片在线观看 | 成人动漫一区二区 | 一级淫片在线观看 | 国产视频综合在线 | 在线免费亚洲 | 亚洲爱视频 | 麻豆免费看片 | 99久久一区| 婷婷激情综合五月天 | 亚洲不卡在线 | 97视频免费观看 | 亚州日韩中文字幕 | 欧美日韩一区二区三区在线免费观看 | 日韩中文字幕视频在线 | 国产破处在线视频 | 丁香综合网 | 中文字幕精品三级久久久 | 最近日本中文字幕a | 久久99精品国产99久久 | 欧美日韩后| 午夜精品视频一区 | 黄色电影在线免费观看 | 日产中文字幕 | 免费高清在线观看成人 | mm1313亚洲精品国产 | 亚洲欧美日韩一区二区三区在线观看 | 精品福利视频在线观看 | av成人黄色 | 亚洲精品乱码久久久久久按摩 | 中文在线最新版天堂 | 九九热视频在线播放 | 亚洲在线| 日日骑| 精品伊人久久久 | 中文字幕你懂的 | 9999激情| 久久草在线精品 | 欧美精品亚洲二区 | 色香天天 | 免费av在线播放 | 国产一区二区三区免费观看视频 | 亚洲乱亚洲乱妇 | 丰满少妇久久久 | 欧美与欧洲交xxxx免费观看 | 久久久视频在线 | 久久综合五月天 | 国产成人一区二区三区影院在线 | 国产视频日韩视频欧美视频 | 中文字幕免费观看视频 | 天天干视频在线 | 97超碰国产精品女人人人爽 | 婷婷色中文字幕 | 狠狠艹夜夜干 | 日韩在线视频看看 | 欧美日韩电影在线播放 | 久久天堂影院 | 成人午夜影视 | 黄色成人影院 | 91av视频观看 | 视频在线在亚洲 | 亚洲精品国产电影 | 成人黄视频 | 女人18精品一区二区三区 | 成人黄色中文字幕 | 日韩国产在线观看 | 成全免费观看视频 | 亚洲国产成人在线播放 | 五月婷婷黄色 | 91亚洲狠狠婷婷综合久久久 | 亚洲亚洲精品在线观看 | 久草网免费 | 国产a精品| 91成版人在线观看入口 | 91精品国自产拍天天拍 | 亚洲精品在线播放视频 | 久久久www成人免费毛片 | 免费观看一区二区三区视频 | 亚洲精品网站 | 欧美激情综合五月色丁香 | 狠狠狠狠狠狠狠狠干 | 免费在线黄 | 婷婷六月中文字幕 | 欧美日韩国产精品一区二区 | 国产真实在线 | 亚洲九九| 国产中的精品av小宝探花 | 五月天婷婷在线观看视频 | 日韩影视在线观看 | 天天操操操操操 | 日韩伦理片hd| 久久午夜视频 | www,黄视频 | 欧美成人手机版 | 99精品视频在线观看免费 | 中国一级片在线播放 | 国产手机视频 | 天天射网 | 成人一级电影在线观看 | 四虎国产精品成人免费4hu | 97在线看片 | 免费看毛片在线 | 欧美一级片在线观看视频 | 96在线 | 国产亚洲精品久久久久久 | 91精品国产电影 | 91在线日本| 国产一区欧美在线 | 国产午夜激情视频 | 天天插日日射 | 国产精品久久人 | 日韩电影一区二区在线 | 久久精彩免费视频 | 亚洲精品视频在线播放 | 成人免费视频网 | 精品久久久影院 | 日韩剧情| 韩国精品福利一区二区三区 | 久久午夜电影网 | 久久伦理电影网 | 日免费视频 | 国产精品免费成人 | 91中文字幕在线视频 | 天天色综合久久 | 日韩在观看线 | 久久99深爱久久99精品 | 中文字幕专区高清在线观看 | 一级免费黄色 | 成人欧美亚洲 | 国产激情久久久 | 一区二区欧美日韩 | 国产一二区视频 | av高清一区 | 五月天丁香亚洲 | 久久午夜网 | 久久久久久久毛片 | 久久久男人的天堂 | 中文字幕亚洲精品在线观看 | 91精品婷婷国产综合久久蝌蚪 | 久久专区 | 黄色大片日本免费大片 | 激情视频一区二区三区 | 99热.com | 极品久久久久久久 | 亚洲精品乱码久久久久久高潮 | 欧美日韩在线看 | 色综合国产 | 亚洲精品国产精品国产 | 97av视频 | 亚洲一级二级三级 | 午夜精品久久久久久久久久久久久久 | 久久亚洲视频 | 综合天天久久 | 最新动作电影 | 91久久久久久久一区二区 | 亚洲欧美在线综合 | 成年人在线免费看 | 久久久久一区 | 久久五月婷婷丁香社区 | 爱爱av在线| 国产成人一区二区三区免费看 | 色综合久久精品 | 高潮毛片无遮挡高清免费 | 国产乱对白刺激视频不卡 | 国产免费视频在线 | 亚洲激情视频在线 | 国产麻豆视频免费观看 | 九九热re | 91亚洲精品久久久蜜桃借种 | 成年人在线播放视频 | 久久精品91久久久久久再现 | 久久国产免 | 国产精品99久久久久的智能播放 | 香蕉视频啪啪 | 黄色大片av | 在线va网站 | 国产91在线观 | 久草爱视频| 香蕉视频网站在线观看 | 亚洲一级电影 | av一区在线播放 | 久久久久久久久久久免费av | 偷拍精品一区二区三区 | 视频国产一区二区三区 | 天堂av网站 | 午夜国产一区二区 | 久久黄色影院 | 国产精品18p | 国产玖玖精品视频 | 中文字幕免费不卡视频 | 国产午夜麻豆影院在线观看 | 一二三精品视频 | 91成人精品国产刺激国语对白 | 欧美日韩1区 | 成人av一区二区兰花在线播放 | 日韩视频一区二区在线观看 | 麻花传媒mv免费观看 | 中文国产在线观看 | 伊人va| 亚洲婷久久| 一本一本久久a久久精品牛牛影视 | 中文字幕在线免费观看视频 | 色网站在线免费观看 | 九九免费在线观看视频 | 国产精品扒开做爽爽的视频 | 国产精品毛片久久 | 国产高清视频网 | 国产91在线播放 | 中文字幕丰满人伦在线 | 欧美日韩一区二区在线 | 亚洲久草在线 | 午夜精品麻豆 | 狠狠干夜夜 | 91精品国产乱码在线观看 | 国产区精品 | 一区二区三区免费在线观看视频 | 日韩一区二区三区免费视频 | 国产色婷婷在线 | 91精品国产自产91精品 | 久久久999精品视频 国产美女免费观看 | 国产精品久久久久一区二区国产 | 麻豆一区二区 | 亚洲欧美日韩精品久久久 | 97精品国自产拍在线观看 | 国产精品久久一区二区无卡 | 久久久夜色| av在线小说 | 欧美va天堂va视频va在线 | 黄色电影网站在线观看 | 日本精品在线 | 韩日精品在线 | 国产精品久久久久永久免费看 | 深爱激情五月网 | 国产福利91精品 | 激情五月婷婷综合网 | 伊人日日干| 九九精品久久 | 香蕉精品视频在线观看 | 三级动态视频在线观看 | 久久系列 | 九九久久久久久久久激情 | 92中文资源在线 | 日韩视频免费在线 | 青青河边草免费直播 | www.五月天婷婷.com | 亚洲天堂网站视频 | 亚洲精区二区三区四区麻豆 | 91热| 免费在线中文字幕 | 天堂va在线高清一区 | www久草 | 午夜黄色 | 欧美激情视频在线免费观看 | 亚洲黄色区 | 国产一二三区在线观看 | 久久久免费播放 | 国产色婷婷精品综合在线手机播放 | 午夜视频在线观看一区二区三区 | 国产精品久久久视频 | 精品美女在线视频 | 91自拍视频在线观看 | 久久人人添人人爽添人人88v | av动态图片 | 亚洲情影院 | 日日干夜夜爱 | 精品一区二区综合 | 亚洲欧美怡红院 | 超碰在线观看99 | 久久久久99精品成人片三人毛片 | 97福利在线观看 | 91视频国产免费 | 久久99亚洲精品 | 国产91亚洲 | 天堂av免费看 | 亚洲永久av| 成人av在线资源 | 色婷婷www| 久久久香蕉视频 | 成人在线免费观看视视频 | 97电影手机 | 日韩免费在线观看网站 | 中日韩免费视频 | 日日草av | 精品一区二区在线免费观看 | 97在线看 | 国产精国产精品 | 91av观看 | 一区二区三区免费在线观看视频 | 国产 字幕 制服 中文 在线 | 一区二区三区四区在线免费观看 | 国产成人久久精品一区二区三区 | 五月婷婷丁香六月 | 97精品国产91久久久久久 | 干天天 | 国产成人久久av977小说 | 欧洲一区二区三区精品 | 亚洲成年人免费网站 | 麻豆视频在线免费观看 | 中文字幕一区二区三区四区久久 | 国产精品成人免费一区久久羞羞 | 在线观看91久久久久久 | 精品一区二区日韩 | а中文在线天堂 | 欧美日韩一级久久久久久免费看 | 国产亚洲精品日韩在线tv黄 | 999久久精品 | 波多野结衣最新 | 国产96在线视频 | av丝袜在线 | 97精品国产97久久久久久 | 在线免费试看 | 日韩免费网址 | 亚洲国产人午在线一二区 | 欧美日韩国产综合一区二区 | 久久成人国产精品入口 | 黄色精品久久 | 在线观看小视频 | 久久久高清视频 | 丁香花中文字幕 | 国产精品毛片一区二区 | 啪啪午夜免费 | 日日日操操 | 69精品人人人人 | 久久69精品久久久久久久电影好 | 亚洲精品国 | 国产黄色片久久 | 久久乱码卡一卡2卡三卡四 五月婷婷久 | av天天干| 麻豆视频入口 | 国产黄色播放 | 亚洲无吗av | 天天se天天cao天天干 | 日韩精品一区在线播放 | 午夜精品一区二区三区在线播放 | 国产视频一区二区三区在线 | 国产免费av一区二区三区 | 亚洲国产精品视频 | 中文字幕一区二区三区四区视频 | 日韩免费一区 | 天堂av在线中文在线 | h文在线观看免费 | 久久综合射 | 成人av播放 | 91av在线免费观看 | 日本精品一区二区在线观看 | 97超碰资源总站 | 91在线精品观看 | 亚洲成人精品在线观看 | 蜜臀久久99静品久久久久久 | 欧美va天堂在线电影 | 久久综合网色—综合色88 | 日韩av一区二区三区四区 | 人人揉人人揉人人揉人人揉97 | 91亚洲精品久久久久图片蜜桃 | 一二三久久久 | 国产精品成人av在线 | 日本中文不卡 | 国产精品久久久久久久久免费 | 欧美黑人性猛交 | 国产精品自产拍在线观看桃花 | 国产精品亚州 | 亚洲高清91 | 天天干夜夜想 | 国产在线国产 | 天天激情 | 久久精品99国产精品亚洲最刺激 | 欧美91精品久久久久国产性生爱 | 国产欧美日韩视频 | 久久综合久久综合这里只有精品 | 伊人狠狠干 | 免费看污的网站 | 91av电影网| 日韩超碰在线 | 美女视频黄在线观看 | 日日夜夜中文字幕 | 亚洲视频 一区 | 最新国产福利 | 婷婷久久久 | 中文字幕日韩av | 成人v| 伊人成人精品 | 国产高清在线免费 | 午夜少妇av | 国产精品剧情在线亚洲 | 九九热视频在线免费观看 | 欧美激情va永久在线播放 | 日韩精品免费在线观看 | 欧美成a人片在线观看久 | 亚洲精品日韩av | 521色香蕉网站在线观看 | 国产视频午夜 | 黄色视屏av | 欧美在线91| 久久久综合电影 | 91视频这里只有精品 | h视频在线看 | 亚洲精品激情 | 免费在线观看一区二区三区 | 日本乱码在线 | 丁五月婷婷 | 久久伊人综合 | 精品国产一区二区三区噜噜噜 | 96超碰在线| 欧美激情综合色综合啪啪五月 | 国产精品永久免费观看 | 国产一区二区精品 | 午夜精品久久久久久久久久久久 | 免费看的黄色片 | 9999亚洲 | 久久激情婷婷 | 国产专区精品视频 | 欧美日本一二三 | 婷婷综合网 | 日本激情中文字幕 | 九九视频热 | 免费看91的网站 | 欧美成人手机版 | 亚洲精品午夜国产va久久成人 | www.五月天婷婷 | 91中文在线视频 | 中文字幕精品三区 | 久草视频在线播放 | 片网址| 午夜久久久久久久久 | 92av视频| 奇米影视8888 | 成人黄色大片网站 | 久久精品久久久久久久 | 亚州精品天堂中文字幕 | 欧美日韩国产精品一区二区三区 | 国产亚洲人成网站在线观看 | 一区二区三区动漫 | 99re在线视频观看 | 麻豆传媒视频在线免费观看 | 91九色蝌蚪在线 | 婷婷色社区 | 国产成人精品一区二区三区网站观看 | 99热这里只有精品在线观看 | 日韩一区二区三区观看 | 黄色免费观看网址 | 91网页版在线观看 | 日韩在线在线 | 国产精品1区 | 久久久久亚洲精品 | 亚洲人成影院在线 | 视频在线观看99 | 99精品国产一区二区 | 蜜臀一区二区三区精品免费视频 | 亚洲一一在线 | 999男人的天堂 | 国产免费一区二区三区最新 | 久久视频国产精品免费视频在线 | 五月香视频在线观看 | 永久免费精品视频网站 | 国产成人免费在线 | 亚洲精品乱码久久久久久蜜桃动漫 | 97色婷婷成人综合在线观看 | 超碰人人乐 | 色丁香婷婷| 精品在线观看一区二区 | 国产一级视屏 | 午夜视频色 | 一区二区三区视频在线 | 色婷婷免费视频 | 国产黄色片一级 | 一区二区三区在线看 | 美女福利视频一区二区 | av蜜桃在线 | 99久久毛片 | 亚洲精品国产综合99久久夜夜嗨 | 久久在线电影 | 日本一区二区三区视频在线播放 | 国产理伦在线 | 美国av大片| 丁香午夜 | 久久精彩视频 | 六月丁香在线观看 | 久久婷婷激情 | 国产免费xvideos视频入口 | 欧美韩日精品 | 亚洲欧洲av| 中文字幕视频免费观看 | 狠狠操操 | 欧美激情奇米色 | 国产一级免费观看视频 | 99视频精品全部免费 在线 | 久久久久北条麻妃免费看 | 久久视频这里有久久精品视频11 | 久久人人爽视频 | 免费人成在线观看网站 | 天堂av在线免费观看 | 久久99久久99精品免观看软件 | 成人全视频免费观看在线看 | 亚洲欧洲精品一区二区精品久久久 | 亚洲一级国产 | 国产玖玖在线 | 综合网伊人 | 91成人看片| 精品99久久| 国产精品久久久久久久久免费看 | 国产91电影在线观看 | 亚洲天堂网在线播放 | 日韩在线观看不卡 | 高清av中文在线字幕观看1 | 波多野结衣视频一区二区三区 | 伊人伊成久久人综合网小说 | 区一区二区三在线观看 | 在线观看a视频 | av免费线看 | 国产精品乱码久久久久久1区2区 | 亚洲精品视频在线观看免费视频 | 国产免费一区二区三区网站免费 | 黄色三级网站 | 中文字幕国产精品 | 日韩精品视频网站 | 久久电影中文字幕视频 | 亚洲精品理论 | 中文字幕电影在线 | 奇米网网址 | 99re6热在线精品视频 | 久久婷婷一区二区三区 | 精品黄色片 | 美女久久久久久 | 欧美伦理一区二区三区 | 国产一级片不卡 | 免费观看一级 | 久久精品人人做人人综合老师 | 精品在线亚洲视频 | 久久刺激视频 | 日韩精品免费在线观看视频 | 国产视频在线看 | 国产精品欧美日韩 | 成人在线视频一区 | 精品女同一区二区三区在线观看 | 在线观看av国产 | 日韩免费一级电影 | 国产一级视频在线 | 国产视频一区在线播放 | 在线视频 日韩 | 久久综合久久综合这里只有精品 | 密桃av在线| 麻豆成人精品视频 | 人交video另类hd | 五月婷婷六月丁香 | 97精品国产aⅴ | 久久精品国产亚洲 | 成片视频免费观看 | 超碰最新网址 | 亚洲精品国偷拍自产在线观看 | 婷婷丁香av | 亚洲欧美色婷婷 | 国产精品18久久久久久不卡孕妇 | 在线91av | 高清国产午夜精品久久久久久 | 久久久久中文 | 九九视频这里只有精品 | 成人黄色小说视频 | 天天干天天想 | 中文免费在线观看 | av中文在线影视 | a级国产片 | 国产精品s色 | 岛国av在线不卡 | 欧美狠狠操 | 日韩色中色 | 四虎成人网| 精品国产视频在线观看 | 国产精品一区二区三区电影 | 成人av影院在线观看 | 色婷婷激情四射 | 国产在线观看中文字幕 | 伊人色综合久久天天网 | 国产一区二区三区黄 | 日韩在线无 | 婷婷av在线 | 亚洲国产精品999 | 免费高清男女打扑克视频 | 欧美一级高清片 | 久久精品一区二区三 | 一区二区三区不卡在线 | 国产高清精 | 国产精品一区二区视频 | 国产精品一区免费观看 | 国产黄色片免费看 | 国产精品网站一区二区三区 | 国产精品18久久久久久首页狼 | 精品国产乱码久久久久 | 久久久精品综合 | 欧美精品在线视频 | 成人av免费播放 | 欧美极品少妇xbxb性爽爽视频 | 欧美日韩国产色综合一二三四 | 2020天天干夜夜爽 | 中文字幕久久网 | 91免费高清 | 亚洲黄色一级大片 | 91大神电影 | 欧美a级在线播放 | 五月天天av | 人人干天天干 | 天天干天天射天天操 | 鲁一鲁影院 | www国产在线| av免费在线播放 | 欧美地下肉体性派对 | 亚洲自拍偷拍色图 | 中文字幕免费在线 | 午夜精品视频一区二区三区在线看 | 久久1电影院 | 91精品久久久久久 | 国产白浆视频 | 久久精品99久久久久久2456 | 91av社区| 婷婷激情站 | 激情在线五月天 | 一级欧美黄| 免费网站看v片在线a | 色综合天天综合在线视频 | 麻豆久久一区 | 毛片网在线观看 | 久久久精品一区二区三区 | 六月丁香综合网 | 91精品啪在线观看国产线免费 | 国产精品第一 | 精品福利网站 | 国产午夜精品理论片在线 | 91精品黄色 | 亚洲年轻女教师毛茸茸 | 九九视频热 | 天天干天天做 | 精品999在线观看 | 97人人模人人爽人人喊网 | 国产精品久久久久久久久久尿 | 人人操日日干 | 亚洲在线色 | 久久在线 | 久久综合成人网 | 最近免费中文字幕大全高清10 | 久久精品视频2 | 人人揉人人揉人人揉人人揉97 | 久久久91精品国产一区二区精品 | 丁香婷婷久久久综合精品国产 | 国产免费专区 | 久久综合99| 丁香在线视频 | 欧美极度另类性三渗透 | 日韩丝袜在线观看 | 日日夜夜天天久久 | 激情网第四色 | 伊人网av| 久久久久久久久久久久久影院 | 国产成人精品午夜在线播放 | 国产一区二区精品久久 | 中文字幕色网站 | 97精品国产一二三产区 | 亚洲视频久久久久 | 亚洲精品福利在线观看 | 亚洲综合在线五月 | 色香com. | 国产黄 | 粉嫩av一区二区三区免费 | 国产一区在线视频观看 | 国产成人av电影在线 | 国产亚洲久一区二区 | 国产精品久久久一区二区 | 六月丁香婷婷久久 | 91豆麻精品91久久久久久 | 中文国产在线观看 | 亚洲欧美日韩在线看 | av先锋影音少妇 | 波多野结衣精品 | 天天干,狠狠干 | 欧美精品生活片 | 欧美另类调教 | 五月婷婷六月丁香 | 五月婷婷深开心 | 99精品国产一区二区 | 久久视频这里有精品 | 午夜黄色大片 | 九色91视频| 亚洲一级影院 | 伊人伊成久久人综合网小说 | 国产精品video爽爽爽爽 | 日日麻批40分钟视频免费观看 | 亚洲国产高清在线 | 久久热首页 | 色婷婷综合成人av | 欧美一区二区日韩一区二区 | 成人在线电影观看 | 中文字幕视频播放 | 国内成人综合 | 精品 激情 | 亚洲伊人第一页 | 国产高清中文字幕 | 2019免费中文字幕 | 久久午夜精品视频 | 一级α片 | 超碰97在线看 | 天天操夜夜看 | 色在线视频网 | 中文字幕刺激在线 | 国产剧情一区 | 国产日韩三级 | 国产精品嫩草在线 | 免费黄色小网站 | 天天激情综合 | 日韩精品免费在线播放 | 黄色在线免费观看网站 | 天天艹天天 | 国产精品视频一二三 | 亚洲国产理论片 | 五月天亚洲婷婷 | 国产黄在线播放 | 日韩高清国产精品 | 日韩av一区二区在线 | 欧美日韩一级久久久久久免费看 | 中文字幕在线日亚洲9 | 99久久久国产精品免费99 | 久久国产精品网站 | 国产色视频网站2 | 亚洲国产成人在线观看 | 精品久久一区 | 天天爱综合 | 日韩高清dvd | 久草在线视频中文 | av导航福利| 日本中文字幕久久 | 黄色小视频在线观看免费 | 五月婷婷av | 久久久久久美女 | 亚洲资源网 | 国产99中文字幕 | 天天爱天天 | 97在线免费观看视频 | 国产91全国探花系列在线播放 | 欧美日韩国产综合一区二区 | av在线免费播放 | 99热最新精品 | 91视频 - v11av | 91一区啪爱嗯打偷拍欧美 | 激情网第四色 | 99在线热播精品免费 | 国产精品美女久久久网av | 国产韩国日本高清视频 | 中文字幕免费国产精品 | 九九视频免费在线观看 | 黄色三级网站在线观看 | 中文乱幕日产无线码1区 | 黄色日视频 | 日韩精品一区二区在线观看视频 | 公开超碰在线 | 丝袜网站在线观看 | 西西www4444大胆视频 | 91精品入口 | 又黄又爽又色无遮挡免费 | 久久免费视频观看 | 久久久久久久久久久久久国产精品 | 91精品国产乱码久久 | 精品一区免费 | 手机看片 | 日韩在线小视频 | 五月婷婷在线视频观看 | 日韩欧美成人网 | 日韩a级免费视频 | 中文字幕精品一区久久久久 | 日韩欧美一区二区三区在线 | 免费涩涩网站 | av免费播放 | 亚洲欧洲在线视频 | 在线免费观看视频 | 激情五月五月婷婷 | 手机av网站| 最近2019年日本中文免费字幕 | 在线观看国产区 | 久久公开免费视频 | 国产成人l区 | 婷婷社区五月天 | 在线观看aaa| 91大神电影 | 国产精品专区一 | 国产精品视频最多的网站 | 一区二区电影在线观看 | 中文字幕成人一区 | 久久久久观看 | 日韩视频区 | 天天射射天天 | 狠狠色丁香婷婷综合视频 | 久久国产精品久久久 | 成人国产精品久久久久久亚洲 | 国产精选在线 | 麻花豆传媒一二三产区 | 18性欧美xxxⅹ性满足 | 色先锋资源网 | 久久久久综合网 | 六月丁香在线观看 | 久久少妇免费视频 | 久久久久久久av麻豆果冻 | 人人躁| 免费在线激情电影 | 黄色天堂在线观看 | 欧美日韩中文国产一区发布 | 国产精品美女久久久久久网站 | 中文字幕高清视频 | 少妇精69xxtheporn | 久久免费视频国产 | 亚洲激情中文 | 在线播放一区 | 亚洲国产黄色 | 91麻豆精品国产91久久久久久 | 97超碰在线久草超碰在线观看 | 欧美91视频| 国内99视频| 婷婷日日 | 丁香婷婷激情五月 | 亚洲国产一二三 | 亚洲精品久久久久久中文传媒 | 久久久久国产精品午夜一区 | 狠狠色丁香久久婷婷综合丁香 | 久草在线99 | www.夜色321.com | 国产一区在线免费观看 | 在线国产一区二区三区 | 日韩精品综合在线 | 久久久久久久久久网站 | 日韩电影中文字幕 | 免费看一级黄色大全 | 深爱婷婷网 | 日韩最新在线 | 顶级bbw搡bbbb搡bbbb | 国产xxxx | 亚洲成人国产精品 | www.com久久| 亚洲精品在线观看免费 | 欧美性成人 | 中文区中文字幕免费看 | 国产精品中文字幕在线播放 | 黄色片网站大全 | 黄色国产在线 | 黄色www| 一区二区国产精品 | 青青河边草观看完整版高清 | 成人作爱视频 | 国产精品h在线观看 | 亚洲人成精品久久久久 | 国产精品久久久 | 在线观看涩涩 | 91爱在线 | 免费a级毛片在线看 | 欧美日韩一区二区视频在线观看 | 国产精品一区二区三区在线看 | 日韩网站在线 | 中国老女人日b | 麻豆传媒一区二区 | 天天干天天看 | a一片一级 | 日本中文字幕一二区观 | 国产亚洲欧美精品久久久久久 | 国产成人综合精品 | 狠狠狠色丁香综合久久天下网 | 国产精品久久一区二区三区, | 国产精品第2页 | 国产丝袜 | 亚洲综合欧美激情 | 国产97在线播放 | 欧美激情奇米色 | 久久任你操 | 狠狠干综合网 | 免费看的国产视频网站 | 中文字幕欧美日韩va免费视频 | 97精品国产97久久久久久春色 | 久久夜av | 天堂黄色片 | 99国产精品久久久久老师 | 久草视频在线资源站 | 日狠狠| 亚洲精品美女在线观看 | 91精品视频播放 | 99热日本| 国产经典三级 | 在线观看欧美成人 | 精品久久99 | 99久久99久久精品国产片果冰 | 91麻豆精品国产91久久久无需广告 | 日韩av电影免费观看 | 国产精品综合av一区二区国产馆 | 久久免费观看视频 | 久久精品99国产 | 免费av片在线 | 成人av电影在线播放 | 国产精品毛片久久久久久久久久99999999 | 欧美一级片在线观看视频 | 天天插日日插 | 色婷婷丁香 | 欧美性直播 | 国产精品一区二区三区在线看 | 99久久久国产精品免费观看 | 天天鲁一鲁摸一摸爽一爽 | 欧美,日韩 | .国产精品成人自产拍在线观看6 | 色a综合| 黄污网站在线观看 | 日韩视频一二三区 | 欧美aaa级片 | 国产视频亚洲视频 | 国产精品一码二码三码在线 | 视频精品一区二区三区 | 日本最新高清不卡中文字幕 | 亚洲国产日韩一区 | 天天操天天操天天干 | 国产视频一二三 | 亚洲日本在线视频观看 | 国产精品 国内视频 | 国产精品免费在线播放 |