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

使用JOOQ生成原生查询并通过Hibernate映射时遇别名重复错误咨询

问题:JOOQ生成原生SQL交给Hibernate映射时出现重复别名异常

问题重现

想用JOOQ生成原生SQL,再通过Hibernate完成对象映射。但即便生成的SQL和Hibernate开启print-sql: true输出的HQL对应SQL完全一致,还是触发错误:

org.hibernate.loader.NonUniqueDiscoveredSqlAliasException: Encountered a duplicated sql alias [id] during auto-discovery of a native-sql query

HQL查询输出参考

// HQL查询示例
val hql = entityManager.createQuery("SELECT p FROM Parent p JOIN FETCH p.children WHERE p.id = ?1", Parent::class.java)

// Hibernate打印的SQL:
// select ce1_0.id, <other columns>, ce2_0.id, <other columns> from parent ce1_0 join children ce2_0 on ce1_0.id=ce2_0.parent_id where ce1_0.id=?

直接复用SQL或JOOQ生成SQL的错误场景

  1. 直接复制Hibernate打印的SQL执行:
val query = em.createNativeQuery(copiedSql, Parent::class.java)
  1. JOOQ生成同结构SQL执行:
val ce0 = Parents.PARENTS.`as`("ce1_0")
val ce1 = Children.CHILDREN.`as`("ce2_0")

val jooqQuery = 
  dsl.select(*ce0.fields(), *ce1.fields())
  .from(ce0)
  .join(ce1)
  .on(ce0.ID.eq(ce1.ID)) // 注:此处疑似逻辑错误,应该是ce0.ID.eq(ce1.PARENT_ID)
  .where(ce0.ID.eq(id))

val query = entityManager.createNativeQuery(jooqQuery.sql, Parent::class.java)

以上两种方式都会触发重复别名异常。

原因分析

Hibernate处理HQL时,内部会对关联查询的重复列名(比如parent.id和children.id)做隐式处理——生成的SQL表面有重复的id别名,但Hibernate自己能区分对应的实体属性。

但使用createNativeQuery时,Hibernate的自动别名发现机制无法识别重复列名对应的不同实体属性,因此抛出重复别名异常。

解决方案

核心思路是给重复的列名指定唯一别名,并明确映射到实体属性。

方案1:JOOQ生成带唯一别名的SQL,配合@SqlResultSetMapping

步骤1:修改JOOQ查询,给重复列加唯一别名

val ce0 = Parents.PARENTS.`as`("ce1_0")
val ce1 = Children.CHILDREN.`as`("ce2_0")

val jooqQuery = 
  dsl.select(
    // Parent表字段加前缀别名
    ce0.ID.`as`("parent_id"),
    ce0.NAME.`as`("parent_name"),
    // 其他Parent字段同理...
    
    // Child表字段加前缀别名
    ce1.ID.`as`("child_id"),
    ce1.NAME.`as`("child_name"),
    ce1.PARENT_ID.`as`("child_parent_id"),
    // 其他Child字段同理...
  )
  .from(ce0)
  .join(ce1)
  .on(ce0.ID.eq(ce1.PARENT_ID)) // 修正关联条件
  .where(ce0.ID.eq(id))

步骤2:定义@SqlResultSetMapping映射规则

在实体类或单独的映射文件中定义结果集映射:

@SqlResultSetMapping(
    name = "ParentWithChildrenMapping",
    entities = {
        @EntityResult(
            entityClass = Parent.class,
            fields = {
                @FieldResult(name = "id", column = "parent_id"),
                @FieldResult(name = "name", column = "parent_name")
                // 其他Parent属性对应别名...
            }
        ),
        @EntityResult(
            entityClass = Child.class,
            fields = {
                @FieldResult(name = "id", column = "child_id"),
                @FieldResult(name = "name", column = "child_name"),
                @FieldResult(name = "parent", column = "parent_id") // 关联Parent实体
                // 其他Child属性对应别名...
            }
        )
    }
)

步骤3:查询时指定映射名称

val query = em.createNativeQuery(jooqQuery.sql, "ParentWithChildrenMapping")
val result = query.getResultList()

方案2:手动添加标量映射并组装实体

如果不想用@SqlResultSetMapping,可以通过Hibernate NativeQuery API手动指定列的类型和别名,再手动组装实体:

val query = em.createNativeQuery(jooqQuery.sql)
// 给每个列指定类型
query.addScalar("parent_id", LongType.INSTANCE)
query.addScalar("parent_name", StringType.INSTANCE)
query.addScalar("child_id", LongType.INSTANCE)
query.addScalar("child_name", StringType.INSTANCE)

// 获取结果并手动组装Parent和Child实体
val tuples = query.getResultList() as List<Tuple>
val parentMap = mutableMapOf<Long, Parent>()

tuples.forEach { tuple ->
    val parentId = tuple.get("parent_id") as Long
    var parent = parentMap[parentId]
    if (parent == null) {
        parent = Parent().apply {
            id = parentId
            name = tuple.get("parent_name") as String
            children = mutableListOf()
        }
        parentMap[parentId] = parent
    }
    
    val child = Child().apply {
        id = tuple.get("child_id") as Long
        name = tuple.get("child_name") as String
        parent = parent
    }
    parent.children.add(child)
}

val result = parentMap.values.toList()

额外提示

注意修正JOOQ查询中的关联条件:原代码ce0.ID.eq(ce1.ID)是关联id相同的记录,不符合父子关联逻辑,应该改为ce0.ID.eq(ce1.PARENT_ID)。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 06:27:54