Considere las siguientes entidades:
class Team { private String name; @OneToMany private Set<Employee> employees; } class Employee { private String name; @OneToMany private Set<Skill> skills; } class Skill { private String name; private boolean active; private Date expiryDate; }Necesito ordenar el conjunto de resultados de Teams de modo que el equipo con el máximo de habilidades activas y vigentes sea el primero. Estoy usando Spring Boot Specification & CriteriaQuery para filtrar equipos en diferentes campos. Hasta ahora tengo el siguiente código que no funciona como se esperaba.
public class TeamSpecs implements Specification<Team> { @Override public Predicate toPredicate(Root<Team> root, CriteriaQuery<?> cq, CriteriaBuilder cb) { Order o = cb.desc(cb.sum(cb.size(root.join("employees").get("skills"))); cq.orderBy(o)); return cb.like(cb.equal(root.get("name"), "%" + value + "%")); } }¿Algo que me esté perdiendo aquí? Por favor recomiende
Para que esto funcione, primero debe unir sus tablas, luego filtrar las entradas, agruparlas y luego ordenarlas.
Entonces su consulta SQL debería verse así:
select team.* from Team team inner join employee inner join skill where skill.active = true and skill.expiryDate > today group by team.name order by count(skill.name) desc Nota al margen :
Usar una Specification en este caso no es lo que desea hacer, porque no representan una Consulta completa, sino una declaración o parte de una consulta, que se usa en múltiples consultas.
Usando la consulta de criterios JPA:
public List<Team> getTeamsWithActiveSkills() { CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Team> cq = cb.createQuery(Team.class); Root<Team> root = cq.from(Team.class); Join<Team, Employee> employees = root.join("employees"); Join<Team, Skill> skills = employees.join("skills"); Predicate isActive = cb.isTrue(skills.get("active")); Predicate isNonExpired = cb.greaterThan(skills.get("expiryDate"), LocalDate.now()); cq.where(isActive, isNonExpired).groupBy(root.get("name")); Order order = cb.desc(cb.count(skills)); cq.orderBy(order); return em.createQuery(cq).getResultList(); }Dado que personalmente considero que la consulta de criterios es difícil de leer y poco intuitiva, podría usar Querydsl como alternativa.
Usando Querydsl:
public List<Team> getTeamsWithActiveSkills() { QTeam team = QTeam.team; QEmployee employee = QEmployee.employee; QSkill skill = QSkill.skill; JPQLQuery<Team> query = from(team).join(team.employees, employee).join(employee.skills, skill); query = teamJPQLQuery.where(skill.active.isTrue().and(skill.expiryDate.gt(LocalDate.now()))); query = query .groupBy(team.name); query = query .orderBy(skill.name.count().desc()); return query.fetch(); }