Java 使用Spring数据JPA进行复杂排序
我正在使用Spring数据规范和排序API(我的存储库是一个JpaSpecificationExecutor),我的数据模型看起来是这样的(稍微精简了一点) 此外,应为每条邮件对收件人进行排序-如果有多个MessageOwnerType.TO类型的messageOwner,则应选择按字母顺序排列的第一个作为邮件的排序值Java 使用Spring数据JPA进行复杂排序,java,spring,sorting,spring-data,spring-data-jpa,Java,Spring,Sorting,Spring Data,Spring Data Jpa,我正在使用Spring数据规范和排序API(我的存储库是一个JpaSpecificationExecutor),我的数据模型看起来是这样的(稍微精简了一点) 此外,应为每条邮件对收件人进行排序-如果有多个MessageOwnerType.TO类型的messageOwner,则应选择按字母顺序排列的第一个作为邮件的排序值 在纯SQL中,我可能会使用某种子查询来实现这一点。使用Spring数据规范和Sort对象是否可以实现类似的功能?我宁愿不必重写QueryDSL/raw标准中的所有内容,除非我必须
在纯SQL中,我可能会使用某种子查询来实现这一点。使用Spring数据规范和Sort对象是否可以实现类似的功能?我宁愿不必重写QueryDSL/raw标准中的所有内容,除非我必须这样做。好的,所以我想出了如何使用规范来完成它,但它并不漂亮。我不能使用Sort对象,而是使用JPA CriteriaBuilder将orderBy子句放在规范中 这导致SimpleParepository出现问题,它在所有可分页findAll查询之前执行select count()。我必须禁用此功能(在的帮助下) 所以我的说明书是这样的:(讨厌,我知道。如果有人有更好的建议,我很乐意听)
@覆盖公共谓词toPredicate(根根目录、CriteriaQuery查询、CriteriaBuilder cb){
//注意-如果JPA允许在联接中使用子查询,这将更简单,
//我们必须将子查询放在where子句中,用邮箱连接双方
//并将结果关联起来
//加入邮箱上的主查询(对于ORDER BY子句)
Join-messageOwners=root.Join(“messageOwners”,JoinType.LEFT);
加入邮箱=messageOwners.Join(“所有者”);
//创建子查询并将消息与主查询关联
Subquery Subquery=query.Subquery(String.class);
Root sqMessage=subQuery.from(Message.class);
根correlateMessage=subQuery.correlate(根);
//加入邮箱上的子查询
Join sqMessageOwners=sqMessage.Join(“messageOwners”,JoinType.LEFT);
Join sqMailbox=sqmessageowner.Join(“所有者”);
//按字母顺序获取最低的ldapId
表达式minLdapId=cb.least(sqMailbox.get(“ldapId”);
//实际子查询
//为当前邮件选择最低的ldapId(请参阅group by子句)
//其中收件人类型为“收件人”,且邮件与主查询相同
子查询.select(minLdapId)
.在哪里(
cb.及(
cb.equal(sqMessageOwners.get(“type”)、MessageOwnerType.TO),
cb.equal(sqMessage,correlateMessage)))
.groupBy(sqMessage.get(“id”);
//子查询为我们提供了每封邮件的最低收件人
//根据这些值进行排序。注意:his必须在此处完成,而不是在sort属性中完成
//在order by子句中维护邮箱之间的连接
//和子查询中的一个
路径recipientOrderClause=mailbox.get(“ldapId”);
Order recipientOrder=sortDirection==Sort.Direction.ASC?cb.ASC(recipientOrderClause):cb.desc(recipientOrderClause);
//按发送时间递减的二次排序
Order sendTimeOrder=cb.desc(root.get(“sendTime”);
//在此处添加按查询排序,而不是通过排序对象
//违反了标准的Spring数据实践(参见上文了解其原因)
//这使我们无法进行一些分组调用,例如select count(*)
query.orderBy(recipientOrder、sendTimeOrder);
//将子查询附加到查询并返回。
返回cb.equal(mailbox.get(“ldapId”),子查询);
}
您总是希望数据按该顺序排序吗?还是只排序一次?如果是前者,请在实体上添加@OrderBy。
@Entity
public class Message extends AbstractVersionedEntity {
@OneToMany
private Set<MessageOwner> messageOwners = new HashSet<>();
}
@Entity
public class MessageOwner extends AbstractVersionedEntity {
@ManyToOne
private Mailbox owner;
@Enumerated(EnumType.STRING)
private MessageOwnerType type;
}
@Entity
public class Mailbox extends AbstractVersionedEntity {
@Column(unique=true)
private String ldapId;
}
message->messageOwners(type=TO)->owner->ldapId
@Override public Predicate toPredicate(Root<Message> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
// Note - this would be simpler if JPA allowed subqueries in joins. As it doesn't,
// we have to put the subquery in a where clause, join both sides with the mailbox
// and correlate the results
// join the main query on the mailbox (for the ORDER BY clause)
Join<Message, MessageOwner> messageOwners = root.join("messageOwners", JoinType.LEFT);
Join<Message, Mailbox> mailbox = messageOwners.join("owner");
// create a subquery and correlate the messages with the main query
Subquery<String> subQuery = query.subquery(String.class);
Root<Message> sqMessage = subQuery.from(Message.class);
Root<Message> correlateMessage = subQuery.correlate(root);
// join the subquery on the mailbox
Join<Object, Object> sqMessageOwners = sqMessage.join("messageOwners", JoinType.LEFT);
Join<Message, Mailbox> sqMailbox = sqMessageOwners.join("owner");
// get the lowest ldapId alphabetically
Expression<String> minLdapId = cb.least(sqMailbox.<String>get("ldapId"));
// the actual subquery
// select the lowest ldapId for the current message (see the group-by clause)
// where the recipient type is TO and the message is the same as the main query
subQuery.select(minLdapId)
.where(
cb.and(
cb.equal(sqMessageOwners.get("type"), MessageOwnerType.TO),
cb.equal(sqMessage, correlateMessage)))
.groupBy(sqMessage.get("id"));
// the subquery gives us the lowest TO recipient for each mail
// sort on these values. Note: his must be done here rather than in the Sort property
// of the Pageable in order to maintain the connection between the mailbox in the order by clause
// and the one in the subquery
Path<String> recipientOrderClause = mailbox.get("ldapId");
Order recipientOrder = sortDirection == Sort.Direction.ASC ? cb.asc(recipientOrderClause) : cb.desc(recipientOrderClause);
// secondary sorting descending by sendTime
Order sendTimeOrder = cb.desc(root.get("sendTime"));
// adding order by queries here, rather than via the Sort object
// is something of a violation of standard Spring Data practice. (see above for the reason it is done)
// this precludes us from making some grouped calls such as select count(*)
query.orderBy(recipientOrder, sendTimeOrder);
// attach the subquery onto the query and return.
return cb.equal(mailbox.get("ldapId"), subQuery);
}