如何在Spring Boot/Spring测试中检查XML查询均含limit 1条件?
在Spring Boot测试中检查MyBatis XML查询是否包含
limit 1 当然可以实现,这里提供两种实用方案,你可以根据场景选择:
方案一:直接扫描解析MyBatis XML文件
这种方法不需要执行SQL,直接读取XML文件做静态检查,适合快速验证所有查询配置。
实现步骤
- 在测试类中定位MyBatis XML文件的存放路径(通常是
src/main/resources/mapper) - 遍历所有
.xml文件,解析每个文件里的<select>节点 - 提取查询的SQL内容,检查是否包含
limit 1(可按需忽略大小写) - 发现不符合要求的查询时,抛出断言异常终止测试
代码示例
import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import java.io.File; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; @SpringBootTest public class MyBatisSqlStaticCheckTest { @Test void verifyAllSelectQueriesHaveLimit1() throws Exception { Path mapperRoot = Paths.get("src/main/resources/mapper"); List<String> invalidQueries = new ArrayList<>(); Files.walk(mapperRoot) .filter(path -> path.toString().endsWith(".xml")) .forEach(path -> { try { File xmlFile = path.toFile(); DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); Document doc = builder.parse(xmlFile); doc.getDocumentElement().normalize(); NodeList selectNodes = doc.getElementsByTagName("select"); for (int i = 0; i < selectNodes.getLength(); i++) { Element selectElem = (Element) selectNodes.item(i); String queryId = selectElem.getAttribute("id"); String sql = selectElem.getTextContent().trim().toLowerCase(); if (!sql.contains("limit 1")) { invalidQueries.add(String.format("文件[%s]中的查询[%s]未包含limit 1", xmlFile.getName(), queryId)); } } } catch (Exception e) { throw new RuntimeException("解析XML失败: " + path, e); } }); assert invalidQueries.isEmpty() : "存在不符合要求的查询:\n" + String.join("\n", invalidQueries); } }
方案二:用MyBatis拦截器在查询执行时检查
这种方法会在SQL实际执行前拦截检查,能覆盖动态生成的SQL(比如用<if>标签拼接的语句),适合集成测试场景。
实现步骤
- 自定义MyBatis拦截器,拦截
StatementHandler的prepare方法 - 在拦截逻辑中获取待执行的SQL,检查是否包含
limit 1 - 在测试类中注册拦截器,确保仅在测试环境生效
- 执行DAO层测试用例时,所有查询都会被校验,不符合则抛出异常
代码示例
自定义拦截器
import org.apache.ibatis.executor.statement.StatementHandler; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.plugin.*; import java.sql.Connection; import java.util.Properties; @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})}) public class SqlLimitCheckInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler handler = (StatementHandler) invocation.getTarget(); BoundSql boundSql = handler.getBoundSql(); String sql = boundSql.getSql().trim().toLowerCase(); // 仅检查SELECT语句,可按需调整范围 if (sql.startsWith("select")) { if (!sql.contains("limit 1")) { throw new AssertionError("查询SQL未包含limit 1: " + sql); } } return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { // 可配置白名单等参数,比如忽略某些查询ID } }
在测试类中注册拦截器
import org.apache.ibatis.session.SqlSessionFactory; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest public class MyBatisInterceptorCheckTest { @Autowired private SqlSessionFactory sqlSessionFactory; @BeforeEach void setUp() { // 测试前注册拦截器 sqlSessionFactory.getConfiguration().addInterceptor(new SqlLimitCheckInterceptor()); } @Test void testFindById() { // 调用你的DAO方法,比如fooMapper.findById(1L); // 若SQL未包含limit 1,会直接抛出AssertionError } @Test void testFindByStatus() { // 调用对应的DAO方法 } }
补充说明
- 如果有部分查询不需要检查
limit 1,可以在逻辑中添加白名单(比如根据查询ID过滤) - 静态解析XML时要注意SQL的空格、换行和大小写问题,建议统一转小写后再校验
- 拦截器方案能覆盖动态生成的SQL,静态解析方案仅能检查XML中写死的静态SQL
内容的提问来源于stack exchange,提问作者coder
相关产品推荐
相关产品推荐

