You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring ScriptUtils执行PostgreSQL脚本报错:未终止美元引号

解决Spring Boot中ScriptUtils执行PostgreSQL含美元引号函数脚本的问题

这个问题我之前也碰到过,根源是Spring的ScriptUtils默认的SQL语句分割逻辑和PostgreSQL的PL/pgSQL语法不兼容,具体原因和解决方法如下:

问题原因

你的SQL脚本使用了PostgreSQL特有的**美元引号($total$)来定义PL/pgSQL函数体,而ScriptUtils默认会以分号(;)**作为SQL语句的分隔符。当它扫描到函数内部的分号(比如SELECT count(*) into total FROM COMPANY;末尾的分号)时,会错误地将整个脚本截断成多个独立语句,导致美元引号没有被正确闭合,最终抛出"Unterminated dollar quote"异常。

解决方案

方案一:调整ScriptUtils的执行参数,适配PL/pgSQL脚本

你可以使用ScriptUtils.executeSqlScript的重载方法,自定义语句分隔符并关闭转义处理,避免误分割:

修改Java代码:

@Override
public void run(String... args) throws Exception {
    try {
        @Cleanup
        Connection c = ds.getConnection();
        // 配置适配PostgreSQL的脚本执行参数
        ScriptUtils.executeSqlScript(
                c,
                resource,
                false,          // 是否在错误时继续执行
                true,           // 是否忽略删除不存在对象的错误
                "--",           // 单行注释前缀
                ";;",           // 自定义语句分隔符(脚本中不会出现的符号)
                "/*", "*/",     // 块注释的起始/结束标记
                false           // 关闭转义处理,适配PostgreSQL美元引号
        );
    } catch (Exception e) {
        e.printStackTrace();
    }
}

同时修改你的SQL脚本,在函数定义末尾添加自定义的分隔符;;:

CREATE OR REPLACE FUNCTION totalRecords () RETURNS integer AS $total$
declare
total integer;
BEGIN
SELECT count(*) into total FROM COMPANY;
RETURN total;
END;
$total$ LANGUAGE plpgsql;;

方案二:使用ResourceDatabasePopulator简化配置

如果觉得ScriptUtils的参数过于繁琐,可以用ResourceDatabasePopulator来封装配置,效果完全一致:

@Override
public void run(String... args) throws Exception {
    try {
        @Cleanup
        Connection c = ds.getConnection();
        ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
        populator.setContinueOnError(false);
        populator.setIgnoreFailedDrops(true);
        populator.setSeparator(";;");       // 自定义分隔符
        populator.setEscapeProcessing(false);// 关闭转义处理
        populator.addScripts(resource);
        populator.populate(c);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

方案三:直接用JDBC执行完整脚本(绕过ScriptUtils)

如果上述方案仍有问题,你可以直接读取整个SQL脚本内容,作为单个SQL语句通过JDBC执行,彻底避免语句分割的问题:

@Override
public void run(String... args) throws Exception {
    try {
        @Cleanup
        Connection c = ds.getConnection();
        // 读取脚本的全部内容
        String sqlScript = FileCopyUtils.copyToString(new InputStreamReader(resource.getInputStream()));
        @Cleanup
        Statement stmt = c.createStatement();
        stmt.execute(sqlScript);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

这种方法适合复杂的PL/pgSQL脚本,完全绕过了ScriptUtils的分割逻辑,把整个函数定义作为一个完整命令执行。

额外注意事项

  • 确保你的COMPANY表已经存在,否则函数执行时会抛出表不存在的异常;
  • 确认Spring Boot的数据源配置正确(比如application.properties中的spring.datasource.url、spring.datasource.username、spring.datasource.password),能正常连接到Docker中的PostgreSQL容器。

内容的提问来源于stack exchange,提问作者kulatamicuda

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:58:37