欧美成人午夜精品久久久,国产?V天堂一区二区三区,欧美精品va在线观看,亚洲一区二区三区免费在线观看,av无码精品一区二区久久,欧美性爱视频不卡一区三区,欧美乱人伦视频在线观看,国产一级牲交高潮

ARTICLE DETAIL

資訊詳情

深耕商務(wù)建站與企業(yè)官網(wǎng)運(yùn)營的一線實(shí)戰(zhàn)洞察。

MyBatis-Plus 單測 lambda 緩存報錯根因與五種修復(fù)方案

MyBatis-Plus 單測 lambda 緩存報錯根因與五種修復(fù)方案 上周三下午CI 上一條原本跑得好好的流水線突然變紅日志里躺著這么一行can not find lambda cache for this entity [com.example.demo.entity.User]。第一反應(yīng)是代碼合錯了回滾重跑——還是紅。本地 IDEA 里點(diǎn)一下單測綠的。把 CI 上失敗的那個測試類單獨(dú)拎到本地跑也綠。折騰了半個小時才發(fā)現(xiàn)問題出在一個新同事加的ArgumentCaptor上他為了斷言參數(shù)把LambdaQueryWrapper打印了出來于是 MyBatis-Plus 第一次真正去渲染 SQL 片段結(jié)果在純 Mockito 環(huán)境里實(shí)體對應(yīng)的 lambda 緩存壓根沒被注冊過。這個場景應(yīng)該很多人撞過。用 Mockito 給 MyBatisPlus 的 Service 或 Mapper 做單元測試平時只when(...).thenReturn(...)的時候一切正常一旦某個環(huán)節(jié)觸碰到了Wrapper的 SQL 渲染、或者用真實(shí) SqlSession 走了 H2就會蹦出can not find lambda cache。它既不是 Mockito 的 bug也不是 MyBatisPlus 的 bug純屬兩套機(jī)制在隔離的測試環(huán)境里沒接上頭。下面我把這條報錯從堆棧到根因、從修復(fù)方案到完整實(shí)操按我自己踩坑的順序捋一遍涉及 Mockito、MyBatisPlus、lambda 緩存、分頁攔截、IRepository泛型這幾塊不管你剛學(xué)單元測試還是已經(jīng)在寫測試覆蓋率卡口都能直接拿去用。1. 別急著改代碼先把這個錯定位清楚1.1 從堆棧反推異常究竟是誰拋的先看完整的堆棧長什么樣這一步能省掉后面大量的猜測com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: can not find lambda cache for this entity [com.example.demo.entity.User] at com.baomidou.mybatisplus.core.toolkit.Assert.notNull(Assert.java:xx) at com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper.tryInitCache(AbstractLambdaWrapper.java:xx) at com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper.getColumn(AbstractLambdaWrapper.java:xx) at com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper.columnToString(AbstractLambdaWrapper.java:xx) at com.baomidou.mybatisplus.core.conditions.AbstractLambdaWrapper.lambda$columnToSqlSegment$0(...) at com.baomidou.mybatisplus.core.conditions.AbstractWrapper.getSqlSegment(AbstractWrapper.java:xx) at com.baomidou.mybatisplus.core.conditions.AbstractWrapper.getTargetSql(AbstractWrapper.java:xx) at com.example.demo.UserServiceImplTest.pageByAge(UserServiceImplTest.java:xx)堆棧信息量很大。關(guān)鍵在AbstractLambdaWrapper#tryInitCache它是整個 lambda 解析鏈路上唯一會拋這個斷言的地方。往上看一層是getColumn再往上是columnToString而它的調(diào)用者是一個包裹了columnToSqlSegment的 lambda 表達(dá)式這個表達(dá)式只有在getSqlSegment()被真正執(zhí)行時才跑起來。換句話說這個異常不是構(gòu)造 Wrapper 的那一刻拋的而是Wrapper 需要拼 SQL 的那一刻拋的。這一點(diǎn)極其重要因?yàn)樗忉屃藶槭裁从行┤说膯螠y一直沒事?lián)Q個斷言方式就炸了。兩個測試類可能寫著幾乎一樣的when(mapper.selectList(any())).thenReturn(list)唯一的差別是一個只斷言返回值另一個順手打印了一下 wrapper 的 SQL結(jié)果后者紅前者綠。我在排查時習(xí)慣先做一件事把報錯那一行的實(shí)體類名抄下來然后在工程里全局搜這個類確認(rèn)它有沒有加TableName。遇到過兩次實(shí)體類是從別的模塊復(fù)制過來的、TableName忘了帶本地跑集成測試時因?yàn)殚_了table-prefix也能湊合單測里就現(xiàn)原形。搜實(shí)體類是成本最低的一步排查。1.2 為什么本地跑得好好的一到單測環(huán)境就翻車要理解這件事得先知道 MyBatisPlus 是怎么工作的。它對待LambdaQueryWrapper的方式和對待QueryWrapper完全不同QueryWrapper用的是字符串列名wrapper.eq(user_name, 張三)里的user_name直接就是 SQL 片段MyBatisPlus 不需要查任何東西。而LambdaQueryWrapper用的是方法引用wrapper.eq(User::getUserName, 張三)里的User::getUserName只是一個 Java 的SFunctionMyBatisPlus 必須把它翻譯成數(shù)據(jù)庫列名user_name。這個翻譯動作靠的是一張映射表屬性名 → 列名。這張表不是憑空來的。啟動階段MyBatisPlus 掃描 Mapper 接口、解析泛型、拿到實(shí)體類然后由TableInfoHelper逐個實(shí)體解析注解、拼出完整的TableInfo表名、主鍵、所有字段的TableField、邏輯刪除字段、樂觀鎖字段、自動填充字段最后把每個實(shí)體的屬性名到 ColumnCache的映射塞進(jìn)LambdaUtils維護(hù)的一個靜態(tài)ConcurrentHashMap里。這個動作叫installCache。SpringBootTest或者真實(shí)啟動應(yīng)用時Spring 拉起 MyBatisPlus 的自動配置Mapper 被掃描TableInfoHelper老老實(shí)實(shí)跑完了所有實(shí)體緩存滿滿當(dāng)當(dāng)LambdaQueryWrapper隨便用。而純 Mockito 單測里沒有 Spring 上下文、沒有 Mapper 掃描、沒有SqlSessionFactoryTableInfoHelper一次都沒被調(diào)用那個靜態(tài) Map 是空的。等你真的去解析 lambda 的時候查不到實(shí)體對應(yīng)的條目Assert.notNull就拋出來了。它本質(zhì)上是個環(huán)境缺失問題不是邏輯錯誤。理解到這一層后面的幾個修復(fù)方案就都是同一個思路的不同實(shí)現(xiàn)想辦法把那一步初始化在單測里補(bǔ)上。1.3 哪些寫法會真的觸發(fā)這個異常我把自己和同事碰到過的情況整理成一張表你可以對著自己的測試代碼掃一眼測試?yán)锏膶懛〞|發(fā) SQL 渲染嗎會不會報 lambda cache 錯構(gòu)造 wrapper 后直接傳給 mock 的 mappermapper 返回固定值不會不會when(mapper.selectList(any())).thenReturn(list)不會不會斷言wrapper.getTargetSql()或getSqlSegment()會會用ArgumentCaptor抓 wrapper然后打印或斷言 SQL會會thenAnswer里手動調(diào)invocation.getArgument(0).getSqlSegment()會會測試?yán)镒约捍盍?SqlSessionFactory走 H2 真庫會會Service 內(nèi)部對 wrapper 做了二次加工比如取getSqlSelect()會會單測里手動調(diào)分頁攔截器的beforeQuery做驗(yàn)證會會日志切面在方法返回時把 wrapper 打進(jìn)日志會會這張表第一次看到的時候我有點(diǎn)意外原來絕大多數(shù)看著像會炸的寫法其實(shí)根本不炸。很多人寫單測時用的都是前兩行的模式所以一直相安無事直到某天為了排查參數(shù)問題加了個 SQL 打印或者為了驗(yàn)證 SQL 拼接對不對加了斷言才第一次見到這個錯。這也解釋了一個現(xiàn)象——同一個測試類別人跑綠你跑紅很可能不是環(huán)境差異而是你的斷言更嚴(yán)格。提示如果你只是想快速確認(rèn)是不是這個原因在報錯的地方往上找getTargetSql或getSqlSegment十有八九就在你的測試代碼里躺著。2. 把 lambda 緩存這條鏈路徹底拆開2.1 一行方法引用到底經(jīng)歷了什么User::getUserName在 Java 里是一個編譯期生成的 lambda 實(shí)例。如果你的目標(biāo)類型是SFunctionUser, ?MyBatisPlus 自己的函數(shù)式接口繼承自Serializable那么編譯器會為它生成一個帶有writeReplace方法的內(nèi)部類。這個方法返回一個SerializedLambda對象里面記錄了這段 lambda 的全部元信息implClass實(shí)現(xiàn)類名、implMethodName方法名這里是getUserName、instantiatedMethodType實(shí)例化后的方法簽名等等。MyBatisPlus 的LambdaUtils.extract(SFunction)做的就是這件事反射調(diào)用writeReplace把SerializedLambda摳出來。這一步不需要任何緩存隨時可用。接下來是兩步關(guān)鍵轉(zhuǎn)換從implMethodName里把getUserName變成屬性名userName。這一步由PropertyNamer.methodToProperty完成規(guī)則跟 JavaBean 一致去掉get、首字母小寫isXxx開頭的 boolean getter 也支持lambda$開頭的方法名會做特殊處理。用屬性名去緩存里查對應(yīng)的ColumnCache拿到真正的列名user_name和查詢別名getColumnSelect形如user_name AS userName。第二步就是那個緩存唯一存在的地方。緩存是Map實(shí)體類, Map屬性名, ColumnCache的結(jié)構(gòu)不同版本內(nèi)部實(shí)現(xiàn)略有差異早期按類名字符串做 key后來改成 Class 對象它由TableInfoHelper在初始化實(shí)體時通過LambdaUtils.installCache(tableInfo)寫入。所以整條鏈路是SFunction→SerializedLambda→ 方法名 → 屬性名 → 緩存查詢 → 列名。前四步都是純計算最后一步才是需要初始化的地方。緩存沒寫進(jìn)去前面全白搭。2.2TableInfoHelper在什么時候把緩存寫進(jìn)去正常啟動流程里TableInfoHelper的調(diào)用點(diǎn)很隱蔽MyBatisPlus 的MybatisMapperAnnotationBuilder在解析每個 Mapper 接口時會拿 Mapper 的泛型參數(shù)調(diào)TableInfoHelper.initTableInfo(builderAssistant, entityClass)。也就是說實(shí)體緩存的初始化是被 Mapper 掃描順帶觸發(fā)的只要你的 Mapper 被掃到實(shí)體就會被注冊。這帶來一個很反直覺的推論一個實(shí)體如果沒有對應(yīng)的 Mapper 接口它在生產(chǎn)環(huán)境里其實(shí)也是沒有緩存的。所以有時候你新建了一個只用來接 DTO 的實(shí)體放在entity包下、沒有寫 Mapper然后在某個工具類里用了Wrappers.lambdaQuery(那個實(shí)體)運(yùn)行時照樣報這個錯。這個問題和 Matlab 那種沒聲明就報未定義有點(diǎn)像——不是語法問題是注冊問題。再補(bǔ)一個細(xì)節(jié)initTableInfo內(nèi)部對同一個實(shí)體是冪等的它先查自己的TABLE_INFO_CACHE已經(jīng)存在就直接返回不會重復(fù)解析注解。這意味著你可以在測試基類、BeforeAll、BeforeEach里反復(fù)調(diào)不用擔(dān)心副作用唯一的代價是第一次的那點(diǎn)反射開銷。同時它也是靜態(tài)全局的同一個 JVM 里所有測試類共享所以如果你是多模塊 Maven 構(gòu)建、Surefire 會 fork 多個 JVM緩存是不共享的每個 fork 都要自己初始化一遍。這一點(diǎn)在排查為什么單跑綠、全量跑紅的時候尤其要注意。2.3 版本差異別照著過時博客抄 APIMyBatisPlus 3.5.x 的小版本迭代非常密集lambda 緩存這塊的類名和方法名變過好幾次這也是為什么你在網(wǎng)上搜到的解決方案抄到自己工程里編譯不過。我手上這個 3.5.3.1緩存入口在com.baomidou.mybatisplus.core.toolkit.LambdaUtils取緩存的方法叫g(shù)etColumnMap(Class)緩存值的類型是ColumnCache在toolkit.support包下面。而 3.5.4 之后TableInfo自己增加了columnMap和fieldColumnMap兩個字段LambdaUtils上的取緩存方法名調(diào)整成了getColumnCacheColumnCache的包路徑也挪了位置。如果你搜到的方案里寫的是LambdaUtils.getColumnMap而你的依賴是 3.5.7那編譯不過很正常。注意涉及 MyBatisPlus 內(nèi)部 API 的代碼第一件事是打開 IDE按住 Ctrl 點(diǎn)進(jìn)LambdaUtils看源碼確認(rèn)你當(dāng)前版本的方法簽名。我見過太多人因?yàn)閺?fù)制了一個不匹配的版本號在initTableInfo的參數(shù)順序上卡了一下午——老版本是(Configuration, String namespace, Class)新版本是(MapperBuilderAssistant, Class)看著像參數(shù)順序完全不一樣。我個人的做法是把 MyBatisPlus 的版本鎖死在父 pom 的dependencyManagement里測試基類里用到的內(nèi)部 API 全部加一行注釋說明此寫法適配 3.5.3.x升級需復(fù)核。這樣別人升級依賴時至少會看到提示不至于某天 CI 突然全紅還找不到人。3. 五種修復(fù)方案我挨個試了一遍3.1 方案一手動調(diào)用initTableInfo最輕量這是我最推薦的方案改動量極小只依賴mybatis-plus-core不需要引入任何 Spring 測試上下文跑起來是毫秒級。核心就三行代碼MybatisConfiguration configuration new MybatisConfiguration(); MapperBuilderAssistant assistant new MapperBuilderAssistant(configuration, ); TableInfoHelper.initTableInfo(assistant, User.class);MapperBuilderAssistant是 MyBatis 原生的類用來在構(gòu)建期輔助解析MybatisConfiguration是 MyBatisPlus 對 MyBatisConfiguration的擴(kuò)展里面注冊了 MyBatisPlus 需要的LanguageDriver和類型處理器。第二個參數(shù)是resource傳空字符串就夠了它只用于錯誤提示和日志。為什么選這兩個類而不是自己 new 一個Configuration因?yàn)門ableInfoHelper.initTableInfo內(nèi)部會調(diào)GlobalConfigUtils.getGlobalConfig(builderAssistant.getConfiguration())去拿全局配置涉及主鍵策略、邏輯刪除默認(rèn)值、字段自動填充這些而GlobalConfigUtils內(nèi)部對一個非MybatisConfiguration的實(shí)例也能勉強(qiáng)工作但在某些版本上會走到不一致的分支。用MybatisConfiguration是最保險的。這里有個真實(shí)的坑值得單獨(dú)說GlobalConfigUtils內(nèi)部用configuration.toString()作為全局配置緩存的 key。MybatisConfiguration沒有重寫toString所以每次new出來的實(shí)例 key 都不一樣每次初始化都會往那個靜態(tài) Map 里塞一個新條目。單測場景下總共就那么幾個測試類無所謂但如果你在BeforeEach里每次 new 一個新的MybatisConfiguration幾百個用例跑下來會留幾百個GlobalConfig對象不放。所以我在測試基類里把MybatisConfiguration和MapperBuilderAssistant都做成static final全局共用一個實(shí)例。3.2 方案二MybatisPlusTest官方給的切片測試姿勢從 3.5.3 開始MyBatisPlus 提供了一個獨(dú)立的測試模塊mybatis-plus-boot-starter-test里面有個注解MybatisPlusTest。它的定位和 Spring Boot 的DataJpaTest、WebMvcTest一樣屬于切片測試只啟動 MyBatis 相關(guān)的那一層不加載整個應(yīng)用上下文。MybatisPlusTest class UserMapperTest { Autowired private UserMapper userMapper; Test void testSelectByLambda() { LambdaQueryWrapperUser wrapper Wrappers.UserlambdaQuery() .eq(User::getUserName, 張三); ListUser users userMapper.selectList(wrapper); assertThat(users).isNotNull(); } }這個方案最大的好處是Mapper 是真的TableInfo是自動初始化的你不需要手動管理緩存而且能順帶驗(yàn)證 SQL 在真實(shí)數(shù)據(jù)庫上跑得通。MybatisPlusTest默認(rèn)會用內(nèi)嵌數(shù)據(jù)庫替換掉項(xiàng)目的 DataSource所以你得在測試依賴?yán)锛由?H2 或者 HSQLDB否則啟動時找不到可用數(shù)據(jù)源會直接報錯。它的缺點(diǎn)也很明確這是集成測試不是單元測試。啟動一次上下文大概幾百毫秒到一秒如果用它覆蓋所有 Service 方法整個測試套件的執(zhí)行時間會從幾十秒漲到幾分鐘。我一般的分配是Mapper 層、復(fù)雜 SQL、分頁邏輯用MybatisPlusTestService 的業(yè)務(wù)分支參數(shù)校驗(yàn)、異常拋出、緩存判斷、事務(wù)邊界用純 Mockito 單測。兩層各司其職覆蓋率反而更實(shí)。3.3 方案三SpringBootTestMockBean最貼近生產(chǎn)如果項(xiàng)目本身就有一整套 Spring Boot 測試腳手架最省事的做法是在原有測試類上加SpringBootTest讓完整的自動配置跑一遍緩存自然就有了然后把你需要隔離的外部依賴第三方接口、Redis、消息隊(duì)列用MockBean替掉。SpringBootTest class UserServiceImplSpringTest { Autowired private UserService userService; MockBean private UserMapper userMapper; Test void pageByAge_should_return_empty_when_no_data() { PageUser mockPage new Page(1, 10); mockPage.setRecords(Collections.emptyList()); mockPage.setTotal(0L); when(userMapper.selectPage(any(), any())).thenReturn(mockPage); PageUser result userService.pageByAge(20, 1, 10); assertThat(result.getRecords()).isEmpty(); } }注意這里MockBean替換的是 Mapper而 Service 是真實(shí)的 Spring Bean。這個組合下 lambda 緩存是齊的因?yàn)樗鼘儆谏舷挛某跏蓟囊徊糠指?Mapper 是不是 mock 無關(guān)。這是它比方案一穩(wěn)的地方。代價是啟動慢而且有個隱蔽的坑MockBean會污染 Spring 的上下文緩存。Spring 的測試框架會為不同的MockBean組合創(chuàng)建不同的上下文如果一個模塊里每個測試類的MockBean都不一樣上下文緩存命中率極低跑一輪測試可能啟動十幾個完整的 Spring 上下文。解決思路是把MockBean的定義收斂到基類里同一個模塊的測試類盡量共用同一套 mock 集合。3.4 方案四換成QueryWrapper字符串列名臨時兜底如果只是某個工具類的單測卡在這里短期內(nèi)又不想動測試基建最粗暴的辦法是把LambdaQueryWrapper換成QueryWrapper用字符串列名QueryWrapperUser wrapper new QueryWrapperUser() .eq(user_name, 張三) .orderByDesc(id);這條路繞開了 lambda 解析自然也就繞開了緩存一定能跑通。但我把它排在第四位原因是它解決的是測試的問題破壞的是代碼的一致性生產(chǎn)代碼里用 lambda 保證編譯期類型安全測試?yán)镉米址忻坏┳侄沃孛鸌DE 重構(gòu)會改到生產(chǎn)代碼改不到測試代碼測試悄悄失效。如果確實(shí)要用我的建議是只在一個很小的、明確標(biāo)注// TODO 臨時方案待測試基建補(bǔ)齊后回退的方法里用并且用常量而不是裸字符串// 和實(shí)體字段一一對應(yīng)用 Lombok 的 FieldNameConstants 生成 QueryWrapperUser wrapper new QueryWrapperUser() .eq(User.Fields.userName, 張三);Lombok 從 1.18.4 開始提供FieldNameConstants生成的字符串常量在字段重命名時會跟著變能補(bǔ)回來一部分安全性。這個折中方案我在遷移老項(xiàng)目的時候用過效果還行。3.5 方案五mockStatic硬改LambdaUtils知道就好還有一種技術(shù)流的解法用 Mockito 的靜態(tài) mock 能力把LambdaUtils的取緩存方法直接打樁返回一個手工構(gòu)造的 Maptry (MockedStaticLambdaUtils mocked Mockito.mockStatic(LambdaUtils.class)) { MapString, ColumnCache fakeCache new HashMap(); fakeCache.put(userName, new ColumnCache(user_name, user_name AS userName)); mocked.when(() - LambdaUtils.getColumnMap(User.class)).thenReturn(fakeCache); // 執(zhí)行測試 }這個寫法有幾個致命問題第一ColumnCache的構(gòu)造簽名在不同版本里不一樣編譯期就可能掛第二靜態(tài) mock 是全局生效的如果你用了 JUnit 5 的并行測試其他線程的用例會受影響必須嚴(yán)格放在 try-with-resources 里第三它需要mockito-inlineMockito 5.x 之后已經(jīng)內(nèi)置為默認(rèn) mock maker不用單獨(dú)引老版本項(xiàng)目加這個依賴可能引起別的行為變化。所以我把這個方案列出來只是為了讓你在看到一個開源項(xiàng)目這么寫的時候不至于懵實(shí)際落地我一次都沒用過。緩存初始化本身只需要三行代碼沒必要用靜態(tài) mock 去對抗它。五個方案橫向?qū)Ρ确桨竼娱_銷是否需要 Spring與生產(chǎn)環(huán)境一致性推薦場景手動initTableInfo極低毫秒級否中僅緩存層一致Service 業(yè)務(wù)邏輯單測首選MybatisPlusTest中幾百毫秒是高M(jìn)yBatis 層完整Mapper 層、分頁、復(fù)雜 SQLSpringBootTestMockBean高1 秒以上是最高需要驗(yàn)證 Bean 裝配、事務(wù)、切面換QueryWrapper無否低臨時兜底不推薦長期用mockStatic低否低只建議在閱讀源碼時理解不建議落地4. 完整跑一遍UserServiceImpl的單測怎么寫4.1 依賴清單和版本先把 pom 里需要的東西列清楚。這里刻意只列測試相關(guān)的生產(chǎn)依賴按你自己的項(xiàng)目來。properties mybatis-plus.version3.5.3.1/mybatis-plus.version mockito.version4.11.0/mockito.version junit.version5.9.3/junit.version /properties dependencies dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version${mybatis-plus.version}/version /dependency !-- 純 Mockito 單測只需要 core -- dependency groupIdorg.mockito/groupId artifactIdmockito-core/artifactId version${mockito.version}/version scopetest/scope /dependency dependency groupIdorg.mockito/groupId artifactIdmockito-junit-jupiter/artifactId version${mockito.version}/version scopetest/scope /dependency !-- 需要靜態(tài) mock 或 final 類的 mock 才加Mockito 5 已內(nèi)置 -- dependency groupIdorg.mockito/groupId artifactIdmockito-inline/artifactId version${mockito.version}/version scopetest/scope /dependency !-- 用 MybatisPlusTest 才需要 -- dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter-test/artifactId version${mybatis-plus.version}/version scopetest/scope /dependency dependency groupIdcom.h2database/groupId artifactIdh2/artifactId version2.1.214/version scopetest/scope /dependency /dependencies有一點(diǎn)要提醒mockito-inline在 Mockito 5.x 里已經(jīng)被合并進(jìn)mockito-core默認(rèn)的 mock maker 就是 inline 的。如果你的項(xiàng)目還用著 Spring Boot 2.x 帶來的 Mockito 4.x那就得顯式引如果你已經(jīng)在 Spring Boot 3.x看到別人 pom 里還留著mockito-inline反而會互相沖突最好確認(rèn)一下。4.2 被測代碼和實(shí)體實(shí)體設(shè)計得稍微復(fù)雜一點(diǎn)把幾個容易踩坑的點(diǎn)都覆蓋上Data TableName(t_user) public class User { TableId(type IdType.AUTO) private Long id; private String userName; private Integer age; /** 字段名是數(shù)據(jù)庫關(guān)鍵字需要轉(zhuǎn)義 */ TableField(desc) private String desc; TableLogic private Integer deleted; TableField(exist false) private String extraInfo; }這里塞了三個典型場景desc是 MySQL 的關(guān)鍵字需要反引號轉(zhuǎn)義MyBatisPlus 的TableField會把它寫進(jìn)ColumnCachedeleted加了TableLogic初始化時會被識別成邏輯刪除字段extraInfo加了TableField(exist false)它不會進(jìn)入列名映射所以你不能用User::getExtraInfo去構(gòu)造查詢條件用了照樣報找不到對應(yīng)列或者直接報緩存問題。Service 層public interface UserService extends IServiceUser { PageUser pageByAge(Integer age, long current, long size); } Service public class UserServiceImpl extends ServiceImplUserMapper, User implements UserService { Override public PageUser pageByAge(Integer age, long current, long size) { LambdaQueryWrapperUser wrapper Wrappers.UserlambdaQuery() .eq(age ! null, User::getAge, age) .orderByDesc(User::getId); return this.page(Page.of(current, size), wrapper); } }4.3 測試基類把初始化收斂到一處不要在每一個測試類里重復(fù)寫initTableInfo抽一個基類出來public abstract class BaseMockTest { /** 全局共用一份避免每次 new 都往 GlobalConfigUtils 里加新 key */ private static final MybatisConfiguration CONFIGURATION new MybatisConfiguration(); private static final MapperBuilderAssistant ASSISTANT new MapperBuilderAssistant(CONFIGURATION, ); static { // 這個模塊里所有可能被 lambda 引用的實(shí)體集中在這里注冊 TableInfoHelper.initTableInfo(ASSISTANT, User.class); TableInfoHelper.initTableInfo(ASSISTANT, Order.class); } }放在static塊里而不是BeforeEach好處有三個。第一每個測試類只初始化一次而不是每個用例一次第二static塊在類加載時執(zhí)行早于所有測試生命周期回調(diào)不會出現(xiàn)某個BeforeAll里先跑了別的邏輯的順序問題第三JUnit 5 在并行執(zhí)行測試方法時static塊本身由 JVM 保證只執(zhí)行一次不存在并發(fā)寫緩存的問題。如果你擔(dān)心后續(xù)新增實(shí)體忘了注冊可以寫一個自檢啟動測試時掃一遍com.xxx.entity包下的所有類凡是帶TableName的如果TableInfoHelper.getTableInfo(clazz) null就直接拋異常。這個自檢我在一個三十多個實(shí)體的項(xiàng)目里加過成本不高收益是再也不會有新同事加了實(shí)體忘了注冊、然后一臉懵地來找我這種事了。實(shí)現(xiàn)思路可以用 Spring 的ClassPathScanningCandidateComponentProvider也可以簡單地用 Reflections 庫。4.4 測試用例覆蓋三種典型斷言方式ExtendWith(MockitoExtension.class) class UserServiceImplTest extends BaseMockTest { Mock private UserMapper userMapper; InjectMocks private UserServiceImpl userService; Test DisplayName(分頁查詢mock 返回數(shù)據(jù)斷言結(jié)果) void pageByAge_normal() { User u1 new User(); u1.setId(1L); u1.setUserName(張三); u1.setAge(20); PageUser mockPage Page.of(1, 10); mockPage.setRecords(Collections.singletonList(u1)); mockPage.setTotal(1L); when(userMapper.selectPage(any(), any())).thenReturn(mockPage); PageUser result userService.pageByAge(20, 1, 10); assertThat(result.getRecords()).hasSize(1); assertThat(result.getTotal()).isEqualTo(1L); } Test DisplayName(斷言 wrapper 的 SQL這條在修復(fù)前會報 lambda cache) void pageByAge_checkSql() { when(userMapper.selectPage(any(), any())).thenReturn(Page.of(1, 10)); userService.pageByAge(20, 1, 10); ArgumentCaptorLambdaQueryWrapperUser captor ArgumentCaptor.forClass(LambdaQueryWrapper.class); verify(userMapper).selectPage(any(), captor.capture()); String sql captor.getValue().getTargetSql(); // 注意getTargetSql 會把 #{ew.paramNameValuePairs.MPGENVAL1} 替換成 ? assertThat(sql).contains(age).contains(ORDER BY id DESC); } Test DisplayName(參數(shù)為 null 時不拼接 age 條件) void pageByAge_nullAge() { when(userMapper.selectPage(any(), any())).thenReturn(Page.of(1, 10)); userService.pageByAge(null, 1, 10); ArgumentCaptorLambdaQueryWrapperUser captor ArgumentCaptor.forClass(LambdaQueryWrapper.class); verify(userMapper).selectPage(any(), captor.capture()); String sql captor.getValue().getTargetSql(); assertThat(sql).doesNotContain(age); } }第二個和第三個用例就是會報can not find lambda cache的典型寫法因?yàn)間etTargetSql()會真正去渲染 SQL 片段。加上基類的靜態(tài)初始化之后這兩個用例就能穩(wěn)定通過了。這里還有一個值得留意的細(xì)節(jié)getTargetSql()返回的 SQL 里參數(shù)占位符是?而不是#{ew.paramNameValuePairs.MPGENVAL1}。這是因?yàn)閃rapper接口里對getTargetSql的默認(rèn)實(shí)現(xiàn)做了正則替換。如果你需要斷言具體的參數(shù)值得去看getParamNameValuePairs()返回的 Map鍵是MPGENVAL1這種自動生成的序號而不是屬性名。這個鍵名是 MyBatisPlus 內(nèi)部生成的寫斷言時不要去猜順序用getParamNameValuePairs().values()做集合斷言更穩(wěn)。另外一個容易忽略的點(diǎn)ExtendWith(MockitoExtension.class)默認(rèn)開啟嚴(yán)格打樁檢查。如果某個when(...)在測試?yán)餂]被用到會直接報UnnecessaryStubbingException讓測試失敗。上面前兩個用例里when用到了第三個也用到了都沒問題。但如果你寫了一個只為兜底、實(shí)際沒觸發(fā)的when記得加lenient()或者改用Mockito.lenient().when(...)。4.5 mock 場景下分頁的坑和 500 條限制分頁這塊必須單獨(dú)說因?yàn)樗驼鎸?shí)環(huán)境的行為差異很大很多人第一次寫會踩。userService.pageByAge(...)內(nèi)部調(diào)的是ServiceImpl#page(IPage, Wrapper)這個方法最終委托給baseMapper.selectPage(page, wrapper)。在真實(shí)環(huán)境里selectPage會被PaginationInnerInterceptor攔截自動拼LIMIT、自動執(zhí)行 count 查詢、把結(jié)果塞進(jìn)Page對象。但在 Mockito 單測里整個攔截器鏈都不存在selectPage是你自己打樁的返回什么就是什么。所以Page的records和total必須你自己設(shè)置否則斷言getTotal()時會拿到默認(rèn)值 0。Page的pages總頁數(shù)是在getPages()被調(diào)用時懶計算的依賴total和size只要total設(shè)對了就沒問題。分頁參數(shù)校驗(yàn)比如 current 小于 1 時自動糾正為 1是Page構(gòu)造器干的跟攔截器無關(guān)可以正常測試。至于熱詞里提到的單頁 500 條限制那來自PaginationInnerInterceptor的maxLimit配置典型寫法是Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); // 單頁最多 500 條超出后按 overflow 策略處理 pagination.setMaxLimit(500L); // true超過 maxLimit 時按 maxLimit 處理false超過時拋異常 pagination.setOverflow(false); interceptor.addInnerInterceptor(pagination); return interceptor; }這個限制在純 Mockito 單測里是驗(yàn)證不到的因?yàn)樵?mock 的selectPage被調(diào)用之前攔截器根本沒機(jī)會介入。如果你想在單測里覆蓋這個行為有兩條路一是切到MybatisPlusTest讓它進(jìn)真實(shí)的攔截器鏈二是單獨(dú)對PaginationInnerInterceptor寫一個測試手動構(gòu)造Executor和MappedStatement調(diào)它的beforeQuery。后者寫起來很啰嗦需要 mock 掉Invocation、Executor、MappedStatement、BoundSql一堆對象我在一個對分頁安全要求很高的項(xiàng)目里寫過一次大概六十行代碼收益是能把前端傳 size100000 時后端是否真的截斷這條規(guī)則鎖死。如果你們的分頁確實(shí)是個風(fēng)險點(diǎn)值得花這個成本。另外提一句IRepository。MyBatisPlus 3.5.4 之后引入了IRepositoryT和CrudRepositoryM, T寫法和IService不太一樣實(shí)體類型通過泛型解析而不是ServiceImpl的getSuperClassGenericType。這里有個坑千萬不要用Mockito.mock(CrudRepository.class)去 mock 這類倉儲接口Mockito 生成的動態(tài)代理類會把泛型信息擦掉getEntityClass()拿回 null最終表現(xiàn)還是 lambda 緩存查不到。正確做法是用InjectMocks注入一個真實(shí)的實(shí)現(xiàn)類實(shí)例或者用Spy包一個真實(shí)實(shí)例。5. 排查速查表和幾個隱蔽的坑5.1 常見問題速查報錯或現(xiàn)象大概率原因處理辦法can not find lambda cache for this entity [X]測試環(huán)境沒初始化TableInfo基類靜態(tài)塊里調(diào)initTableInfo單跑綠、全量跑紅多 JVM fork緩存不共享或靜態(tài)狀態(tài)被污染每個 JVM 自己初始化檢查靜態(tài)變量并行測試偶發(fā)失敗多個線程同時寫靜態(tài)緩存初始化放static塊或BeforeAll報錯實(shí)體在生產(chǎn)環(huán)境從不報該實(shí)體沒有 Mapper從沒被注冊補(bǔ) Mapper或確認(rèn)是否該用 lambdanot found method writeReplace傳入的不是SFunction或方法引用來自非序列化接口檢查是否誤用了普通FunctionCannot call abstract real method對接口用了Spy換Mock或注入真實(shí)實(shí)現(xiàn)UnnecessaryStubbingException打了樁但沒用上刪掉多余樁或用lenient()getTargetSql里看不到具體參數(shù)值占位符被替換成?了改用getParamNameValuePairs()5.2 幾個不太容易被發(fā)現(xiàn)的坑坑一實(shí)體類里的TableField(exist false)字段不能用于 lambda 查詢。這個字段壓根不進(jìn)列名映射你寫wrapper.eq(User::getExtraInfo, x)屬性名extraInfo在緩存里查不到。不同版本的行為不一樣有的拋NPE、有的拋can not find column報錯信息跟緩存問題長得像但根因不同。我一般建議 DTO 和實(shí)體徹底分開實(shí)體里不放展示字段。坑二InjectMocks和Mock混用時Service 必須是真實(shí)實(shí)例。如果你的UserServiceImpl是通過Mockito.mock(UserServiceImpl.class)造出來的getEntityClass()反查泛型時會拿到 Mockito 生成的代理類泛型鏈斷了entityClass為 null。這時候tryInitCache里的兜底邏輯就失效了——它本來想用getEntityClass()的結(jié)果去覆蓋 lambda 自帶的類型結(jié)果兩邊都沒著落。所以務(wù)必用InjectMocks??尤o態(tài)工具類Db在單測里幾乎沒法用。com.baomidou.mybatisplus.extension.toolkit.Db提供了一批靜態(tài)查詢方法內(nèi)部通過SqlHelper拿SqlSessionFactory這個 Factory 在純單測里是 null直接 NPE。如果你們的代碼里用了Db.lambdaQuery(...)要么把這段代碼抽到一個可注入的組件里要么這類方法只能用MybatisPlusTest覆蓋??铀腖ombok 的Accessors(chain true)會改變 getter 的形態(tài)嗎不會。Accessors(chain true)只影響 setter 的返回值類型getter 還是標(biāo)準(zhǔn)的getXxxPropertyNamer.methodToProperty能正常解析。但如果你用了Accessors(fluent true)getter 會變成userName()這種沒有g(shù)et前綴的形式methodToProperty的解析規(guī)則就不適用了lambda 查詢會得到錯誤的屬性名。這個坑很隱蔽因?yàn)榫幾g期不報錯只有跑到 SQL 渲染時才出問題??游灏?SQL 打印在日志里本身就可能觸發(fā)這個異常。我見過一個切面在方法出口統(tǒng)一打印wrapper.getTargetSql()做審計日志。這個切面對所有方法生效包括單測。于是本來好好的單測因?yàn)檫@個切面被MockBean進(jìn)來或者切面用Component被掃到全部炸掉。排查這種問題的方法是把堆棧里getTargetSql上方那幾幀看清楚是不是來自切面而不是測試代碼。5.3 怎么在 CI 上防止這個問題回歸方案一上線之后最大的顧慮是以后新加的實(shí)體忘了注冊。我的處理是加兩個保險。第一個保險是前面說的自檢在測試基類的static塊里掃一遍實(shí)體包凡是帶TableName但沒有TableInfo的直接拋異常并打印類名CI 會在第一個用例就紅比等到某個具體斷言失敗要快得多。第二個保險是把基類做成強(qiáng)制的。在項(xiàng)目里加一條 ArchUnit 或者自定義的檢查規(guī)則*Test結(jié)尾的類必須繼承BaseMockTest或者在類上標(biāo)注一個自定義注解。這個規(guī)則可以用 ArchUnit 寫ArchTest static final ArchRule service_tests_should_extend_base classes() .that().haveSimpleNameEndingWith(ServiceImplTest) .should().beAssignableTo(BaseMockTest.class);這條規(guī)則的好處是把約定變成了編譯期/測試期的強(qiáng)制約束新人入職不用讀文檔也知道該怎么做。ArchUnit 的執(zhí)行開銷很小一般幾秒鐘塞進(jìn)單元測試階段完全沒問題。另外還有一個習(xí)慣值得養(yǎng)成測試?yán)飻嘌?SQL 的時候優(yōu)先斷言結(jié)構(gòu)而不是完整字符串。assertThat(sql).contains(ORDER BY id DESC)比assertThat(sql).isEqualTo(SELECT ... FROM t_user WHERE ... ORDER BY id DESC)要穩(wěn)得多。后者在 MyBatisPlus 升級一小版本、列名排序變了、或者多了一個邏輯刪除條件之后就會紅維護(hù)成本極高。我見過一個項(xiàng)目因?yàn)閿嘌粤送暾?SQL升級 3.5.3 到 3.5.6 時改了四十多個測試類純粹是無效勞動。最后再說一個容易被忽略的工程習(xí)慣把TableInfoHelper.initTableInfo的調(diào)用集中在一個類里而不是散落在各個測試類中。集中之后將來 MyBatisPlus 大版本升級導(dǎo)致 API 變了你只需要改一個文件。我上一家公司從 3.4.x 升到 3.5.x 的時候就是靠這一點(diǎn)把遷移成本壓到了半個小時以內(nèi)——三十多個測試類一行沒動。我在實(shí)際做這套東西的時候最大的體會是單元測試?yán)镉龅降哪切┬W(xué)報錯十有八九不是框架的問題而是框架依賴的某個只在啟動時做一次的準(zhǔn)備工作被測試環(huán)境跳過了。找到那個準(zhǔn)備工作把它顯式地補(bǔ)回去問題就解決了。lambda 緩存是這樣DataSource初始化是這樣LocalDateTime的序列化器也是這樣。所以我現(xiàn)在的習(xí)慣是每接一個新的三方框架先去翻它的AutoConfiguration類看看啟動階段到底做了哪些副作用這些副作用就是寫單測時必須手動補(bǔ)齊的清單。
返回列表
PREV
查看更多資訊
NEXT
返回資訊列表
如何安全看伊人婷婷| 婷婷五月综合婷婷| 色噜噜狠狠色综合日日| 激情婷婷五月| 97婷婷五月天| 亚洲欧洲自拍图片专区五月天| 久久婷婷午夜| 国产毛片欧美毛片久久久| 热99AV网站| 久久久婷| 五月丁香91| 丁香婷婷天堂| 国产偷人爽久久久久久老妇APP | 9热久久| 激情五月天小说网| 色五月婷婷久久| 婷婷色正月| 深情六月婷婷综合久久| 五月激情啪啪| www.久久久久久久| 国产精品成人AV在线| 亚洲中字AV电影在线网站| 天天干天天拍| 五月天激情综合| 丁香婷婷伊人| 99热12| 久久精品国产色| 9久9久| 丁香五月之久操视频| 日本激情91| 五月色婷婷激情| 久久99热这里只有精品| 怡红院视频| 丁香六月婷婷五月婷婷| 激情第四色| 婷婷五月18永久免费视频| 亚洲综人色综网| 亚洲婷婷基地| 国产 亚洲 在线| 国产亚洲色婷婷久久99精品91 www.riverspirits.org www.hnnun.com www.changh | 九九色情网站| 欧美久久九九| 另类国产区| 欧美精品狠狠色丁香婷婷| 大香蕉五月天| 97碰 在线视频观看| 精品一二三区久久AAA片| 丁香五月香蕉| 成人精品一区日本无码网| 日韩成人综合网| 2017人人操| 人人爱人人草| 亚洲热视频| 99视频内射三四| 毛片新网地| 丁香 亚洲 久久| 人妻内射视频| 夜夜夜夜夜操| 婷婷五月天av小说| 丁香久久综合| 日本激情ⅩXX免费视频| 婷婷五月天首页| 丁香六月毛片| 婷婷成人五月天成人文学| 激情综合网五月天| 色五月在线播放| 在线99热| 91精品啪| 日本在线视频手机播放五月婷| 亚洲avjiujiur91| 青草激情综合| 婷婷丁香成人色综合| 91黄色五月天视频| 丁香五月婷婷色情综合| 99热这里只有精品23| 激情美女五月天激情在线| 甈你aaaaa| 丁香六月激情网C0W| 亚洲激情婷婷| 天堂成人A片永久免费网站 | 97干视频在线| 婷婷激情五月综合基地| 成人短视频在线免费观看| 五月婷视频在线| 久久丁香五月天| 亚洲色五月| 大香伊人久色| 99热这里只有精品268| 婷婷开心青青草| 午夜色丁香| 五月丁香久久久久| 操逼电影免费看| 亚洲综合一区二区| 亚洲国产精品综合色区| 亭亭五月丁香五月天激情| 大伊香蕉精品视频在线| 丁香六月婷婷开心| 婷婷激情视频欧美视频自拍视频欧美剧 | 久久久久9久无码视频| 久久久jd| 五月婷婷在线播放| 中文字幕无码人妻少妇免费视频| 国产精品操| 五月婷婷色综图片| 五月天伊人网| 丁香狠狠色婷婷| site:picc-up.com| 亚洲男女激情| 操逼毛片国语对白| 婷婷狠狠18禁久久| 五月丁香狠狠爱婷婷综合| 久久婷婷国产| 丁香五月天狠狠| 精品国产乱码久久久久夜深人妻| 99九色视频在线观看| 第六色在线| 色婷婷a v| 色9999综合久久| 五月丁香美女视频| 亚洲综合色色色| 亚洲欧美成人在线观看| 天天干天天干天天干天天干天天干天天 | 久久五月天视频| 610018岁成人视频| 婷婷六月丁香1| 97色天堂| 棕合影院色色| 激情综合五月天| 国产操逼网站| 五月开心久久| 久久综合中文字幕| 人妻久久婷婷| www,色婷婷| 99在这里有精品| 亚洲妇女熟BBW| 国产熟女大叫受不了| 人人视频色| 任你艹| 色婷婷久久综合| 能看的av| 99自拍视频网站| 婷婷金品综合视频| 色婷婷情片| Www,五月天| 欧美成人猛片AAAAAAA| 色欲天天综合网| 五月亭亭狠狠| 婷婷五月丁香啪啪| 九九热精品| 色亚洲无码| 亚洲人人操| 九九综合五月欧美| 99热这里都是精品| 99热99网| 一级片操逼视频| 五月情婷婷五月| 五月丁香综合激情在线观看| 五月激情影院| 国际国外精品欧洲南美洲专区无码不卡| 五月丁香婷婷欧美| 国产色五月婷婷| 极品精品一区二区三区在线| 五月天另类小说| 99色嘟嘟精品网站| 五月婷婷九九热| 99久精品视频| www久久久| 夜夜夜夜操| 成人网在线观看视频| 婷婷五月丁香色情| 狠狠色大香蕉| 三男玩一女三A片| 亚洲精| 91久久国产自产拍夜夜91久久精品文字>91麻豆精品国产 | 91无码高清| 超碰人人操人人干| 青青草护士中出内射-欧美电影在线天堂新版| 亚洲丁香花色| 拳交大逼| 亚洲一个色| 日日日日日| 九九这里精品| 欧美在线97| 综合激情视频| 五月婷婷第四色| 99视频九九热| 91夫妻视频| 欧美色色色色色| 99九九玖玖| 婷婷网五月天| 精品乱码久久久久| 蜘蛛女免费观看完整版高清电影| 综合精品99| 亚洲综合网 665566| sS丁香五月婷婷| 123日本不卡在线| 五月婷婷啪啪综合网| 丁香婷色| 67194线路二在线观看| 免费观看的av| 性天天中文网| 天天操夜夜夜夜爽| 91九色熟女| 国自产拍偷拍精品啪啪一区二区| 99热性色| 国产av天天插天天操天天爽| 色色亚洲视频| WWW.99视频| 婷婷丁香高潮了| 婷婷五月色花丁香社区| 亚洲精品久久久久久久久久飞鱼| 国产熟女一区二区三区五月婷| 美女要搞搞天天搞搞搞网站| 99九九在线| 丁香五月激情图片婷婷| 欧美精品999| 国产精品香蕉| 99热这里有精品2| 97色婷| 久久精品日| 91chinese在线| 五月天婷婷激情| 激情小说五月天中文字幕| 色色狼人综合| 97色97干| 99热精品99| 五月丁香亭亭电影久久| 99精品成人无码A片观看金桔| 久久久久人妻精品| 五月天婷婷乱论小说| 五月丁香久久综合精品| 色五月无码| 五月天婷婷基地综合网| 丁香五月停停基地| 99热国产婷婷| 女人天堂 AV| 久久aaa| 色丁香五月| 成人五月天。COM| 九九热精品| 日本久热| 五月丁香大香蕉| 天天影院色| 丁香五月23111| 婷婷五月天少妇| 思思热国产视频| 婷婷丁香激情综合色情| 久久99久久99精品免观看粉嫩| 色综合女人99| 综合婷| 一本到不卡高清DVD| 99热精品在线观看| 一本大道伊人AV久久综合| 人人干人人看| 亚洲妇女熟BBW| 另类视频在线| 婷婷大香焦| 国产肥白大熟妇BBBB视频| 欧美色综合天天久久综合精品| 色99在线视频| 少妇高潮A片无套内谢麻豆传| 99久久极情精品一区| 五月婷婷与六月丁香图片激情| 人人色婷婷| 五月色影院| 婷婷情爱五月天6| 五月丁香六月情| 婷婷五月天综合久久| 五月天综合图片| 夜夜爽天操| 激情丁香六月| 五月丁香六月激情欧美综合| 色婷婷五月天| 国产在线视频1234| 香蕉久久国产AV一区二区| 丁香五月,开心五月,成人婷婷| 97干在线视频| 丁香婷婷伊人| 久久婷婷91| 淫视馆aV二区一区| 色香蕉婷婷| 在线亚洲综合| 五月丁香色色网| 久去色色| 六月丁AV| 五月婷婷六月色| 农村熟妇高潮精品A片| 欧美69久成人做爰视频| 狠狠干在线| www.99热在线观看| 手机AVAV天堂看网| 色色吧综合| 婷色五月天| 噢美99| 伊人婷婷五月天av| 久久亚洲网| 久草xx性爱视频| AA久久| 婷婷导航| 极品另类| 婷婷五月综合激情小说| 日本在线视频播放91| 高清无码视频网址| 婷婷综合av| 99色在线观看| 思思热精品在线| 99色| 婷婷丁香激情综合色情| 五月婷婷九月婷婷九月婷婷| 日韩中文欧美| 91久久色| 色婷婷九月| 久久超级碰碰| 夜夜AVV| 五月丁香爱婷婷深深| 色综合网综合| 伊人激情| 激情久久久| 九九精品视频在线观看| 色婷婷成人做爰A片免费看网站| 大香蕉伊然在亚洲90| 色噜噜狠狠色综合日日| 99视频在线精品| 中文字幕乱码亚洲精品一区| 五月婷婷综合激情网| 丁香五月婷婷激情中文| 亚洲精品又粗又大又爽A片| 九热久| 99精品在线下载| 久99久视频| 九九综合五月欧美| 激情五月少妇| 精品自拍99| 国产VA播放| 女人天堂AV| 成人在线观看精品| www.操逼comm| 丁香网五月天| 激情丰满熟妇五月| AA片在线观看视频在线播放| 综合丁香婷婷五月天| 狠狠干,狠狠操| 久久五月婷婷综合网| 色五月婷婷天天干| 99这里只有精品| 久久久久亚洲AV成人无码电影| 久热网在线视频| 午夜不卡久久精品无码免费| 五月婷色丁香| 亚洲网在线观看| 99这里只有精品国产| 99re8这里只有精品99re8热视频| 色综合激情| 99精品一二三四视频| 九九美女视频| www.婷婷.com| 丁香色色色| 欧美婷婷丁香五月社区| 色性综合| 成人做爰A片免费看视频| 伊九九三级区| 99ER热精品视频| 伍月婷丁香婷| 亚洲 在线 另类| 五月婷婷六月丁香激情深爱| 五月婷婷丁香| 五月婷导航| 在线看AV| 在线观看婷婷5月| 婷婷色偷拍| 婷五月天| 色婷婷五月影视| 日韩人妻无码专区| 99久久久国产精品免费蜜乳tv| 男人的天堂婷婷色五月| 99热在线看| 99久久精彩视频| 亚洲情色一区| 亚洲色激婷| 99久久精品免费精品国产_国产精品久久久久久_国产在线|日韩_久久国产精品电影 | 这里有精品| 深爱五月天婷综合| 五月婷婷丁香大香蕉| 五月婷婷成人| 婷婷激情伍月网| 欧美S码亚洲码精品M码| 色香欲综合| 六月丁香综合| 国产婷婷色综合AV蜜臀AV | 五月天色婷婷图片| www.色九月| 五月丁香色| 伊人干练久| 日本nghangse中文字幕| 91九色精品熟女内射| 99热精品10| 欧美在线ee日韩| 欧美色色色色色色| 六月婷婷狠狠色在线观看| 97色射| 色婷婷亚洲婷婷在线观看| 99在线热| 色亭亭丁香五月天| 可以免费观看的AV| 久久免片| 精品一区二区三区四区五区六区介绍 | 色色吧综合| 婷婷五月色影视先锋| 久久99网| 91久久99久久91熟女精品| 色五月开心开心五月激情五月| 超热久碰.com| 国产做A爰片毛片A片美国| 五月草视频| 99热精品在线播放| www.99久久久久99| 国产人妻777人伦精品HD| 色播播五月| 99rewww| 91精品国产综合久久蜜芽解析速度| 91色综合| 夜夜夜夜夜操| 亚洲 无码 中文字幕 中出| 99噜噜噜| 大香蕉伊然在亚洲90| 操一操干一干| 色婷婷六月天| 久久538| 五月天小说激情| 超碰免费人妻| 在线不卡中文字幕| 99久久婷婷五月| 激情深爱五月天| 综合图片色色| 婷婷五月亚洲一本在线丁香| 人人干AV| www,欧美干干干干干干| 六月丁香婷婷色狠狠久久| 99热精品网| 亚洲国产精品成人午夜| 日日干日日| 综合久久97| 99热这| 激情二色月| 狠狠香婷婷五月| 欧美日韩国产伦精品日韩人妻一| 午夜激情四射影院| 极品精品一区二区三区在线| 99国产精品白浆在线观看免费| 婷婷综合五月天| 思思热精品在线| 日日做夜夜爱| 天天天天天天天操| 日日做夜夜爱| 亚洲色无码| 婷婷玉月丁香五月在线视频| 狠狠色性| 丁香激情六月天婷婷| 色丁香五月婷婷婷| 麻豆科斗777| 91人妻色色网| 天天色天天爽| 婷婷伊人激情婷婷| 热99这里只有精品视频| 婷婷香五月综合激情| 狠狠色婷婷丁香六月| 五月天色狠狠| 玖操97| A A色色| 国产精品久久久久久五月天加勒比 | 色婷婷亚洲精品天天综| 桃色五月天| 美欧成人视频| 岛国AV网| 4438亚洲欧美| 青996青| 丁香久久综合| 九九热视频首页/这里只有精品| wwW天天干| 伊人日日干| 色五月天在线| 色停停五月,在线观看| 色五月婷婷很很操| 丁香九月激情| 这里只有精品96| 五月天小说激情| 99日在线观看视频| 婷婷五月天成人五月天| 色综合网综合| 啪啪激情综合| se色婷婷视频| 美女黄频aⅴ视频| 婷婷五月天堂网| 色狠狠综合网| 五月婷婷综合网| 9色在线| 亚洲亚洲人成综合网络| Aα在线免费观看| 欧美色九| 五月天激情久色| 99热精品无码| 亚洲色色色| 五月婷婷丁香五月| 激情五月丁香婷婷夜夜操| 欧美五月丁香在线观看| 另类激情五月在线视频欧美| 久久婷五月| 亚洲啪啪自拍| 激情深愛五月視頻| 色久丁香五| 天天色播| 日韩在线观看亚洲| 深爱激情网噜噜色| 九九视频这里只有精品| 99热九九九九| 五月黄色婷婷| 99色综合网| 五月婷婷开心六月激情小说| 久久资源网五月婷| 狠狠色综合久久久久| 久久婷婷激情四射五月天| 婷婷97| 男女久久婷婷五月天| 亚洲中文乱字字幕在线永久| 香蕉AV福利精品导航| 超碰免费观看| 另类亚洲视频| 草逼大片| 五月丁香啪啪网| 色播五月天婷婷老师| 激情五月第四色| 九九在线这里只有精品视频| 99热这里只有精品3| 九月激情婷婷丁香| 六月丁香婷婷拍拍| 色婷婷免费视频| www久久久久久久久久久| 色婷婷影| 九九热这里只有精品31| 五月丁香啪啪| 五月天天综合| 日本五月天婷婷丁香| 伊人五月天| 久久丁香九| 丁香激激情网| 国产精品电影| 激情综合亚洲| 97九色| 91久草五月天婷婷| 久久精品视频在这里有| 610018岁成人视频| 婷婷免费精品视频| 色五月av| 99色色网| 婷婷色丁香五月| 久热re在线视频| 日韩六六久久电影| 色人久久| 九九操操| 九九热av| 九九热这里只有精品5| 草综合14| 人妻六月天| 丁香五月综合在线观看| 婷婷丁香日韩五月| 亚洲色婷婷99一9|| 日韩性爱AV| 色久在| 五月婷婷中文字幕| 色视频2025| 色婷婷综合网站| 久热91| 97狠狠色| 九月丁香| 亚洲精品V天堂中文字幕| 99视频自拍| 超碰人人操人人干| 99免费成人网| 天天天天操| 襙比视频| 九月婷婷在线视频| 日屌日日操日日色| 丁香婷婷色九月| 播五月开心婷婷欧美综合| 亚洲无码AV片| 色5月婷婷| 婷婷久草| 六月五月天婷婷涩播在线| 5月婷婷性视频| 少妇大叫太大太粗太爽了A片| 久热这里有精品视频| 国产又黄又爽又色的免费| 丁香五月香蕉在线| 99久久精品免费精品国产_国产精品久久久久久_国产在线|日韩_久久国产精品电影 | 欧美一线视频| 色婷婷综合视频| 欧美日韩91| 99欧美| 99综合婷婷五月| 在线看的免费网站| 激情五月丁香六月综合AVXXXX| 亚洲成人AV一区在线观看| 婷婷色综合| 婷婷爱爱蜜臀天天操| 天天操夜夜肏| 色碰碰视频| 91超级碰| 亚洲最大激情无码| 色优久久| 亚洲天堂婷婷| 337p大胆噜噜噜噜噜91Av| 热的五码久久精品| 日韩精品一区二区刘| 日本女人久久| 色国产五月| bukadeavzaixian| 黄网在线免费观看| 婷婷五月天狠狠| www.99热视频| 激情婷婷丁香| 激情五月婷婷五月| 五月丁香花开综合网| 4399亚洲视频| 丁香伍月婷电影全集| www夜夜操| 色99视| 99在线爽| 97av在线视频| 人人97碰| 久热最新视频 | 99精品网| 97色色色色色| 91啪级电影| 欧美色色色色色色| 免费无码毛片一区二区A片| 欧日韩成人| 六月丁香五月亭亭| 激情综合五月丁香| 成人VAV视频在线观看| 激情丁香五月婷婷| 欧洲亚洲免费视频9| 99热九九在线| 亚洲av成人在线| 日本色99网站| 婷婷爱五月| 色婷视频| 99热成人精品网站| 日韩成人五月天| 色999五月色| 九九美女视频| 99艹精品在线观看| 成人无码精品1区2区3区免费看 | 爱射综合| 色狠狠综合| 91色在线 | 日韩| 激情五月天网页| 五月婷婷综合色啪| 国产欧美日韩性爱| 五月天激情小说| 丁香六月婷婷开心婷婷网| 亚洲视频在线网站| 中文字幕欧美日韩VA免费视频| 丁香五月六月综合激情| av在线免费网站| 天天日天天舔| 婷婷激情图片| 久久这里都是精品| 久久性爱视频这里只有精品| 天天综合网亚洲综合网| 天天摸天天爽| 99视频在线观看视频| 色综合网上班开心婷婷久久| 超碰日日操| 久久天堂网| 婷婷五月天AV在| 人人草碰| 日韩一级网站| 色婷婷91| 日本色婷婷| 成人在线不卡| 亚洲精品网站色视频| 婷婷色丁香五月| 熟女国产在线一区二区三区四区| 激情淫乱男女| 久久九九囯产| 青青草色在线视频观看| 99ri久久| 99这里精品| 99热这里只有精品21| 丁香六月婷婷激情综合| www.狠狠干| 激情 婷婷 丁香五月天| 五月婷婷丁香瑟瑟视频| www.minyis.com【JT】实力收量可预付QQ2101460746 | 99五月丁香丁| 操操操av| 婷婷五月综合啪| 玖玖婷婷五月天| 人人色婷婷| 久久久婷| 2018国产大陆天天弄| 久久看九九90| 五月天丁香欧美激情| 色噜久| 狠狠色丁香| 热99精品视频观看| 色婷婷丁香五月天在线观看| 色玖玖综合| 婷婷久月| 成人做爰A片免费看网站找不到了| 国产欧美精品AAAAAA片| 亚洲AVwwwwwww| 婷婷五月综激情| renrencaoni| 久噜久噜| 亚洲色情网站| 天天干天天做| 丁香五月玖玖| 全亚洲最大的婷婷五月天网站COM| 五月婷婷 欧美| 色色色色色色色色网站| 激情婷婷丁香五月天| A片试看50分钟做受视频| 亚洲人成播放网站| 91美女被操| 狠狠色噜噜狠狠狠888了| 国产激情综合五月久久| 五月天激情网图片| WWW.久久久久久久久久久久久| 99热九九这里只有精品| 99久久精品视频女神1| 久久五月天网| 五月婷婷影视| 精品婷婷| 婷婷六月综合激情| 五月丁香六月| 97精品欧美91久久久久久久| 99在线看片| 五月婷综合| 婷婷伊人综合| 99精品在线观看视频| 区区欧美你爱| 91精品综合久久久久久五月丁香| 亚洲第二AV| 精品一区二区三区四区五区六区介绍| 成人电影在线免费试看| 久久婷婷五月综合色丁香花| 桃色Av色哟哟| 亚洲色五月天| 精品9197碰| 这里都是精品99| 丁香婷婷情色五月天| 天堂久久久久天堂网| 99日韩| 天天操综合网| 人妻视频一区而且二区| 色的色综合| 欧美六月| www免费在线视频| 六月激情婷婷色| www.maotanji.com| 青青日韩| 亚洲欧美另类在线23p| 国产欧美日韩综合精品一区二区| 六月丁香啪啪啪| 免费成人中文字幕| 新五月天婷婷激情电影| 99爱在线视频| 狠狠爱综合| 六月 丁香 视频| 亚洲9久久精品| 五月天天天开心激情网| 九九在线视频| 日本色噜| 六月丁香婷| 人妻AV在线观看| 久热 91| av在线免费网站| 殴美综合激情五月天免费视频| 久久九网| 99热官网精品在线| 婷婷综合天堂| 99热这里只有精品亚洲| 成人做爰A片免费看网站找不到了| 日韩成人无码人妻| 91热在线| 成人在线精品| 日本va欧美va国产激情| 激情图片亚洲| 精品无码久久久久久久久| WWW.天天日| 综合网色| 久色姿源| 久久密臀婷婷| 五月天婷婷在线AN| 密臀av无码人妻精品| 婷婷色正月| 99re青青草| 国产操碰| 成人天天爽| 在线观看免费观看在线9久| 婷婷六月激情啪啪| 亚洲黄色操逼| 色五月丁香婷婷在线观看| 色九九七七| 婷婷五月天奸女| 在线国产精品色| 99精品网| 五月激情天天干| 五月天婷婷激情网| 开心激情网在线| 亚洲综合视频天天精品| WWW嗯嗯啊啊啊啊| 性99网站| 亚洲精品字幕| 99热这里只有精品66| 伊人九九综合| 天天日天天爽| 色五月婷婷久久大| 色婷婷超碰| 天天操天天干天天日| 这里只有国产精品在线| 色色热日| www婷婷| 丁香五月激情澎湃一区| 狠狠色丁香久久婷婷综合五月| 性色婷婷| 天天 青草 丝袜制服 在线| 国产精产国品一二三在观看| 99在线亚洲| 久久婷婷色色| 超碰在线免费观看日韩| 五月天丁香成人| 2050人人操免费工开爱| 国产黄色大片| 综合激情站| 激情五月综合网最新 | 五月天综合久久| 天天干天天干天天干天天干天天| 久久999久久999久久999久久| 日本久久99| 丁香五月中文字幕久色| 五月丁香婷婷综合视频| 久久色六月| 日本色天堂| 五月丁香婷婷三级| 亚洲成人在线播放| 亚洲国产网站| 开心激情站| 婷婷六月啪啪| 五月停停丁香| 日韩无码一区二区三区四区| 丁香五月很很肏| 国产午夜精品一区二区三区嫩草| 久久久精品人妻| 五月天婷婷久久综合| 91精品婷婷国产综合| av五月天婷婷丁香| 天天操婷婷| 九九精品综合| 亚洲综合激情五月| 色五月婷婷丁香婷婷| 91干在线| 久激情| 9热网站| 五月激情天| 99熟女视频| 激情网五月天| 99综合免费视频| 99这里只有精品在线| 久久久激情视频| 99九九在线视频| 久久婷婷免费| 色爆五月| 综合激情婷婷| 五月天啪啪啪| 欧美天堂久久| 五月婷婷激情中文字幕| 九九机热| 天天爽天天草| 亚洲精品小视频| 色综合久久伊伊婷婷五月| 成人 在线 日韩| 殴美日比视频| 密乳Va| 91人人澡人人爽人人看| yazhoujiqingav| 玩熟女五十AV一二三区| 丁香婷婷六月| 激情五月天开心| 思思99精品视频在线观看| 天天做综合| 电影爱拉战争免费观看| 操人无码| 色五月婷婷天天干| 五月丁香六月综合情在线观看 | 伊人影院久久网| 亚洲精品第一国产综合亚AV | 久久久久久婷| 中文字幕在线人妻| av人人操| 亚洲色婷婷五月天| 久热久操久热久草国产91| bukadeavzaixian| 激情婷婷视频在线| av无码电影| 久久综合色五月| 五月天丁香网站| www.henhenl| 精品久色| 婷婷五六月丁香| 天天爽综合| 好激情在线综合网| 欧美成人va| 日本激情91| 天天草天天爽| 97五月婷| 久久综合综合综合| 五月婷激情影院| 99九九在线| 午夜 外网 精品 在线| 激情综合网五月天天| 五月婷综合网| 天天五月天综合网址| www.婷婷.com| 97 A I色色| 日本人人超碰| 青青操成人福利| 六月丁香啪啪啪| 丁香五月亚洲激情婷婷射| 五月丁香999| 久久这里只有精品07 | 婷婷欧美综合| 激情99。| 天天插天天操| 99视频在线精品免费观看2| 第九色区av天堂| 人人操9| 99在线热| 丁香开心深爱| 在线观看婷婷5月| 综合色五月| 9l久久久视频| 亚洲免费99| 很很干在线视频| 嫩草免费视频| 91精品国产91久久久久青草| 色 五月婷婷基地| 亚洲天堂aaaa| 亚洲综合激情五月久久| 人妻有码乱操| 婷婷激情五月天综合| 婷婷激情5月| 久久婷婷成人综合色怡春院| 91av传媒高清在线视频网| 色欲影香| 五月深爱婷婷| 色婷婷AAA| 久久久五月天婷婷成人网| -91九色大屁股| 五月播播| 婷婷综合网站| 色情五月婷| 爱草视频在线| 亚洲第一第二网站| 69人人操人人爽| 五月激情综合婷婷| AV在线大香蕉| 99久99久| 色婷婷激情小说网| 久久草中文日韩欧美| 婷婷伊人综合| 噜一噜免费视频| 亚洲在线操| 亚洲岛国电影| 人妻videos人妻高清| 亚洲AV免费在线| 无码一区二区三区四区五区91c| 国产VA播放| 婷婷五月天AV在线| 久久久久九九九九视屏小说88| 婷婷五月丁香基地在线视频官网| 五月天婷婷成人网| 成人五月天综合网| 国产欧美婷婷五月| 久久久精品人妻| 开心五月婷婷综合在线精品素人| 99色1| 亚洲色热| 在线99热| 国产精品色婷婷久久久精品| 日本天堂网站99| 亚州精品久久久久AV无码| 激情深爱五月天| 91欧美| 婷婷五月天情色| 亚洲第二AV| av在线色五月丁香婷区久| 欧美私人家庭影院| xxxx五月激情| 久久综合爱| 五月激情综合网| 这里只有精品无码| 成年视频免费观看| 五月开心深深爱激情综合 | 色婷婷狠狠| 丁香婷婷精品视频| 五月丁香六月婷综合成人综合| 色婷婷综合综合网| 婷婷五月久久| 人碰人人人玩91| 99热只有精| 婷婷五月激情欧美大胆视频| 成人欧美Va| 国产精品人成A片一区二区| 天天上天天爽| 思思热在线观看| 狠狠爱综合网| 久久激情网| 亚洲艹网| 极品人妻VIDEOSSS人妻| 亚州激情在线视频| 日本强伦片中文字幕免费看| 99爱最新免费视频在线观看| 欧美在线| 日韩乱玛久久| 久久9情免费| 高清无码视频网址| 99色热综合| 天天婷婷| 日韩高清成人| 色婷婷久久久| 天天日日人| 天天弄天天操| 丁香六月天之亚州热女 | 五月婷婷六月奇米网丁香| 色亭亭丁香五月天| 色情五月婷婷| 色欲丁香| 99色爱| 色噜噜婷婷| 婷婷爱爱蜜臀天天操| 伊人干综合| 五月婷婷香蕉| 久久丁香九| 91操在线视频| 日本狠狠爽| 亚洲久久天堂| 美女激情综合| 夜夜天天久久婷婷| 五月丁香婷婷伊人日韩| 91热网址| 26uuu国自产精品| 久9热视频在线| 97热精品| 99re这里只有精品9| 无码视频国内精品久久久| 天天综合影院| 九九久久精品| 99福利视频导航| 五月丁香激情综合啪啪| 激情五月综合视频| 久久九网| 丁香五月天堂| 激情婷婷丁香色五月| www.com任你艹| 亚洲精品99| 丁香五月婷婷在线观看| 激情五月综合| 免费观看日韩成人av| 99热在线精品观看| www.五月婷婷久久.com| 婷婷丁香五月社区亚洲| 五月丁香六月婷综合成人综合| 婷婷五月丁香婷婷| 光棍影院日韩精品| 99热国品| 亚洲综合色色色| 超碰人妻在线| 免费观看2018www黄色操逼网站| 国产一级片| www.99情趣网| 久久婷婷五月综合色播| 色五月大| 99久久综合狠狠综合久久| 五月天五月天激情网| 开心深爱五月天| 婷婷少妇激情| 激情小说色五月| 九九热视频这里只有精品| 五月叮香啪| 九九热视频网站| 丁香婷婷六月天| 99色综合久久| 欧美黑人巨大性生话| 九色视频91疯狂| 99视频热| 丁香五月天欧美| 激情综合婷婷五月| 久人操| 国产精品 的国产| 丁香五月天堂| 97色天堂| 久色视频| 激情婷婷丁香色五月| 成人羞羞啪啪 全 视频| 午夜天堂啪啪| 欧美啪啪五月天| 五月丁香另类网| 国产一区二区av免费| 亚洲免费看片| 久综合| 欧美大道不卡| 九九九九这里只有精品| 欧洲综合视频在线观看。欧洲,亚洲综合食品在线观看。 | 大香蕉九九| 激情AV在线| 五月婷婷真爱激情网| 成 人 色 色| 1995年关宝慧版蜘蛛女| 五月丁香婷婷无码中文| 亚洲综合色婷婷| 丁香五月激情啪啪综合| 综合久久五| 色色色激情网| 久久久99精品免费观看| 国产精品18久久久| 香蕉久久国产AV一区二区| 欧美人妻一区二区| 午夜九九九九九九九九九九九九九| 播五月婷婷开心| 激情五月综合网| 92国产福利| 欧美在线看| 久操乱| 婷婷色五月91啪啪| 狼人伊人天堂| 四川BBB搡BBB爽爽视频| 9久热精品在线视频| 精品少妇人妻AV无码专区偷人 | 日日夜夜狠狠| 激情六月天| 五月丁香六月激情综合| 99色视频| 五月激情丁香啪啪| 国产AV一区二区三区最新精品| 性一交一乱一交A片久| 丁香五月天色婷婷| 五月色网| 亚洲亚洲人成综合网络| 国产在这里只有精品| 成人片在线播放| 日日鲁鲁鲁夜夜爽爽狠狠视频97| 拍真实国产伦偷精品| 亚洲精品白浆高清久久久久久| 激情综合色五月丁香| 99精品丁香五月| 色色网91| 99熟女视频| 九九色精品| 国产精品五月丁香| 99爱在线视频观看| 日韩色色视频| 六月丁香婷婷综合狠狠爱夜夜爱| 六月婷婷综合激情| 亚洲无码色色| 伊人玖玖精品| 色五月大香蕉| 欧美性猛交 XXXX 乱大交| 超碰操日| 96精品久久久久久久久| 超碰com| 亚洲色婷婷| 九九精品视频在线6| 97se视频在线| 欧洲亚洲精品| 五月天激情美女久久| 久久婷中文字幕| 午夜一区| 五月丁香六月婷婷久久久综合| 停婷丁五月在线| 久久久久er热| 日本三级99人妇网站| 91九色 婷婷| 五月激情四射婷婷丁香| AV五月婷婷露脸| 九九视频这里只有精品| 夜精品无码A片一区二区蜜桃| 六月丁香啪| 熟女色专区| 99热这里只有精品 搜| 五月婷婷三级| 99亚洲精品综合在线| 六月婷婷七月丁香| 538久久| 婷婷九月亚洲| 五月天丁香久久综合| 99热欧美偷拍| 《》【无码】想被搞到爽AV应募而来的超M素人 西纯子 10musume-011723-01 | 婷婷激情五月综合| 五月丁香六月婷婷综合网| 五月天综合网| 97人妻超级碰碰碰碰碰| 99热第一页| 天天综合图片| 婷婷激情综合网| 五月婷婷香蕉| 久久天天天| 熟女激情五月天| 色五月超碰| 黄色高清无码| 99热这里只有精品亚洲| 日韩三级高清无码| 深爱五月婷婷| www.五月婷婷久久.com| 色色综合色| av大香蕉| 99久久9| 色婷婷综合影院| 亚洲情综合五月天| 狠狠久久婷婷| 六月丁香五月激情网| 人妻啪啪啪| 91精品婷婷国产综合| 6080av| 99精品视频在线观看| 久久人人超| 99久久综合精品五月天| 99这里有精品视频| 99ri在线观看视频| 婷婷五月天视频免费在线观看| 国产精品人成A片一区二区| 97色婷婷成人综合在线观看| 激情五月天影院| 草了bav视频在线观看| 亚洲国产精品五月天| 伊人久久大香网| 久99久热| 六月婷婷五月天| 婷婷五月天丁香久久| 五月天堂在线| 亚洲日韩一页精品发布| 成人噜噜网| 五月婷婷在线视频| 丁香五月婷婷色| 五月天激情啪啪| 欧美精品A片一区在线观看| 狠色色狠网| 色色色色欧美| 精品综合五月| 变态 另类 在线| www激情网| 婷色五月天| xx综合网| 99热国品| 97极品在线| 丁香五月婷婷啪| 亚洲午夜av| 79精品视频在线观看,| 国产精品成人AV在线| 综合久久久| 激情九月综合| 婷婷六月丁香五月| 久久婷五月天| 婷婷五月天小说| 热热久久久久久久久|