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

升级Hibernate 6后递归原生SQL查询出现NonUniqueDiscoveredSqlAliasException

问题描述

将基于spring-boot-starter-parent的Spring Boot应用从2.6.1版本升级至3.4.1,同步把Hibernate从5.x版本升级到6.6.4.Final,搭配PostgreSQL数据库后,某个测试用例抛出异常:

org.hibernate.loader.NonUniqueDiscoveredSqlAliasException: Encountered a duplicated sql alias [id_number] during auto-discovery of a native-sql query
    at org.hibernate.query.results.ResultSetMappingImpl.resolve(ResultSetMappingImpl.java:289)
    at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.resolveJdbcValuesSource(JdbcSelectExecutorStandardImpl.java:340)
    ...

该异常关联的原生递归SQL查询代码如下:

@Override
public List<ParentNodeChieldTreeDTOInterface> findAllSubNodesRecursively(NodeInterface node) {
    try {
        Session session = getSessionFactory().openSession();
        session.beginTransaction();

        String sql_query = 
                        " WITH RECURSIVE tree AS "
                      + "("
                      + " SELECT id, id_number, node_type_id, cast (array[] as BIGINT[]) AS ancestors"
                      + " FROM node" 
                                 
                      + " UNION ALL"
                                 
                      + " SELECT node.id, node.id_number, node.node_type_id, tree.ancestors || node.parent_id_number"
                      + " FROM node, tree"
                      + " WHERE node.parent_id_number = tree.id_number"
                      + ") "
                      + "SELECT DISTINCT ON (id_number) tree.id_number, * FROM tree WHERE (:Argument1 = ANY(tree.ancestors) )"
                      + "  ORDER BY tree.id_number" 
                      ;
        
        @SuppressWarnings("unchecked")
        Query<Object[]> query = session.createNativeQuery(sql_query)
                .addScalar("id", byte[].class)
                .addScalar("id_number", Long.class)
                .addScalar("node_type_id", Integer.class)
                .addScalar("ancestors",  LongArrayType.INSTANCE)
                ;
        
        Long nodeIdHex = node.getIdNumber();
        query.setParameter("Argument1", nodeIdHex, Long.class);
        
        List<Object[]> objectCollection = (List<Object[]>) query.list();
        
        List<ParentNodeChieldTreeDTOInterface> parentNodeChieldTreeDTOList = new ArrayList<ParentNodeChieldTreeDTOInterface>();
        if (objectCollection.iterator().hasNext()) {
            for (Object[] objects : objectCollection) {
                byte[] nodeUUIDArray = (byte[]) objects[0];
                long nodeIdNumber = (long) objects[1];
                int nodeTypeIdNumber = (int) objects[2];
                Object parentNodes =  objects[3];
                long[] parents =  (long[]) parentNodes;
                
                ParentNodeChieldTreeDTOInterface dtoObject = new ParentNodeChieldTreeDTO(
                        nodeUUIDArray, 
                        nodeIdNumber, 
                        nodeTypeIdNumber, 
                        parents
                        );
                
                if(logger.isDebugEnabled()) {
                    ByteBuffer buffer = ByteBuffer.wrap(nodeUUIDArray);
                    UUID nodeUUID = ByteBufferOperations.byteBufferToUUID(buffer);
                    
                    logger.debug("Cheild Node UUID       : " + nodeUUID.toString() );
                    logger.debug("Parent Nodes           : " + parents);
                    logger.debug("Cheild Node            : " + nodeUUIDArray);
                }
                parentNodeChieldTreeDTOList.add(dtoObject);
            }
        }
        session.getTransaction().commit();
        session.close();
        return parentNodeChieldTreeDTOList;
    } catch (Exception e) {
        logger.error("Exception", e);
        throw e;
    }
}

