【程序大俠傳】大表分庫分表切換數據庫類型導致pagehelper生成sql語法報錯

前序

代碼劍宗等級分明,其門下弟子等級劃分如下:

  1. 入門弟子
    剛剛拜入代碼劍宗,學習基礎編程語言和基本劍法(語法和基礎概念)。他們的代碼還顯得生澀,但已經開始展現出對優雅代碼的追求。

  2. 江湖小蝦
    初步掌握了幾種編程語言,能夠寫出基本的算法劍法(簡單算法)。他們的代碼開始變得簡潔高效,但還需要更多的實戰經驗。

  3. 江湖俠客
    在代碼劍宗中已經小有名氣,能夠獨立完成復雜的項目。他們的算法劍法(算法優化)和架構心法(系統設計)已經初見成效,代碼如行云流水,功能完備。

  4. 武林高手
    技術爐火純青,精通多種編程語言和框架,他們的代碼不僅高效,而且極具美感。無論是算法劍法還是架構心法,他們都能運用自如,解決各種復雜的技術難題。

  5. 武林宗師
    在代碼劍宗中享有極高的聲望,擅長傳授弟子,指導團隊。他們在算法優化和系統架構設計方面有獨到的見解,能夠引領技術方向,推動團隊進步。

  6. 武林至尊
    代碼劍宗的巔峰存在,技術造詣無人能及。他們的代碼不僅極致高效,還能預見未來的技術趨勢,推動整個行業的發展。每一行代碼都如同絕世劍招,令人嘆為觀止。

  7. 絕世神功
    達到了編程和算法的終極境界,代碼劍宗的傳說人物。他們的技術超越了凡人的理解,能夠創造出前所未有的奇跡。江湖中流傳著他們的神話,后輩們都以他們為榜樣,追隨他們的步伐。

在代碼劍宗中,越高級別,能夠修煉的功法與接收到的任務就越深奧,也正是如此,代碼劍宗中的弟子每個人都想提升自己的級別,而級別的提升主要由個人的積分所決定,主流的獲取積分大致有兩種方式,一種是通過不斷接宗門內的任務不斷獲取宗門的積分,而任務越難積分也就越多,另一種是由門派武林至尊主動跟門派內的長老去申請。當然還有其他的提升級別的方式,如:換門派等,不過這些方式有一定的風險。而我們的主角阿強經過長達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系統創建以來就有了,分庫分表的時候根本沒有會想到這個配置會有問題!”。阿強聽完搖搖頭,然后將此任務結束后就又開始進行棘手需求的開發

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/42457.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/42457.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/42457.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《python程序語言設計》2018版第5章第53題利用turtle繪制sin和cos函數 sin藍色,cos紅色和52題類似

直接上題和代碼 5.53 &#xff08;Turtle&#xff1a;繪制sin和cos函數&#xff09;編寫程序繪制藍色的sin函數和紅色的cos函數。 代碼和結果 turtle.speed(10) turtle.penup() # sin 用藍色 turtle.color("blue") #這道題和上道題一樣&#xff0c;先把turtle放到起始…

架構面試-數據庫優化問題

文章目錄 如何定位慢查詢1. 開啟慢查詢日志MySQL示例&#xff1a;PostgreSQL示例&#xff1a; 2. 分析慢查詢日志MySQL&#xff1a;PostgreSQL&#xff1a; 3. 使用數據庫性能工具MySQL&#xff1a;PostgreSQL&#xff1a; 4. 優化慢查詢5. 監控與持續優化 sql語句執行的很慢&a…

從0到1制作單只鰲蝦運動軌跡追蹤軟件

前言 需要準備windows10操作系統&#xff0c;python3.11.9&#xff0c;cuDNN8.9.2.26&#xff0c;CUDA11.8&#xff0c;paddleDetection2.7 流程&#xff1a; 準備數據集-澳洲鰲蝦VOC數據集 基于RT-DETR目標檢測模型訓練導出onnx模型進行python部署平滑濾波處理視頻幀保留的…

簡介時間復雜度

好了&#xff0c;今天我們來了解一下&#xff0c;我們在做練習題中常出現的一個名詞。時間復雜度。我相信大家如果有在練習過題目的話。對這個名詞應該都不陌生吧。但是可能很少的去思考它是干什么的代表的什么意思。反正我以前練習的時候就是這樣。我只知道有這么一個名詞在題…

【全面講解下iPhone新機官網驗機流程】

&#x1f3a5;博主&#xff1a;程序員不想YY啊 &#x1f4ab;CSDN優質創作者&#xff0c;CSDN實力新星&#xff0c;CSDN博客專家 &#x1f917;點贊&#x1f388;收藏?再看&#x1f4ab;養成習慣 ?希望本文對您有所裨益&#xff0c;如有不足之處&#xff0c;歡迎在評論區提出…

MybatisPlus實現插入/修改數據自動設置時間

引言 插入數據時自動設置當前時間&#xff0c;更新數據時自動修改日期為修改時的日期。 使用MybatisPlus的擴展接口MetaObjectHandler 步驟 實現接口 實體類加注解 實現接口 package com.example.vueelementson.common;import com.baomidou.mybatisplus.core.handlers.M…

C++ 模版進階

目錄 前言 1. 非類型模版參數 1.1 概念與講解 1.2 array容器 2. 模版的特化 2.1 概念 2.2 函數模版特化 2.3 類模版特化 2.3.1 全特化 2.3.2 偏特化 3.模版的編譯分離 3.1 什么是分離編譯 3.2 模版的分離編譯 3.3 解決方法 4. 模版總結 總結 前言 本篇文章主要…

