前序
代碼劍宗等級分明,其門下弟子等級劃分如下:
-
入門弟子
剛剛拜入代碼劍宗,學習基礎編程語言和基本劍法(語法和基礎概念)。他們的代碼還顯得生澀,但已經開始展現出對優雅代碼的追求。 -
江湖小蝦
初步掌握了幾種編程語言,能夠寫出基本的算法劍法(簡單算法)。他們的代碼開始變得簡潔高效,但還需要更多的實戰經驗。 -
江湖俠客
在代碼劍宗中已經小有名氣,能夠獨立完成復雜的項目。他們的算法劍法(算法優化)和架構心法(系統設計)已經初見成效,代碼如行云流水,功能完備。 -
武林高手
技術爐火純青,精通多種編程語言和框架,他們的代碼不僅高效,而且極具美感。無論是算法劍法還是架構心法,他們都能運用自如,解決各種復雜的技術難題。 -
武林宗師
在代碼劍宗中享有極高的聲望,擅長傳授弟子,指導團隊。他們在算法優化和系統架構設計方面有獨到的見解,能夠引領技術方向,推動團隊進步。 -
武林至尊
代碼劍宗的巔峰存在,技術造詣無人能及。他們的代碼不僅極致高效,還能預見未來的技術趨勢,推動整個行業的發展。每一行代碼都如同絕世劍招,令人嘆為觀止。 -
絕世神功
達到了編程和算法的終極境界,代碼劍宗的傳說人物。他們的技術超越了凡人的理解,能夠創造出前所未有的奇跡。江湖中流傳著他們的神話,后輩們都以他們為榜樣,追隨他們的步伐。
在代碼劍宗中,越高級別,能夠修煉的功法與接收到的任務就越深奧,也正是如此,代碼劍宗中的弟子每個人都想提升自己的級別,而級別的提升主要由個人的積分所決定,主流的獲取積分大致有兩種方式,一種是通過不斷接宗門內的任務不斷獲取宗門的積分,而任務越難積分也就越多,另一種是由門派武林至尊主動跟門派內的長老去申請。當然還有其他的提升級別的方式,如:換門派等,不過這些方式有一定的風險。而我們的主角阿強經過長達2年多的修煉,目前成為了一名武林高手。雖然在門派中的等級不低,但是由于其所在的部門大多是武林宗師、武林至尊級別,因此阿強一直沒覺得自己的職級有多高,反而覺得自己的職級太低。但也正是在這種環境下,阿強一直在想法設法地去接一些難度高的任務去獲取積分。而門派中除了自己去接受的任務之外,每個人每半個月都會統一分配一定積分點的任務,這些任務積分不會很多。偶爾有一些積分多的任務,往往都被搶走,除了極個別的那種難度很高的任務沒什么人去接之外,其他的稍微難度低一點,積分高的任務都是剛一出來就被領取。而那種突發又比較緊急的任務往往積分高,這種任務一般需要接任務的人在某一方面的能力比較突出。沒有這方面能力的人接這種任務往往完不成,而一旦沒有完成則是會扣個人積分,而所扣積分的多少是由此任務的緊急程度決定。
第三章 什么?sql語法報錯了?
上次阿強略微出手解決了NPE的問題之后,就回到洞府繼續思考阿汝提出的需求,正當他完成需求落地的方案正打算把需求的排期給到天工閣小凱時,腦海里就聽到門派的緊急任務傳音:“警告,F服務線上出現大量sql語法錯誤,請及時處理!!”,阿強聽到此傳音后,風緊扯乎地查看了一些此任務的難度與解決完的積分獎勵,阿強連忙接下了此任務。阿強這么快接下來這任務是因為他對于F服務是比較熟悉的,之前接一些任務的時候有過F服務的開發經驗。看到任務負責人變成自己后,阿強把需求排期的傳音發給小凱后便打開任務查看具體內容,10分鐘后…,阿強眼神閃爍,心里則是在想,大表分庫分表切換怎么會影響pagehelper的分頁sql的生成?半響后,阿強搖搖了頭,心里甩掉一些無用的心緒。阿強使用了天書法器,開始查看起了F服務的error日志,不一會他就找到了sql語法報錯的輸出日志。
Caused by: org.postgresql.util.PSQLException: ERROR: LIMIT #,# syntax is not supportedHint: Use separate LIMIT and OFFSET clauses.Position: 447
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2455)
at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2155)
at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:288)
at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:430)
at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:356)
at org.postgresql.jdbc.PgPreparedStatement.executeWithFlags(PgPreparedStatement.java:168)
at org.postgresql.jdbc.PgPreparedStatement.executeQuery(PgPreparedStatement.java:116)
at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeQuery(ProxyPreparedStatement.java:52)
at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeQuery(HikariProxyPreparedStatement.java)
at com.xxx.hbdl.core.jdbc.BasePreparedStatement.executeQuery(BasePreparedStatement.java:513)
at com.xxx.hbdl.atom.wrapper.AtomPreparedStatementWrapper.lambda$executeQuery$1(AtomPreparedStatementWrapper.java:56)
at io.micrometer.core.instrument.composite.CompositeTimer.recordCallable(CompositeTimer.java:68)
at com.xxx.bdl.atom.AtomExecutionTemplate.execute(AtomExecutionTemplate.java:93)
at com.xxx.bdl.atom.wrapper.AtomStatementWrapper$1.executeSql(AtomStatementWrapper.java:144)
at com.xxx.bdl.core.jdbc.filter.DefaultJdbcFilterChain.executeSql(DefaultJdbcFilterChain.java:61)
at com.xxx.bdl.atom.jdbc.filter.HhJdbcFilter.executeSql(HahasJdbcFilter.java:35)
at com.xxx.bdl.core.jdbc.filter.DefaultJdbcFilterChain.executeSql(DefaultJdbcFilterChain.java:59)
at com.xxx.bdl.core.jdbc.filter.JdbcFilter.executeSql(JdbcFilter.java:81)
at com.xxx.bdl.core.jdbc.filter.DefaultJdbcFilterChain.executeSql(DefaultJdbcFilterChain.java:59)
at com.xxx.bdl.atom.wrapper.AtomStatementWrapper.executeInternal(AtomStatementWrapper.java:147)
at com.xxx.bdl.atom.wrapper.AtomPreparedStatementWrapper.executeQuery(AtomPreparedStatementWrapper.java:57)
at com.xxx.bdl.group.GroupExecutor.executeQuery(GroupExecutor.java:90)
at com.xxx.bdl.group.GroupPst.executeQuery(GroupPst.java:97)
at com.xxx.bdl.group.GroupPst.execute(GroupPst.java:109)
at org.apache.ibatis.executor.statement.PreparedStatementHandler.query(PreparedStatementHandler.java:63)
at org.apache.ibatis.executor.statement.RoutingStatementHandler.query(RoutingStatementHandler.java:79)
at sun.reflect.GeneratedMethodAccessor287.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63)
at com.sun.proxy.$Proxy460.query(Unknown Source)
at sun.reflect.GeneratedMethodAccessor287.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:63)
at com.sun.proxy.$Proxy460.query(Unknown Source)
at org.apache.ibatis.executor.SimpleExecutor.doQuery(SimpleExecutor.java:63)
at org.apache.ibatis.executor.BaseExecutor.queryFromDatabase(BaseExecutor.java:326)
at org.apache.ibatis.executor.BaseExecutor.query(BaseExecutor.java:156)
at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:109)
at sun.reflect.GeneratedMethodAccessor315.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.ibatis.plugin.Invocation.proceed(Invocation.java:49)
at com.hellobike.druid.mybatis.plugin.SqlMonitorInterceptor.intercept(SqlMonitorInterceptor.java:42)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
at com.sun.proxy.$Proxy459.query(Unknown Source)
at com.github.pagehelper.PageInterceptor.intercept(PageInterceptor.java:136)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:61)
at com.sun.proxy.$Proxy459.query(Unknown Source)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:148)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:141)
at sun.reflect.GeneratedMethodAccessor706.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:426)
... 112 common frames omitted
從日志中,除了武俠世界中常見的spring、mybatis等靈域框架,F系統還基于mybatis框架針對自身情況進行了封裝,上述日志中輸出的堆棧中包含dbl前限定名的即是封裝后mybatis框架。優化后的mybatis框架并不影響pagehelper插件的使用,因此阿強并沒有過多地關注此靈域框架,直接就將重點放在了“org.postgresql.util.PSQLException: ERROR: LIMIT #,# syntax is not supported”上,這句話表示pg數據庫不支持LIMIT的寫法,為此,阿強特意去找來了mysql與pg兩種數據庫類型支持的分頁sql寫法。
-- MySQL分頁語法SELECT * from tableName where 1=1 limit 10 offset 10;SELECT * from tableName where 1=1 limit 0 , 10;SELECT * from tableName where 1=1 limit 10;
-- PG分頁語法
SELECT * FROM t_privilege_role limit 10 offset 10;
SELECT * FROM t_privilege_role offset 10 limit 10;
從上面不難看出MYSQL兼容PG的分頁sql語法名,但是PG并不支持MYSQL中的LIMIT xx的寫法,但是從阿強的印象中,F系統一直都是使用的PG數據庫,聯想到大表分庫分表的背景,他特意跑去了青云臺(守護盟所維護的系統)看了F系統目前使用的數據庫類型,果不其然,F系統目前除了PG數據庫,還使用了MYSQL數據庫。此時的阿強猜測是因為大表分庫分表所導致的此次問題。但是任務可不只是單純地知道是什么導致的就可以的,還需要解決這個問題。阿強通過法器IDEA打開了F項目的代碼,查看了PageHelper的版本和項目配置如下:
版本:5.1.4
項目配置(yml類型):
spring:datasource.druid.stat-view-servlet.enabled: falsejackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8application.name: Fpagehelper:helperDialect: postgresqlreasonable: truesupportMethodsArguments: trueparams: count=countSql
阿強查看完配置,配置的是pg,按道理來說,所有的分頁sql都應該是用的pg的語法,但實際情況卻不是如此。再思考到F系統的問題,F系統此次上線報錯并不是所有的節點都報錯,而只是其中的一臺節點報錯,其他節點都在正常運行。也就是說,pagehelper只有在這臺報錯的節點生成limit xxx分頁sql,其他節點生成的sql同時支持mysql 與pg,那也就是說,報錯的那臺節點生成的分頁sql是用的mysql語法。那么為什么pagehelper會去選擇使用mysql的語法格式生成分頁sql呢,為了弄懂這塊邏輯,阿強用idea打開了pagehelper的代碼,1小時后,阿強已大致知曉pagehelper生成分頁sql的原理,其中涉及到本次問題導致的核心邏輯在與方言的獲取這塊的邏輯上面,而pagehelper對于方言的處理的入口則是在PageInterceptor中的intercept中:
@Overridepublic Object intercept(Invocation invocation) throws Throwable {try {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement) args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds) args[2];ResultHandler resultHandler = (ResultHandler) args[3];Executor executor = (Executor) invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;//由于邏輯關系,只會進入一次if (args.length == 4) {//4 個參數時boundSql = ms.getBoundSql(parameter);cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);} else {//6 個參數時cacheKey = (CacheKey) args[4];boundSql = (BoundSql) args[5];}checkDialectExists();//對 boundSql 的攔截處理if (dialect instanceof BoundSqlInterceptor.Chain) {boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);}List resultList;//調用方法判斷是否需要進行分頁,如果不需要,直接返回結果if (!dialect.skip(ms, parameter, rowBounds)) {//開啟debug時,輸出觸發當前分頁執行時的PageHelper調用堆棧// 如果和當前調用堆棧不一致,說明在啟用分頁后沒有消費,當前線程再次執行時消費,調用堆棧顯示的方法使用不安全debugStackTraceLog();Future<Long> countFuture = null;//判斷是否需要進行 count 查詢if (dialect.beforeCount(ms, parameter, rowBounds)) {if (dialect.isAsyncCount()) {countFuture = asyncCount(ms, boundSql, parameter, rowBounds);} else {//查詢總數Long count = count(executor, ms, parameter, rowBounds, null, boundSql);//處理查詢總數,返回 true 時繼續分頁查詢,false 時直接返回if (!dialect.afterCount(count, parameter, rowBounds)) {//當查詢總數為 0 時,直接返回空的結果return dialect.afterPage(new ArrayList(), parameter, rowBounds);}}}resultList = ExecutorUtil.pageQuery(dialect, executor,ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);if (countFuture != null) {Long count = countFuture.get();dialect.afterCount(count, parameter, rowBounds);}} else {//rowBounds用參數值,不使用分頁插件處理時,仍然支持默認的內存分頁resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);}return dialect.afterPage(resultList, parameter, rowBounds);} finally {if (dialect != null) {dialect.afterAll();}}}
而其中方言的初始化入口則是在intercept中調用的checkDialectExists中:
private void checkDialectExists() {if (dialect == null) {synchronized (default_dialect_class) {if (dialect == null) {setProperties(new Properties());}}}}@Overridepublic void setProperties(Properties properties) {//緩存 count msmsCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);//在這里指定方言處理器,通過反射拿到具體實例String dialectClass = properties.getProperty("dialect");if (StringUtil.isEmpty(dialectClass)) {dialectClass = default_dialect_class;}Dialect tempDialect = ClassUtil.newInstance(dialectClass, properties);//方言初始化入口tempDialect.setProperties(properties);String countSuffix = properties.getProperty("countSuffix");if (StringUtil.isNotEmpty(countSuffix)) {this.countSuffix = countSuffix;}// debug模式,用于排查不安全分頁調用debug = Boolean.parseBoolean(properties.getProperty("debug"));// 通過 countMsId 配置自定義類String countMsIdGenClass = properties.getProperty("countMsIdGen");if (StringUtil.isNotEmpty(countMsIdGenClass)) {countMsIdGen = ClassUtil.newInstance(countMsIdGenClass, properties);}// 初始化完成后再設置值,保證 dialect 完成初始化dialect = tempDialect;}
最終方言的初始化如果沒有指定通過dialect配置指定dialectClass,則會進入PageHelper這個類中的setProperties方法中:
//類:PageHelper
@Overridepublic void setProperties(Properties properties) {setStaticProperties(properties);pageParams = new PageParams();autoDialect = new PageAutoDialect();pageBoundSqlInterceptors = new PageBoundSqlInterceptors();pageParams.setProperties(properties);autoDialect.setProperties(properties);pageBoundSqlInterceptors.setProperties(properties);//20180902新增 aggregateFunctions, 允許手動添加聚合函數(影響行數)CountSqlParser.addAggregateFunctions(properties.getProperty("aggregateFunctions"));// 異步 asyncCountService 并發度設置,這里默認為應用可用的處理器核心數 * 2,更合理的值應該綜合考慮數據庫服務器的處理能力int asyncCountParallelism = Integer.parseInt(properties.getProperty("asyncCountParallelism","" + (Runtime.getRuntime().availableProcessors() * 2)));asyncCountService = new ForkJoinPool(asyncCountParallelism,pool -> {final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);worker.setName("pagehelper-async-count-" + worker.getPoolIndex());return worker;}, null, true);}
autoDialect#setProperties中的邏輯:
public void setProperties(Properties properties) {this.properties = properties;//初始化自定義AutoDialectinitAutoDialectClass(properties);//使用 sqlserver2012 作為默認分頁方式,這種情況在動態數據源時方便使用String useSqlserver2012 = properties.getProperty("useSqlserver2012");if (StringUtil.isNotEmpty(useSqlserver2012) && Boolean.parseBoolean(useSqlserver2012)) {registerDialectAlias("sqlserver", SqlServer2012Dialect.class);registerDialectAlias("sqlserver2008", SqlServerDialect.class);}initDialectAlias(properties);//指定的 Helper 數據庫方言,和 不同String dialect = properties.getProperty("helperDialect");//運行時獲取數據源String runtimeDialect = properties.getProperty("autoRuntimeDialect");//1.動態多數據源if (StringUtil.isNotEmpty(runtimeDialect) && "TRUE".equalsIgnoreCase(runtimeDialect)) {this.autoDialect = false;}//2.動態獲取方言else if (StringUtil.isEmpty(dialect)) {autoDialect = true;}//3.指定方言else {autoDialect = false;this.delegate = instanceDialect(dialect, properties);}}
到這里方言的初始化就完成了,而分頁sql的生成則是在PageInterceptor#intercept中:
@Overridepublic Object intercept(Invocation invocation) throws Throwable {//do something......
resultList = ExecutorUtil.pageQuery(dialect, executor,ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);//do something......
}/*** 分頁查詢** @param dialect* @param executor* @param ms* @param parameter* @param rowBounds* @param resultHandler* @param boundSql* @param cacheKey* @param <E>* @return* @throws SQLException*/public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter,RowBounds rowBounds, ResultHandler resultHandler,BoundSql boundSql, CacheKey cacheKey) throws SQLException {//判斷是否需要進行分頁查詢if (dialect.beforePage(ms, parameter, rowBounds)) {//生成分頁的緩存 keyCacheKey pageKey = cacheKey;//處理參數對象parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);//調用方言獲取分頁 sqlString pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);//設置動態參數for (String key : additionalParameters.keySet()) {pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));}//對 boundSql 的攔截處理if (dialect instanceof BoundSqlInterceptor.Chain) {pageBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.PAGE_SQL, pageBoundSql, pageKey);}//執行分頁查詢return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);} else {//不執行分頁的情況下,也不執行內存分頁return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);}}
其中dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);這句會執行到com.github.pagehelper.PageHelper#getPageSql(MappedStatement, BoundSql, java.lang.Object, RowBounds, CacheKey)方法中:
@Overridepublic String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {return autoDialect.getDelegate().getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey);}
而autoDialect的初始化則是在com.github.pagehelper.PageHelper#skip方法中,觸發點同樣是在PageInterceptor#intercept
public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {Page page = pageParams.getPage(parameterObject, rowBounds);if (page == null) {return true;} else {//設置默認的 count 列if (StringUtil.isEmpty(page.getCountColumn())) {page.setCountColumn(pageParams.getCountColumn());}//設置默認的異步 count 設置if (page.getAsyncCount() == null) {page.setAsyncCount(pageParams.isAsyncCount());}autoDialect.initDelegateDialect(ms, page.getDialectClass());return false;}}
/*** 多數據動態獲取時,每次需要初始化,還可以運行時指定具體的實現** @param ms* @param dialectClass 分頁實現,必須是 {@link AbstractHelperDialect} 實現類,可以使用當前類中注冊的別名,例如 "mysql", "oracle"*/public void initDelegateDialect(MappedStatement ms, String dialectClass) {if (StringUtil.isNotEmpty(dialectClass)) {AbstractHelperDialect dialect = urlDialectMap.get(dialectClass);if (dialect == null) {lock.lock();try {if ((dialect = urlDialectMap.get(dialectClass)) == null) {dialect = instanceDialect(dialectClass, properties);urlDialectMap.put(dialectClass, dialect);}} finally {lock.unlock();}}dialectThreadLocal.set(dialect);} else if (delegate == null) {if (autoDialect) {this.delegate = autoGetDialect(ms);} else {//如果沒有設置動態多數據源、動態獲取方言則會進入此方法dialectThreadLocal.set(autoGetDialect(ms));}}}
/*** 自動獲取分頁方言實現** @param ms* @return*/public AbstractHelperDialect autoGetDialect(MappedStatement ms) {DataSource dataSource = ms.getConfiguration().getEnvironment().getDataSource();Object dialectKey = autoDialectDelegate.extractDialectKey(ms, dataSource, properties);if (dialectKey == null) {return autoDialectDelegate.extractDialect(dialectKey, ms, dataSource, properties);} else if (!urlDialectMap.containsKey(dialectKey)) {lock.lock();try {if (!urlDialectMap.containsKey(dialectKey)) {urlDialectMap.put(dialectKey, autoDialectDelegate.extractDialect(dialectKey, ms, dataSource, properties));}} finally {lock.unlock();}}return urlDialectMap.get(dialectKey);}
從上述代碼我們知道Dialect如果沒有指定dialectClass那就會通過數據庫的連接去自動獲取分頁方言實現。其中initDelegateDialect中的代碼需要格外關注:
if (autoDialect) {this.delegate = autoGetDialect(ms);} else {//如果沒有設置動態多數據源、動態獲取方言則會進入此方法,//也就是說,如果沒有配置helperDialect與autoRuntimeDialect這兩個配置,//那么在多數據源的場景下pagehelper永遠會拿執行sql中的第一個ds去動態獲取dialectdialectThreadLocal.set(autoGetDialect(ms));}
分析到這里,阿強已經完整地了解到了pagehelper中方言的獲取與方言對于分頁sql的處理原理,此時他已經知道發生此次問題的根因所在,但是為了驗證自己心中所想,他在F系統中寫了兩個測試方法:
//案例一:先pg查詢,后mysql查詢@Testpublic void queryTest(){PageHelper.startPage(1, 1);List<PGBean> list = pgRepository.select();PageInfo<MySqlBean> page = new PageInfo(list);System.out.println(JSON.toJSONString(page));Result<List<Response>> result = mysqlRepository.select();System.out.println(JSON.toJSONString(result));}
//案例二:先mysql查詢,后pg查詢@Testpublic void queryTest(){PageHelper.startPage(1, 1);Result<List<Response>> result = mysqlRepository.select();System.out.println(JSON.toJSONString(result));List<PGBean> list = pgRepository.select();PageInfo<MySqlBean> page = new PageInfo(list);System.out.println(JSON.toJSONString(page));}
在測試環境執行后,其中案例一正常運行,案例二報一樣的錯誤。此時真相大白,yaml配置有問題!
spring:datasource.druid.stat-view-servlet.enabled: falsejackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT+8application.name: Fpagehelper:helperDialect: postgresqlreasonable: truesupportMethodsArguments: trueparams: count=countSql
阿強又仔細檢查了一下配置,發現pagehelper的配置項多了一個spring的前綴,難怪pagehelper配置了Pg,但是卻用的MYSQL語法生成分頁sql,原來是配置沒有生效。發現問題之后的阿強輕嘆了一口氣,又傳音給了負責此項目的分庫分表的師兄弟小濟,小濟收到我的消息后,不一會就回復說,“此配置自F系統創建以來就有了,分庫分表的時候根本沒有會想到這個配置會有問題!”。阿強聽完搖搖頭,然后將此任務結束后就又開始進行棘手需求的開發