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

Oracle下EntityManager原生查询设置表/列参数报错排查

问题

使用EntityManager创建nativeQuery操作Oracle数据库,编写了一个通用方法,尝试通过setParameter设置列名、表名参数来适配不同表、用户和列,但执行时抛出SQL语法错误,错误信息为ORA-00903: 无效表名,相关代码及报错栈如下:

代码

public Map<String, Integer> ricavaDoppioniDiUnaTabellaNSNBroker(String database, String tabella, String colonna) throws PersistenceException {
    Map<String, Integer> doppioniNSN = null;
    try {
        List<Object[]> objList = (List<Object[]>) entityManager.createNativeQuery(
                        "SELECT * FROM " +
                                "(SELECT :colonna, COUNT(*) AS occorrenza FROM :tabella t GROUP BY :colonna) a " +
                                "WHERE a.occorrenza > 1")
                .setParameter("colonna", "t." + colonna)
                .setParameter("tabella", database + "." + tabella)
                //.setParameter("colonna", "t." + colonna)
                .getResultList();

        if (objList != null) {
            doppioniNSN = new HashMap<>();
            for (Object[] elem : objList) {
                String nsn_id = elem[0].toString();
                Integer quantity = Integer.valueOf(elem[1].toString());
                doppioniNSN.put(nsn_id, quantity);
            }
        }
    } catch (PersistenceException e) {
        brkLog.error("Errore durante l'esecuzione della query che conta i doppioni. Di seguito parte dello stacktrace...");
        throw e;
    }
    return doppioniNSN;
}

报错栈

Caused by: org.hibernate.exception.SQLGrammarException: 无法提取结果集
    at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:63)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:67)
    at org.hibernate.loader.Loader.getResultSet(Loader.java:2292)
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2050)
    at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:2012)
    at org.hibernate.loader.Loader.doQuery(Loader.java:953)
    at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:354)
    at org.hibernate.loader.Loader.doList(Loader.java:2838)
    at org.hibernate.loader.Loader.doList(Loader.java:2820)
    at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2652)
    at org.hibernate.loader.Loader.list(Loader.java:2647)
    at org.hibernate.loader.custom.CustomLoader.list(CustomLoader.java:338)
    at org.hibernate.internal.SessionImpl.listCustomQuery(SessionImpl.java:2131)
    at org.hibernate.internal.AbstractSharedSessionContract.list(AbstractSharedSessionContract.java:1163)
    at org.hibernate.query.internal.NativeQueryImpl.doList(NativeQueryImpl.java:173)
    at org.hibernate.query.internal.AbstractProducedQuery.list(AbstractProducedQuery.java:1533)
    ... 97 more
Caused by: java.sql.SQLSyntaxErrorException: ORA-00903: 无效表名

原因分析

setParameter仅能用于绑定SQL查询中的值参数(比如WHERE col = ?里的条件值),无法用来替换表名、列名这类SQL结构元素。当用:tabella作为参数时,Hibernate会将其当作字符串字面量处理,最终生成的SQL会变成类似FROM 'schema.table' t的形式,Oracle会把带引号的字符串识别为普通文本而非表名,因此抛出"无效表名"错误。同理,:colonna参数也会被当作字符串,导致GROUP BY和SELECT子句出现语法问题。

修复方案

通过字符串拼接动态生成表名和列名,同时必须对传入的database、tabella、colonna参数做合法性校验,防止SQL注入风险。

修改后的代码示例:

public Map<String, Integer> ricavaDoppioniDiUnaTabellaNSNBroker(String database, String tabella, String colonna) throws PersistenceException {
    Map<String, Integer> doppioniNSN = null;
    try {
        // 参数合法性校验,拦截非法标识符防止SQL注入
        validateIdentifier(database);
        validateIdentifier(tabella);
        validateIdentifier(colonna);
        
        String fullTableName = database + "." + tabella;
        String qualifiedCol = "t." + colonna;
        
        List<Object[]> objList = (List<Object[]>) entityManager.createNativeQuery(
                        "SELECT * FROM " +
                                "(SELECT " + qualifiedCol + ", COUNT(*) AS occorrenza FROM " + fullTableName + " t GROUP BY " + qualifiedCol + ") a " +
                                "WHERE a.occorrenza > 1")
                .getResultList();

        if (objList != null) {
            doppioniNSN = new HashMap<>();
            for (Object[] elem : objList) {
                String nsn_id = elem[0].toString();
                Integer quantity = Integer.valueOf(elem[1].toString());
                doppioniNSN.put(nsn_id, quantity);
            }
        }
    } catch (PersistenceException e) {
        brkLog.error("统计重复数据时执行查询出错。以下是部分栈跟踪信息...");
        throw e;
    }
    return doppioniNSN;
}

// 自定义标识符校验方法,符合Oracle标识符规则
private void validateIdentifier(String identifier) {
    // Oracle合法标识符:以字母/下划线/#/$开头,后续可跟字母、数字、下划线/#/$
    if (!identifier.matches("[A-Za-z_#$][A-Za-z0-9_#$]*")) {
        throw new IllegalArgumentException("非法标识符: " + identifier);
    }
}

补充说明

  • 如果需要支持带特殊字符的标识符(比如含空格),可以用双引号包裹表名和列名,例如将fullTableName改为\"" + fullTableName + "\",但Oracle会区分大小写,需确保数据库对象名与代码中大小写一致。
  • 动态生成SQL必须严格做好注入防护,避免传入恶意参数破坏数据库或泄露数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 09:57:48