包/final/權限修飾符/代碼塊

包package 1、包的作用 包用來管理不同的類。 2、包名 包名要全部小寫&#xff0c;一般是域名反寫&#xff0c;如com.liu。在Java中&#xff0c;java解釋器會將package中的.解釋為目錄分隔符/&#xff0c;也就是說該文件的目錄結構為&#xff1a;...com/liu/... 3、全類名…

1.pwn的匯編基礎(提及第一個溢出:整數溢出)

匯編掌握程度 能看懂就行&#xff0c;絕大多數情況不需要真正的編程(shellcode題除外) 其實有時候也不需要讀匯編&#xff0c;ida F5 通常都是分析gadget&#xff0c;知道怎么用&#xff0c; 調試程序也不需要分析每一條匯編指令&#xff0c;單步執行然后查看寄存器狀態即可 但…

Text2SQL提問中包括時間的實戰方案

大家好,我是herosunly。985院校碩士畢業,現擔任算法研究員一職,熱衷于機器學習算法研究與應用。曾獲得阿里云天池比賽第一名,CCF比賽第二名,科大訊飛比賽第三名。擁有多項發明專利。對機器學習和深度學習擁有自己獨到的見解。曾經輔導過若干個非計算機專業的學生進入到算法…

實現多數相加,但是傳的參不固定

一、情景 一般實現的加法和減法等簡單的相加減函數的話。一般都是寫好固定傳的參數。比如&#xff1a; function add(a,b) {return a b;} 這是固定的傳入倆個&#xff0c;如果是三個呢&#xff0c;有人說當然好辦&#xff01; 這樣寫不就行了&#xff01; function add(a…

vue中自定義設置多語言(包括使用vue-i18n),并且運行js腳本自動生成多語言文件

在項目中需要進行多個國家語言的切換時&#xff0c;可以用到下面方法其中一個 一、自定義設置多語言 方法一: 可以自己編寫一個設置多語言文件 在項目新建js文件&#xff0c;命名為&#xff1a;language.js&#xff0c;代碼如下 // language.js 文檔 let languagePage {CN…

聊一下Maven打包的問題(jar要發布)

文章目錄 一、問題和現象二、解決方法&#xff08;1&#xff09;方法一、maven-jar-pluginmaven-dependency-plugin&#xff08;2&#xff09;方法二、maven-assembly-plugin 一、問題和現象 現在的開發一直都是用spring boot&#xff0c;突然有一天&#xff0c;要自己開發一個…

Django之項目開發(二)

目錄 一、安裝和使用uWSGI 1.1、安裝 1.2、配置文件 1.3、啟動與停止uwsgi 二、安裝nginx 三、Nginx 配置uWSGI 四、Nginx配置靜態文件 五、Nginx配置負載均衡 一、安裝和使用uWSGI uWSGI 是一個 Web 服務器,可以用來部署 Python Web 應用。它是一個高性能的通用的 We…

味蕾與理解:應對自閉癥兒童挑食的策略與理解

在星貝育園自閉癥康復學校&#xff0c;我們深知飲食習慣對孩子們的成長至關重要&#xff0c;而自閉癥兒童的挑食問題往往比同齡兒童更為突出&#xff0c;給家長和照顧者帶來了額外的挑戰。今天&#xff0c;作為這里的老師&#xff0c;我想與大家分享一些應對自閉癥兒童挑食的策…

(南京觀海微電子)——電阻應用及選取

什么是電阻&#xff1f; 電阻是描述導體導電性能的物理量&#xff0c;用R表示。 電阻由導體兩端的電壓U與通過導體的電流I的比值來定義&#xff0c;即&#xff1a; 所以&#xff0c;當導體兩端的電壓一定時&#xff0c;電阻愈大&#xff0c;通過的電流就愈小&#xff1b;反之&…

鴻蒙應用實踐:利用扣子API開發起床文案生成器

前言 扣子是一個新一代 AI 應用開發平臺&#xff0c;無需編程基礎即可快速搭建基于大模型的 Bot&#xff0c;并發布到各個渠道。平臺優勢包括無限拓展的能力集&#xff08;內置和自定義插件&#xff09;、豐富的數據源&#xff08;支持多種數據格式和上傳方式&#xff09;、持…

[Unity入門01] Unity基本操作

參考的傅老師的教程學了一下Unity的基礎操作&#xff1a; [傅老師/Unity教學] Unity3D基礎入門 [華梵大學] 遊戲引擎應用基礎(Unity版本) Class#01 移動&#xff1a;鼠標中鍵旋轉&#xff1a;鼠標右鍵放大&#xff1a;鼠標滾輪飛行模式&#xff1a;右鍵WASDQEFocus模式&…

算法設計與分析 實驗5 并查集法求圖論橋問題

目錄 一、實驗目的 二、問題描述 三、實驗要求 四、實驗內容 &#xff08;一&#xff09;基準算法 &#xff08;二&#xff09;高效算法 五、實驗結論 一、實驗目的 1. 掌握圖的連通性。 2. 掌握并查集的基本原理和應用。 二、問題描述 在圖論中&#xff0c;一條邊被稱…

基于Android Studio訂餐管理項目

目錄 項目介紹 圖片展示 運行環境 獲取方式 項目介紹 能夠實現登錄&#xff0c;注冊、首頁、訂餐、購物車&#xff0c;我的。 用戶注冊后&#xff0c;登陸客戶端即可完成訂餐、瀏覽菜譜等功能&#xff0c;點餐&#xff0c;加入購物車&#xff0c;結算&#xff0c;以及刪減…