【程序大侠传】大表分库分表切换数据库类型导致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.mzph.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;以及删减…