From 5ce639333725c7d46e5815f9e22057bcbc14afb6 Mon Sep 17 00:00:00 2001 From: zgw <13623675548@163.com> Date: Fri, 4 Sep 2026 14:12:47 +0800 Subject: [PATCH] fix: suppress unsupported Druid SQL parsing --- .../src/main/resources/application-druid.yml | 4 +- ...adowInputMapperDruidCompatibilityTest.java | 86 +++++++++++++++++++ 2 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 ruoyi-admin/src/test/java/com/ruoyi/algorithm/shadow/ShadowInputMapperDruidCompatibilityTest.java diff --git a/ruoyi-admin/src/main/resources/application-druid.yml b/ruoyi-admin/src/main/resources/application-druid.yml index c7b9fab..303c454 100644 --- a/ruoyi-admin/src/main/resources/application-druid.yml +++ b/ruoyi-admin/src/main/resources/application-druid.yml @@ -55,7 +55,9 @@ spring: # 慢SQL记录 log-slow-sql: true slow-sql-millis: 1000 - merge-sql: true + # SQL Server THROW/MERGE/OUTPUT syntax is not understood by Druid 1.2.23. + # Keep statistics and slow-SQL logging, but skip SQL parameterization. + merge-sql: false wall: config: multi-statement-allow: true diff --git a/ruoyi-admin/src/test/java/com/ruoyi/algorithm/shadow/ShadowInputMapperDruidCompatibilityTest.java b/ruoyi-admin/src/test/java/com/ruoyi/algorithm/shadow/ShadowInputMapperDruidCompatibilityTest.java new file mode 100644 index 0000000..db4c1b4 --- /dev/null +++ b/ruoyi-admin/src/test/java/com/ruoyi/algorithm/shadow/ShadowInputMapperDruidCompatibilityTest.java @@ -0,0 +1,86 @@ +package com.ruoyi.algorithm.shadow; + +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.InputStream; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; + +import javax.xml.parsers.DocumentBuilderFactory; + +import com.alibaba.druid.DbType; +import com.alibaba.druid.filter.stat.StatFilter; +import org.junit.jupiter.api.Test; +import org.springframework.core.env.PropertySource; +import org.springframework.core.io.ClassPathResource; +import org.springframework.boot.env.YamlPropertySourceLoader; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; + +class ShadowInputMapperDruidCompatibilityTest { + + private static final String MAPPER_RESOURCE = "mapper/algorithm/ShadowInputMapper.xml"; + private static final Pattern MYBATIS_PARAMETER = Pattern.compile("#\\{[^}]+}"); + + @Test + void configuredDruidStatFilterSkipsUnsupportedParsingAndKeepsSlowSqlMonitoring() throws Exception { + String claimInstance = boundSql("claimInstance"); + String upsertRuntimeMetric = boundSql("upsertRuntimeMetric"); + StatFilter filter = configuredStatFilter(); + + assertAll( + () -> assertFalse(filter.isMergeSql()), + () -> assertEquals(1000L, filter.getSlowSqlMillis()), + () -> assertTrue(filter.isLogSlowSql()), + () -> assertDoesNotThrow(() -> assertEquals( + claimInstance, filter.mergeSql(claimInstance, DbType.sqlserver))), + () -> assertDoesNotThrow(() -> assertEquals( + upsertRuntimeMetric, filter.mergeSql(upsertRuntimeMetric, DbType.sqlserver)))); + } + + private static StatFilter configuredStatFilter() throws Exception { + List> sources = new YamlPropertySourceLoader().load( + "application-druid", new ClassPathResource("application-druid.yml")); + StatFilter filter = new StatFilter(); + filter.setMergeSql(property(sources, "spring.datasource.druid.filter.stat.merge-sql", Boolean.class)); + filter.setLogSlowSql(property(sources, "spring.datasource.druid.filter.stat.log-slow-sql", Boolean.class)); + filter.setSlowSqlMillis(property(sources, "spring.datasource.druid.filter.stat.slow-sql-millis", Integer.class)); + return filter; + } + + private static T property(List> sources, String name, Class type) { + return sources.stream() + .map(source -> source.getProperty(name)) + .filter(Objects::nonNull) + .findFirst() + .map(type::cast) + .orElseThrow(() -> new IllegalArgumentException("Configuration property not found: " + name)); + } + + private static String boundSql(String statementId) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + + try (InputStream stream = Objects.requireNonNull( + Thread.currentThread().getContextClassLoader().getResourceAsStream(MAPPER_RESOURCE), + MAPPER_RESOURCE)) { + Document document = factory.newDocumentBuilder().parse(stream); + NodeList elements = document.getElementsByTagName("*"); + for (int index = 0; index < elements.getLength(); index++) { + Element element = (Element) elements.item(index); + if (statementId.equals(element.getAttribute("id"))) { + return MYBATIS_PARAMETER.matcher(element.getTextContent()).replaceAll("?"); + } + } + } + throw new IllegalArgumentException("Mapper statement not found: " + statementId); + } +}