【程序大侠传】大表分库分表切换数据库类型导致pagehelper生成sql语法报错
前序
代码剑宗等级分明,其门下弟子等级划分如下:
-
入门弟子
刚刚拜入代码剑宗,学习基础编程语言和基本剑法(语法和基础概念)。他们的代码还显得生涩,但已经开始展现出对优雅代码的追求。 -
江湖小虾
初步掌握了几种编程语言,能够写出基本的算法剑法(简单算法)。他们的代码开始变得简洁高效,但还需要更多的实战经验。 -
江湖侠客
在代码剑宗中已经小有名气,能够独立完成复杂的项目。他们的算法剑法(算法优化)和架构心法(系统设计)已经初见成效,代码如行云流水,功能完备。 -
武林高手
技术炉火纯青,精通多种编程语言和框架,他们的代码不仅高效,而且极具美感。无论是算法剑法还是架构心法,他们都能运用自如,解决各种复杂的技术难题。 -
武林宗师
在代码剑宗中享有极高的声望,擅长传授弟子,指导团队。他们在算法优化和系统架构设计方面有独到的见解,能够引领技术方向,推动团队进步。 -
武林至尊
代码剑宗的巅峰存在,技术造诣无人能及。他们的代码不仅极致高效,还能预见未来的技术趋势,推动整个行业的发展。每一行代码都如同绝世剑招,令人叹为观止。 -
绝世神功
达到了编程和算法的终极境界,代码剑宗的传说人物。他们的技术超越了凡人的理解,能够创造出前所未有的奇迹。江湖中流传着他们的神话,后辈们都以他们为榜样,追随他们的步伐。
在代码剑宗中,越高级别,能够修炼的功法与接收到的任务就越深奥,也正是如此,代码剑宗中的弟子每个人都想提升自己的级别,而级别的提升主要由个人的积分所决定,主流的获取积分大致有两种方式,一种是通过不断接宗门内的任务不断获取宗门的积分,而任务越难积分也就越多,另一种是由门派武林至尊主动跟门派内的长老去申请。当然还有其他的提升级别的方式,如:换门派等,不过这些方式有一定的风险。而我们的主角阿强经过长达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系统创建以来就有了,分库分表的时候根本没有会想到这个配置会有问题!”。阿强听完摇摇头,然后将此任务结束后就又开始进行棘手需求的开发
相关文章:

【程序大侠传】大表分库分表切换数据库类型导致pagehelper生成sql语法报错
前序 代码剑宗等级分明,其门下弟子等级划分如下: 入门弟子 刚刚拜入代码剑宗,学习基础编程语言和基本剑法(语法和基础概念)。他们的代码还显得生涩,但已经开始展现出对优雅代码的追求。 江湖小虾 初步掌握…...

7、Redis 队列与 Stream
引言 Redis 自 5.0 版本起引入了一种新的数据结构——Stream。这种数据结构不仅增加了 Redis 的数据处理能力,还使其在消息队列和数据流处理方面更具竞争力。Stream 提供了持久化、多播、消费组等功能,可以满足多种复杂的数据处理需求。 1. Redis Stre…...

FFT剖析
快速傅里叶变换 (fast Fourier transform) xn{x0,x1,…xn-1} (num:N) 旋转因子系数: d2pik/N 旋转因子 wk(n)(cos(dn)isin(dn)) n[0,N-1] y(k) sum(x(n)wk(n),0,N-1) y(k){y(0),y(1),…y(N-1)} 傅里叶级数 x(n)wk(n)的级数是: 1.d2pik/N 这个系数决…...

git clone报错RPC failed; curl 92 HTTP/2 stream 7 was not closed cleanly
问题描述 git clone github上的项目报错: RPC failed; curl 92 HTTP/2 stream 7 was not closed cleanly: CANCEL (err 8) 4796 bytes of body are still expected fetch-pack: unexpected disconnect while reading sideband packet early EOF fetch-pack: invalid index-pac…...

Apispec,一个用于生成 OpenAPI(Swagger)规范的 Python 库
目录 01什么是 Apispec? 为什么选择 Apispec? 安装与配置 02Apispec 的基本用法 生成简单的 API 文档 1、创建 Apispec 实例 2、定义 API 路由和视图 3、添加路径到 Apispec 集成 Flask 和 Apispec 1、安装…...

