Как вы уже знаете, вы не можете комбинировать пользовательские запросы со спецификациями. Можно повторно использовать ваши спецификации с CriteriaBuilder
API и использовать его для запроса вашей базы данных. Предполагая следующий прогноз:
public class AggregatedUserDetails {
private long salarySum;
private double ageAverage;
private long childrenSum;
public AggregatedUserDetails(long salarySum, double ageAverage, long childrenSum) {
this.salarySum = salarySum;
this.ageAverage = ageAverage;
this.childrenSum = childrenSum;
}
// Getters & setters...
}
Вы можете сделать следующее (я сделал некоторые предположения относительно вашей сущности пользователя ...):
Обновление: обратите внимание, что «Фасад» часть имени класса UserDetailsFacade
относится к фасаду приложения из книги «Шаблоны анализа» (Fowler, 1997)
@Component
public class UserDetailsFacade {
private final EntityManager entityManager;
public UserDetailsFacade(EntityManager entityManager) {
this.entityManager = entityManager;
}
public AggregatedUserDetails aggregatedUserDetails() {
return aggregatedUserDetails(null);
}
@Transactional(readOnly = true)
public AggregatedUserDetails aggregatedUserDetails(Specification<User> userSpecification) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<AggregatedUserDetails> query = criteriaBuilder.createQuery(AggregatedUserDetails.class);
Root<User> from = query.from(User.class);
Predicate predicate = null;
if ( userSpecification != null )
predicate = userSpecification.toPredicate(from, query, criteriaBuilder);
CompoundSelection<AggregatedUserDetails> construct = criteriaBuilder.construct(
AggregatedUserDetails.class,
criteriaBuilder.sum(from.get("salary")),
criteriaBuilder.avg(from.get("age")),
criteriaBuilder.sum(from.get("children"))
);
CriteriaQuery<AggregatedUserDetails> select = query.select(construct);
if ( predicate != null )
select.where(predicate);
return entityManager.createQuery(select).getSingleResult();
}
}
, который позволит вам использовать ваши спецификации Spring, избегая при этом лишних затрат на поиск всех соответствующих сущности и вычисление значения в приложении:
@DataJpaTest
@ExtendWith(SpringExtension.class)
public class UserDetailsFacadeTest {
@TestConfiguration
public static class UserDaoTestConfiguration {
@Bean
public UserDetailsFacade getUserDao(EntityManager entityManager) {
return new UserDetailsFacade(entityManager);
}
}
@Autowired
private UserRepository userRepository;
@Autowired
private UserDetailsFacade userDetailsFacade;
@BeforeEach
public void setUp() {
User tom = new User();
User dick = new User();
User sally = new User();
tom.setAge(17);
dick.setAge(40);
sally.setAge(35);
tom.setChildren(0);
dick.setChildren(1);
sally.setChildren(0);
tom.setSalary(240);
dick.setSalary(40000);
sally.setSalary(40000);
userRepository.save(tom);
userRepository.save(dick);
userRepository.save(sally);
}
@Test
public void testGetExpectedSummedSallary() {
AggregatedUserDetails aggregatedUserDetails = userDetailsFacade.aggregatedUserDetails();
assertThat(aggregatedUserDetails.getSalarySum(), is(80240L));
}
@Test
public void testGetExpectedAverageAge() {
AggregatedUserDetails aggregatedUserDetails = userDetailsFacade.aggregatedUserDetails();
assertThat(Math.round(aggregatedUserDetails.getAgeAverage()), is(31L));
}
@Test
public void testGetExpectedSummedChildren() {
AggregatedUserDetails aggregatedUserDetails = userDetailsFacade.aggregatedUserDetails();
assertThat(Math.round(aggregatedUserDetails.getChildrenSum()), is(1));
}
@Test
public void testGetExpectedSummedSallaryOver1k() {
AggregatedUserDetails aggregatedUserDetails = userDetailsFacade.aggregatedUserDetails(
(Specification<User>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.ge(root.get("salary"), 1000));
assertThat(aggregatedUserDetails.getSalarySum(), is(80000L));
}
}
Вывод:
// com.example.demo.dao.UserDetailsFacadeTest#testGetExpectedAverageAge
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
// com.example.demo.dao.UserDetailsFacadeTest#testGetExpectedSummedChildren
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
Hibernate: select sum(user0_.salary) as col_0_0_, avg(cast(user0_.age as double)) as col_1_0_, sum(user0_.children) as col_2_0_ from user user0_
// com.example.demo.dao.UserDetailsFacadeTest#testGetExpectedSummedSallaryOver1k
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
Hibernate: select sum(user0_.salary) as col_0_0_, avg(cast(user0_.age as double)) as col_1_0_, sum(user0_.children) as col_2_0_ from user user0_ where user0_.salary>=1000
// com.example.demo.dao.UserDetailsFacadeTest#testGetExpectedSummedSallary
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
Hibernate: insert into user (id, age, children, salary) values (null, ?, ?, ?)
Hibernate: select sum(user0_.salary) as col_0_0_, avg(cast(user0_.age as double)) as col_1_0_, sum(user0_.children) as col_2_0_ from user user0_