目录
前言
在博客:【写在创作纪念日】基于SpringBoot和PostGIS的各省东西南北四至极点区县可视化中,我们详细而具体的讲解了如何在SpringBoot中与PostGIS进行交互,实现省域的四至(东西南北)各区县的空间检索及可视化。后台计算程序运行友好,功能也都还算正常,在地图界面上点击具体的省份、行政区划等可以正常的查询出东南西北四至点及所在的区县信息,功能正常。
前台看起来一切正常,但是在我们的后台输出日志当中,依然发现了一些不正常本地log日志。如下图所示:
虽然报错的信息对程序的功能并没有实质的影响。但依然值得我们关注,一定是有地方执行有问题。本文即作为排查和解决报错信息的教程,逐步来排查这个错误。通过对问题的重现,让大家了解出现相关问题的场景,重现步骤等信息,为大家在实际项目中解决问题提供相关背景参考。接着讲解在空间分析中Lateral关键字的作用,以及在省域四至问题求解中的具体使用,最后来讲解如何在Mybatis-Plus中集成Druid时修复异常的信息,通过讲解两种解决办法,让大家了解如果在工项目中真实遇到了,可以有哪些解决办法。
一、问题重现
本节将讲解问题的出现场景,同时详细列出本博客参考时使用的相关依赖的版本,也许新的依赖包已经将这些问题统统都解决。
1、环境说明
序号 | 依赖组件 | 版本 | 说明 |
1 | JDK | 1.8 | JDK依赖 |
2 | Mybatis-Plus | 3.5.2 | ORM操作 |
3 | druid | 1.2.11 | 数据库连接池 |
2、重现步骤
在省域的四至所属区县分析中,在页面的右侧边栏中,有全国的所有省份列表,通过点击省份列表操作栏中的“定位”按钮,可以实现对当前省份的四至所属区县求解。功能操作示意图如下所示:
3、错误信息
这里将更多的后台运行错误贴出来,看大家在实际的项目中是否也曾经遇到类似的问题。错误信息如下所示:
22:32:31.176 [http-nio-8080-exec-94] ERROR c.a.d.f.s.StatFilter - [mergeSql,150] - merge sql error, dbType postgresql, druid-1.2.11, sql : WITH temp_area AS ( xxx) SELECT direction,id,province_code,province_name,city_code,city_name,area_code, area_name,type, st_asgeojson(geom) geomJson, x lon,y lat FROM bounds_info
com.alibaba.druid.sql.parser.ParserException: syntax error, error in :'T,LATERAL ST_DumpPoints ( T.geom ) AS dp', expect ), actual (, pos 208, line 1, column 208, token (
at com.alibaba.druid.sql.parser.SQLParser.printError(SQLParser.java:838)
at com.alibaba.druid.sql.parser.SQLParser.accept(SQLParser.java:846)
at com.alibaba.druid.sql.dialect.postgresql.parser.PGSelectParser.query(PGSelectParser.java:62)
at com.alibaba.druid.sql.parser.SQLSelectParser.select(SQLSelectParser.java:61)
at com.alibaba.druid.sql.parser.SQLStatementParser.parseWithQuery(SQLStatementParser.java:5458)
at com.alibaba.druid.sql.dialect.postgresql.parser.PGSQLStatementParser.parseWith(PGSQLStatementParser.java:421)
at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:186)
at com.alibaba.druid.sql.parser.SQLStatementParser.parseStatementList(SQLStatementParser.java:101)
at com.alibaba.druid.sql.visitor.ParameterizedOutputVisitorUtils.parameterize(ParameterizedOutputVisitorUtils.java:163)
at com.alibaba.druid.sql.visitor.ParameterizedOutputVisitorUtils.parameterize(ParameterizedOutputVisitorUtils.java:134)
at com.alibaba.druid.filter.stat.StatFilter.mergeSql(StatFilter.java:148)
at com.alibaba.druid.filter.stat.StatFilter.createSqlStat(StatFilter.java:672)
at com.alibaba.druid.filter.stat.StatFilter.statementPrepareAfter(StatFilter.java:325)
at com.alibaba.druid.filter.FilterEventAdapter.connection_prepareStatement(FilterEventAdapter.java:118)
可以很明显的看到,报错的信息是在一个统计的过滤器中所抛出来的,过滤器所在类是:com.alibaba.druid.filter.stat.StatFilter。本小节内容到此结束,对问题的暴露介绍到这里。下面将逐步来说明这些问题。
二、关于LATERAL
本节将介绍一下载PostGIS空间数据库中的Lateral关键字,主要将介绍Lateral关键字的作用场景以及在省域四至所属区县的问题求解过程中的作用。
1、Lateral作用场景
在 PostgreSQL 中,LATERAL
关键字用于指定一个子查询,该子查询可以引用出现在 FROM
子句中前面的表。它允许子查询访问外部查询中的列,这在需要根据外部查询的行动态生成结果时非常有用。
主要作用
关联子查询:
LATERAL
子查询可以引用FROM
子句中前面的表中的列。这使得子查询可以根据外部查询的行动态生成结果。提高查询灵活性:
LATERAL
允许在FROM
子句中使用动态的子查询,使得查询更加灵活,可以处理更复杂的数据关系。优化性能:在某些情况下,使用
LATERAL
可以减少重复的计算,提高查询效率。
典型应用场景
与表值函数结合使用:
LATERAL
常用于与表值函数(如unnest
、generate_series
等)结合,以便根据外部查询的行生成动态的结果。几何函数:在 PostGIS 中,
LATERAL
常用于与几何函数(如ST_DumpPoints
)结合,将几何对象分解为点集合。多对多关系:在处理多对多关系时,
LATERAL
可以动态生成中间表数据。
2、在四至场景中使用
先来看一下在省域四至所属区县的求解场景中的具体SQL实现:
WITH temp_area AS ( SELECT * FROM biz_area T WHERE T.province_code = '510000' ),
bounds_info AS (( SELECT '最东' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y FROM temp_area T,LATERAL ST_DumpPoints ( T.geom ) AS dp ORDER BY x DESC LIMIT 1
) UNION ALL
( SELECT '最西' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y
FROM temp_area T,LATERAL ST_DumpPoints ( T.geom ) AS dp ORDER BY x ASC LIMIT 1
) UNION ALL
( SELECT '最南' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y
FROM temp_area T, LATERAL ST_DumpPoints ( T.geom ) AS dp ORDER BY y ASC LIMIT 1
) UNION ALL
(SELECT '最北' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y
FROM temp_area T, LATERAL ST_DumpPoints ( T.geom ) AS dp ORDER BY y DESC LIMIT 1 ) ) SELECT direction,id,province_code,province_name,city_code,city_name,area_code,
area_name,type, st_asgeojson(geom) geomJson, x lon,y lat FROM bounds_info;
在这个例子中,LATERAL
子查询 dp
调用 ST_DumpPoints
函数,将几何对象 T.geom
分解为点集合,并提取每个点的坐标信息。在求解具体的区县信息时,会将曲线的Polygon数据转变为点,才能在查询结果中使用St_Y计算对应的经纬度值。 当然上面的这条sql完全是正常的,可以在客户端中正常执行,完全可以在Navicat客户端中进行调试。调试过程暂且不表,大家可以自行尝试验证。
三、问题解决之道
既然遇到了问题,虽然不会造成特别重大的影响,但是有运行强迫症的我们还是要关注下,毕竟是Error,不是普通的程序运行日志。因此我们动手来解决一下遇到的问题。要解决问题,首先要明确出现的问题。这里主要分享两个解决问题的办法,从关闭SQL合并的设置到改写处理SQL两个方向来进行。
1、源码追踪
本着按图索骥的原则,根据在控制台中异常信息所在的代码类,即:com.alibaba.druid.filter.stat.StatFilter中,异常抛出的行数是150行,所处理的程序逻辑如下:
源代码如下所示:
public String mergeSql(String sql, DbType dbType) {
if (!mergeSql) {
return sql;
}
try {
sql = ParameterizedOutputVisitorUtils.parameterize(sql, dbType, null, null, null);
} catch (Exception e) {
LOG.error("merge sql error, dbType " + dbType + ", druid-" + VERSION.getVersionNumber() + ", sql : " + sql, e);
}
return sql;
}
从代码上看,代码不是很复杂,首先判断要执行的SQL是否需要合并,如果不需要合并,直接返回执行SQL,否则对sql进行合并处理。当然,为了不影响后续的执行,如果转换有问题,也对合并前的SQL原样返回。而对于更复杂的处理,大家可以看下druid的处理方式,这里截个图做个示例,还有一些没截图完整的,需要大家去了解。
2、关闭sql合并
了解了Druid的程序合并逻辑之后,就可以对阵下药了。在前面的源码追踪内容中有介绍,如果当前的Druid应用程序设置了不需要合并sql,sql将直接原样返回,不会做任何的处理。因此,如果不想在程序运行过程中发生异常,可以将合并SQL的开关关闭,这样也能解决问题。在Druid中关闭sql合并的方法简单,在Druid声明数据库连接池的地方关闭选项即可,代码如下:
在这个配置文件中,把merge-sql:true的配置修改一下,改成false后再重新运行程序,同样来点击各省份,会发现在控制台中已经没有了合并sql的错误信息。控制台窗口如下所示:
3、改写处理SQL
在上面的例子中,我们选择了关闭sql合并的设置,虽然能将问题暂时解决掉,但是对于其它需要合并的SQL就无法进行合并处理了。难道问题就无法解决了吗?当然不是,这里我们将分享第二种方法,通过改写处理SQL的方式来解决不关闭sql合并的问题。回到我们的查询SQL中,可以发现,在我们的查询需求中,我们需要求解的四至点位,其实最后都会变成一个点,即Limit 1。因此其实可以不使用Lateral的关键字,直接进行查询,通过对查询结果集排序后选择第一条,依然是满足我们的实际需求的,首先将查询SQL做如下改写,其它内容不变:
static final String FIND_ESWNAREA_BYPROVINCE_SQL = "<script>"
+ " WITH temp_area AS ( SELECT * FROM biz_area T WHERE T.province_code = #{province_code} ), "
+ " bounds_info AS ("
+ " ( SELECT '最东' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y "
+ " FROM temp_area T,ST_DumpPoints ( T.geom ) AS dp ORDER BY x DESC LIMIT 1 "
+ " ) UNION "
+ " ( SELECT '最西' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y "
+ " FROM temp_area T,ST_DumpPoints ( T.geom ) AS dp ORDER BY x ASC LIMIT 1 "
+ " ) UNION "
+ " ( SELECT '最南' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y "
+ " FROM temp_area T,ST_DumpPoints ( T.geom ) AS dp ORDER BY y ASC LIMIT 1 "
+ " ) UNION "
+ " (SELECT '最北' AS direction,T.*,ST_X ( dp.geom ) AS x,ST_Y ( dp.geom ) AS y "
+ " FROM temp_area T,ST_DumpPoints ( T.geom ) AS dp ORDER BY y DESC LIMIT 1 ) "
+ " ) SELECT direction,id,province_code,province_name,city_code,city_name,area_code, "
+ " area_name,type, st_asgeojson(geom) geomJson, x lon,y lat FROM bounds_info "
+ "</script>";
将应用程序启动后,再次点击相关按钮,程序控制成功无任何异常,如下所示:
页面正常,且控制台无任何异常输出,成功解决以上问题。
四、总结
以上就是本文的主要内容,本文即作为排查和解决报错信息的教程,逐步来排查这个错误。通过对问题的重现,让大家了解出现相关问题的场景,重现步骤等信息,为大家在实际项目中解决问题提供相关背景参考。接着讲解在空间分析中Lateral关键字的作用,以及在省域四至问题求解中的具体使用,最后来讲解如何在Mybatis-Plus中集成Druid时修复异常的信息,通过讲解两种解决办法,让大家了解如果在工项目中真实遇到了,可以有哪些解决办法。
当然在实际的项目当中,选用哪种解决办法,需要大家根据实际的需求来决定。需要大家知晓的是:当 mergeSql
设置为 true
时,Druid 会将相同的 SQL 语句进行合并,以减少统计信息的数量,这在性能统计和内存占用方面可能有所帮助,但可能会影响调试和统计信息的准确性。如果需要对某个特定的方法或场景禁用 SQL 合并处理,可以在该方法的调用环境中动态调整 Druid 的配置,或者通过其他方式(如使用不同的数据源)来实现。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。