这段代码在Hibernate 5环境下运行正常。异常核心原因是:最终查询语句SELECT DISTINCT ON (id_number) tree.id_number, * FROM tree返回的结果集中,tree.id_number和*展开后的tree.id_number重复,PostgreSQL会生成两个同名列;而Hibernate 6对原生SQL查询的重复别名校验比Hibernate 5严格,因此触发异常。

PostgreSQL中node表的核心结构如下:

# \d node;
                                             Table "public.node"
      Column      |           Type           | Collation | Nullable |                 Default                 
------------------+--------------------------+-----------+----------+-----------------------------------------
...
 node_type_id     | integer                  |           | not null | 
 id_number        | bigint                   |           | not null | nextval('node_id_number_seq'::regclass)
 parent_id_number | bigint                   |           |          | 
 id               | bytea                    |           | not null | 
 parent_id        | bytea                    |           |          | 
... 
解决方法

方案1:修改SQL查询语句,消除重复列(推荐)

直接修正SQL的重复列问题,这是最彻底的解决方式:

方式1.1:移除重复的tree.id_number

因为DISTINCT ON (id_number)已经基于id_number去重,而*已经包含该列,所以可以直接去掉前面的tree.id_number:

WITH RECURSIVE tree AS (
  SELECT id, id_number, node_type_id, cast (array[] as BIGINT[]) AS ancestors
  FROM node 
  UNION ALL
  SELECT node.id, node.id_number, node.node_type_id, tree.ancestors || node.parent_id_number
  FROM node, tree
  WHERE node.parent_id_number = tree.id_number
) 
SELECT DISTINCT ON (id_number) * FROM tree WHERE (:Argument1 = ANY(tree.ancestors) )
ORDER BY tree.id_number

方式1.2:明确列出所有需要的列

如果需要严格控制返回列,避免*带来的隐性问题,可以直接列出所有需要的字段:

WITH RECURSIVE tree AS (
  SELECT id, id_number, node_type_id, cast (array[] as BIGINT[]) AS ancestors
  FROM node 
  UNION ALL
  SELECT node.id, node.id_number, node.node_type_id, tree.ancestors || node.parent_id_number
  FROM node, tree
  WHERE node.parent_id_number = tree.id_number
) 
SELECT DISTINCT ON (id_number) id, id_number, node_type_id, ancestors FROM tree WHERE (:Argument1 = ANY(tree.ancestors) )
ORDER BY id_number

方案2:使用显式结果集映射

如果无法修改SQL,可以通过Hibernate的结果集映射为重复列指定不同别名,不过这种方式相对繁琐:

  1. 定义结果集映射:
@SqlResultSetMapping(
    name = "TreeResultMapping",
    columns = {
        @ColumnResult(name = "id", type = byte[].class),
        @ColumnResult(name = "id_number_1", type = Long.class),
        @ColumnResult(name = "id_number_2", type = Long.class),
        @ColumnResult(name = "node_type_id", type = Integer.class),
        @ColumnResult(name = "ancestors", type = Long[].class)
    }
)
  1. 修改SQL为重复列指定别名:
WITH RECURSIVE tree AS (
  SELECT id, id_number, node_type_id, cast (array[] as BIGINT[]) AS ancestors
  FROM node 
  UNION ALL
  SELECT node.id, node.id_number, node.node_type_id, tree.ancestors || node.parent_id_number
  FROM node, tree
  WHERE node.parent_id_number = tree.id_number
) 
SELECT DISTINCT ON (id_number) tree.id_number AS id_number_1, tree.* FROM tree WHERE (:Argument1 = ANY(tree.ancestors) )
ORDER BY tree.id_number
  1. 创建查询时引用该映射:
Query<Object[]> query = session.createNativeQuery(sql_query, "TreeResultMapping");

方案3:关闭Hibernate 6的重复别名校验(不推荐)

通过配置参数关闭重复别名的校验,仅作为临时绕过方案,可能引发后续隐性问题:
在application.properties中添加:

spring.jpa.properties.hibernate.query.native_query.allow_duplicate_scalar_aliases=true

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 05:24:51