SpringBoot 自定义异常返回数据格式
Spring Boot 默认异常处理 当我们用 spring boot 开发接口是,当遇到异常时返回的数据格式是如下形式的 {"timestamp": "2024-07-06T02:48:55.79100:00","status": 404,"error": "Not Found","path":…...

【xinference】(15):在compshare上,使用docker-compose运行xinference和chatgpt-web项目,配置成功!!!
视频演示 【xinference】(15):在compshare上,使用docker-compose运行xinference和chatgpt-web项目,配置成功!!! 1,安装docker方法: #!/bin/shdistribution$(…...

【Unity 3D角色移动】
【Unity 3D角色移动】 在Unity 3D中实现角色移动通常涉及到几个关键步骤,包括设置角色的物理属性、处理输入、更新角色的位置以及动画同步。下面是实现基本3D角色移动的步骤和示例代码: 步骤1:设置角色的物理属性 角色通常使用Character Co…...

个人视角,社会影响力:自媒体的魅力所在
随着数字化时代的到来,自媒体正成为信息传播领域的一场革命。个人视角与社会影响力的结合,赋予了自媒体独特的魅力。在传统媒体受限制的同时,自媒体为每个人提供了表达自己观点和思想的自由。个体的真实视角使得自媒体在信息传播中发挥着重要…...

算法训练营day70
题目1:108. 冗余连接 (kamacoder.com) #include<iostream> #include<vector>using namespace std;int n; vector<int> father(10001, 0);void init() {for(int i 1;i < n;i) father[i] i; }int find(int u) {return u father[u] ? u : fa…...

EtherCAT转Profinet网关配置说明第二讲:上位机软件配置
EtherCAT协议转Profinet协议网关模块(XD-ECPNS20),不仅可以实现数据之间的通信,还可以实现不同系统之间的数据共享。EtherCAT协议转Profinet协议网关模块(XD-ECPNS20)具有高速传输的特点,因此通…...

日志自动分析-Web---360星图GoaccessALBAnolog
目录 1、Web-360星图(IIS/Apache/Nginx) 2、Web-GoAccess (任何自定义日志格式字符串) 源码及使用手册 安装goaccess 使用 输出 3-Web-自写脚本(任何自定义日志格式字符串) 4、Web-机器语言analog(任何自定义日…...

【面试八股文】java基础知识
引言 本文是java面试时的一些常见知识点总结归纳和一些拓展,笔者在学习这些内容时,特地整理记录下来,以供大家学习共勉。 一、数据类型 1.1 为什么要设计封装类,Integer和int区别是什么? 使用封装类的目的 对象化:…...

ssrf结合redis未授权getshell
目录 漏洞介绍 SSRF Redis未授权 利用原理 环境搭建 利用过程 rockylinux cron计划任务反弹shell 写公钥免密登录 ubuntu 写公钥免密登录 漏洞介绍 SSRF SSRF(server side request forgrey)服务端请求伪造,因后端未过滤用户输入&…...

魔法自如:精通 IPython %automagic 命令的切换艺术
魔法自如:精通 IPython %automagic 命令的切换艺术 在 IPython 的神奇世界里,魔术命令是其强大交互功能的核心。这些以 % 或 %% 开头的命令,能够执行一系列特殊的操作,从而增强用户的编程体验。但是,你是否知道&#…...

基于CentOS Stream 9平台搭建MinIO以及开机自启
1. 官网 https://min.io/download?licenseagpl&platformlinux 1.1 下载二进制包 指定目录下载 cd /opt/coisini/ wget https://dl.min.io/server/minio/release/linux-amd64/minio1.2 文件赋权 chmod x /opt/coisini/minio1.3 创建Minio存储数据目录: mkdi…...

shell-awk语法整理
shell-awk语法整理 前言基本语法内置变量1. $02. NF3. NR4. FS5. RS6. OFS7. ORS8. FILENAME9. FNR10. ARGV11. ENVIRON12. IGNORECASE13. RSTART 和 RLENGTH示例解释 内置函数循环语句(后面的;可不加)条件语句高级特性示例 特殊模式BEGINEND组合示例BEG…...

关于忠诚:忠于自己的良知、理想、信念
关于忠诚: 当我们面对公司、上司、爱人、恋人、合作伙伴还是某件事,会纠结离开还是留下,这里我们要深知忠诚的定义,我们不是忠诚于某个人、某件事、或者某个机构,而是忠诚于自己的良知,忠诚于自己的理想和…...

探索Linux:开源世界的无限可能
Linux是一款开源操作系统,它的起源可以追溯到上世纪90年代初。这个故事始于一个名叫Linus Torvalds的芬兰大学生,他在1983年开始编写一个用于个人电脑的操作系统内核。在他的努力下,Linux逐渐发展成为一个稳定而强大的操作系统。 然而&#…...

深度学习之半监督学习:一文梳理目标检测中的半监督学习策略
什么是半监督目标检测? 传统机器学习根据训练数据集中的标注情况,有着不同的场景,主要包括:监督学习、弱监督学习、弱半监督学习、半监督学习。由于目标检测任务的特殊性,在介绍半监督目标检测方法之前,我…...

Hive 高可用分布式部署详细步骤
目录 系统版本说明 hive安装包下载及解压 上传mysql-connector-java的jar包 配置环境变量 进入conf配置文件中,将文件重命名 在hadoop集群上创建文件夹 创建本地目录 修改hive-site.xml文件 同步到其他的节点服务器 修改node02中的配置 hive-site.xml 修改…...

ubuntu下运行程序时提示缺库问题的有效解决方法
目录 一、问题现象二、解决方式三、总结 一、问题现象 当我们平时在ubuntu上运行一个程序时时长会遇到如下情况,含义为本机缺少执行程序需要的库 这时候我们可能会根据缺少的库使用apt install 库名的模糊名字 进行安装,然后再去运行,此时可…...

GNU/Linux - wic文件的使用
Yocto/OpenEmbedded使用的磁盘镜像格式是 wic。为嵌入式系统提供 bootable images。 The disk image format used in the Yocto Project is wic. .wic 文件显然只是一个带有分区表和分区的磁盘镜像,就像下载 Linux 发行版时获得的所有 .img 文件一样。这就是为什么你…...

前端JS 插件实现下载【js-tool-big-box,下载大文件(fetch请求 + 下载功能版)
上一节,我们添加了下载大文件的纯功能版,意思就是需要开发者,在自己项目里发送请求,请求成功后,获取文件流的blob数据,然后 js-tool-big-box 帮助下载。 但考虑到,有些项目,可能比较…...

JVM专题之垃圾收集器
JVM参数 3.1.1 标准参数 -version -help -server -cp 3.1.2 -X参数 非标准参数,也就是在JDK各个版本中可能会变动 ``` -Xint 解释执行 -Xcomp 第一次使用就编译成本地代码 -Xmixed 混合模式,JVM自己来决定 3.1.3 -XX参数 > 使用得最多的参数类型 > > 非…...

SSM养老院管理系统-计算机毕业设计源码02221
摘要 本篇论文旨在设计和实现一个基于SSM的养老院管理系统,旨在提供高效、便捷的养老院管理服务。该系统将包括老人档案信息管理、护工人员管理、房间信息管理、费用管理等功能模块,以满足养老院管理者和居民的不同需求。 通过引入SSM框架&#x…...

使用Keil将STM32部分程序放在RAM中运行
手动分配RAM区域,新建.sct文件,定义RAM_CODE区域,并指定其正确的起始地址和大小。 ; ************************************************************* ; *** Scatter-Loading Description File generated by uVision *** ; ************************************************…...

【MySQL8.0】 CentOS8.0下安装mysql报错权限问题的记录
这里写自定义目录标题 基本信息问题记录 基本信息 OS: Linux server-02 4.18.0-240.el8.x86_64 #1 SMP Fri Sep 25 19:48:47 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux MySQL: 8.0 问题记录 缺少类库 mysql: error while loading shared libraries: libncurses.so.5: cannot…...

在内网互通的服务器中自由跳转与数据管理
在服务器中自由跳转与数据管理:实用命令指南 在管理或使用集群服务器环境时,高效地在不同节点间跳转、执行命令以及数据的相互拷贝是日常操作的重要组成部分。 1. 在集群节点间自由跳转:SSH(Secure Shell) SSH 是实…...

Arcgis Api 三维聚合支持最新版API
Arcgis Api 三维聚合支持最新版API 最近有同学问我Arcgis api 三维聚合,官方还不支持三维聚合API,二维可以。所以依旧是通过GraphicLayers 类来实现,可支持最新Arcgis Api版本 效果图:...