使用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的错误场景
- 直接复制Hibernate打印的SQL执行:
val query = em.createNativeQuery(copiedSql, Parent::class.java)
- 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
相关产品推荐
相关产品推荐

