使用eBean按子对象的最新消息时间排序时触发GROUP BY子句缺失的SQL异常
我之前在使用Ebean做聚合查询的时候也踩过一模一样的坑!你的问题核心是:当你在查询中使用max()这类聚合函数时,数据库的SQL规范要求所有非聚合的列(也就是SELECT语句里除了max(u1.add_date)之外的t0.id、t0.deleted这些字段)都必须出现在GROUP BY子句中,但Ebean自动生成的SQL没把这些字段加进去,所以才会抛出那个Column "T0.ID" must be in the GROUP BY list的错误。
下面给你几个可行的解决方案,按推荐程度排序:
方案一:维护冗余字段(最推荐,性能最优)
如果可以修改实体结构,最省心的办法是给MessageThread加一个lastMessageDate字段,用来存储该线程最新消息的时间。然后在MessageItem的新增/更新钩子方法里,自动同步更新这个字段:
// MessageItem实体类 @PrePersist @PreUpdate public void syncThreadLastMessage() { if (this.thread != null) { this.thread.setLastMessageDate(this.getAddDate()); Ebean.update(this.thread); } }
之后查询的时候直接按这个字段排序,完全不用聚合查询,代码简洁还没有性能问题:
List<MessageThread> threads = Ebean.find(MessageThread.class) .where() // 这里放入你的原查询条件 .or( Expr.and( Expr.in("roleId", roleIds), Expr.eq("syndicate.regionId", regionId) ), Expr.or( Expr.eq("messages.senderId", senderId), Expr.eq("messages.statuses.device.userId", userId) ) ) .orderBy("lastMessageDate desc") .setFirstRow(0) .setMaxRows(10) .findList();
方案二:手动指定GROUP BY子句
如果不想改实体,那必须手动把SELECT里的所有非聚合字段都加入GROUP BY。Ebean的Query支持groupBy()方法,你需要把MessageThread的所有SELECT字段都列进去:
List<MessageThread> threads = Ebean.find(MessageThread.class) .select("id, deleted, messageRef, sticky, lastUpdate, addDate, roleId, syndicateId, max(messages.addDate)") .groupBy("id, deleted, messageRef, sticky, lastUpdate, addDate, roleId, syndicateId") .where() // 原查询条件 .or( Expr.and( Expr.in("roleId", roleIds), Expr.eq("syndicate.regionId", regionId) ), Expr.or( Expr.eq("messages.senderId", senderId), Expr.eq("messages.statuses.device.userId", userId) ) ) .orderBy("max(messages.addDate) desc") .setFirstRow(0) .setMaxRows(10) .findList();
注意:SELECT里的非聚合列必须和GROUP BY里的完全一致,不能有遗漏,否则还是会报错。
方案三:用子查询获取最新消息时间
如果GROUP BY的字段太多写起来麻烦,还可以用子查询先拿到每个线程的最新消息时间,再关联主查询排序:
// 子查询:查询每个thread的最新消息时间 SubQuery<MessageItem> subQuery = Ebean.createSubQuery(MessageItem.class) .select("thread.id, max(addDate) as lastMessageDate") .groupBy("thread.id") .where().eq("thread.id", Expr.parentId()); // 主查询关联子查询,按子查询的时间排序 List<MessageThread> threads = Ebean.find(MessageThread.class) .select("*") .where() // 原查询条件 .or( Expr.and( Expr.in("roleId", roleIds), Expr.eq("syndicate.regionId", regionId) ), Expr.or( Expr.eq("messages.senderId", senderId), Expr.eq("messages.statuses.device.userId", userId) ) ) .having().subQuery(subQuery) .orderBy("lastMessageDate desc") .setFirstRow(0) .setMaxRows(10) .findList();
这种方式把聚合逻辑隔离在子查询里,主查询不用处理复杂的GROUP BY,可读性会好一些。
至于你之前尝试的@Aggregation注解,它在简单查询里可能好用,但一旦涉及多表关联、OR条件这类复杂场景,Ebean自动生成GROUP BY的逻辑就会失效,所以还是手动控制或者换方案更靠谱。
内容来源于stack exchange

