Liferay Service Builder同表自连接自定义查询异常咨询
Liferay Service Builder 自连接自定义SQL实体重复问题
问题场景
- 自定义SQL(同表自连接):
select e1.*, e2.* from entity as e1 inner join entity as e2 on e1.id=e2.parentid;
- 自定义Finder方法:
public List findMyEntityList() throws SystemException { Session session = null; try { session = openSession(); String sql = ... SQLQuery queryObject = session.createSQLQuery(sql); queryObject.setCacheable(false); queryObject.addEntity("entity1", EntityImpl.class); queryObject.addEntity("entity2", EntityImpl.class); QueryPos qPos = QueryPos.getInstance(queryObject); return (List) QueryUtil.list(queryObject, getDialect(), QueryUtil.ALL_POS, QueryUtil.ALL_POS); } catch (Exception e) { e.printStackTrace(); } finally { closeSession(session); } return null; }
- 结果读取代码:
for (Object object : this.myEntityList) { Object[] objectArray = (Object[]) object; e1 = (Entity) objectArray[0]; e2 = (Entity) objectArray[1]; ... }
问题现象
查询返回的列表大小符合预期,但e1和e2的实体值完全相同,Service Builder无法区分自连接的两个实体。
原因分析
核心问题在于Liferay Service Builder底层依赖的Hibernate,在处理同表多实体映射时,会根据结果集的列名自动匹配实体属性。但自连接返回的两部分列名完全一致(如id、parentid),导致Hibernate无法区分列归属,最终将同一列值重复映射到两个实体对象中。
解决方法
方法1:为查询列添加别名,手动映射属性
修改自定义SQL,为两个表的列添加不同前缀的别名,明确区分归属:
select e1.id as e1_id, e1.parentid as e1_parentid, e1.name as e1_name, -- 所有e1列添加e1_前缀 e2.id as e2_id, e2.parentid as e2_parentid, e2.name as e2_name -- 所有e2列添加e2_前缀 from entity as e1 inner join entity as e2 on e1.id=e2.parentid;
然后在Finder方法中使用addScalar定义列类型,再手动封装实体:
public List<Object[]> findMyEntityList() throws SystemException { Session session = null; try { session = openSession(); String sql = "上述带别名的SQL"; SQLQuery queryObject = session.createSQLQuery(sql); queryObject.setCacheable(false); // 按别名注册列类型(根据实际列类型调整) queryObject.addScalar("e1_id", StandardBasicTypes.LONG); queryObject.addScalar("e1_parentid", StandardBasicTypes.LONG); queryObject.addScalar("e1_name", StandardBasicTypes.STRING); queryObject.addScalar("e2_id", StandardBasicTypes.LONG); queryObject.addScalar("e2_parentid", StandardBasicTypes.LONG); queryObject.addScalar("e2_name", StandardBasicTypes.STRING); return (List<Object[]>) QueryUtil.list(queryObject, getDialect(), QueryUtil.ALL_POS, QueryUtil.ALL_POS); } catch (Exception e) { e.printStackTrace(); } finally { closeSession(session); } return null; }
读取结果时手动赋值:
for (Object[] objectArray : this.myEntityList) { Entity e1 = new EntityImpl(); e1.setId((Long) objectArray[0]); e1.setParentid((Long) objectArray[1]); e1.setName((String) objectArray[2]); Entity e2 = new EntityImpl(); e2.setId((Long) objectArray[3]); e2.setParentid((Long) objectArray[4]); e2.setName((String) objectArray[5]); ... }
方法2:使用HQL替代原生SQL(更贴合Service Builder习惯)
如果业务允许,用HQL实现自连接,Hibernate会自动处理实体映射:
public List<Object[]> findMyEntityList() throws SystemException { Session session = null; try { session = openSession(); String hql = "from Entity e1 inner join Entity e2 on e1.id = e2.parentid"; Query query = session.createQuery(hql); return (List<Object[]>) QueryUtil.list(query, getDialect(), QueryUtil.ALL_POS, QueryUtil.ALL_POS); } catch (Exception e) { e.printStackTrace(); } finally { closeSession(session); } return null; }
这种方式返回的Object[]中两个Entity对象会是正确的不同实例,可直接强转使用。
方法3:自定义ResultTransformer(复用映射逻辑)
如果需要复用实体映射逻辑,可自定义结果转换器处理别名到实体的映射:
public class EntityPairResultTransformer implements ResultTransformer { @Override public Object transformTuple(Object[] tuple, String[] aliases) { Entity e1 = new EntityImpl(); Entity e2 = new EntityImpl(); for (int i = 0; i < aliases.length; i++) { String alias = aliases[i]; Object value = tuple[i]; if (alias.startsWith("e1_")) { String propName = alias.substring(3); switch (propName) { case "id": e1.setId((Long) value); break; case "parentid": e1.setParentid((Long) value); break; case "name": e1.setName((String) value); break; // 其他属性同理 } } else if (alias.startsWith("e2_")) { String propName = alias.substring(3); switch (propName) { case "id": e2.setId((Long) value); break; case "parentid": e2.setParentid((Long) value); break; case "name": e2.setName((String) value); break; // 其他属性同理 } } } return new Object[]{e1, e2}; } @Override public List transformList(List collection) { return collection; } }
在Finder方法中设置转换器:
queryObject.setResultTransformer(new EntityPairResultTransformer());
总结
Liferay Service Builder可以处理同表自连接的自定义SQL,但必须保证结果集列名唯一。通过给列添加别名并手动映射,或使用HQL的方式,即可解决实体重复的问题。
内容的提问来源于stack exchange,提问作者txapeldot
相关产品推荐
相关产品推荐

