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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 04:06:24