我有以下问题 - 我需要从数据库中选择聚合(摘要)数据。将会有很多过滤器会动态更改,并且这种过滤功能已经使用我想使用的 Spring Data JPA 规范支持和实现。
我想优化实际的解决方案,该解决方案有效但消耗大量内存。我需要总结所有用户的工资、 child 的数量,并根据规范得到他们的平均年龄。我有这样的方法
public List<User> findAll(Specification<User> userSpecification)
我可以检索匹配过滤条件的所有用户的列表,并对它们进行迭代,并在 Java 中聚合所需的值,这是完美的。但如果获取的用户数量太大,则会消耗大量内存,这就是问题。
在我看来,最好将这些值聚合到数据库中,并仅从数据库返回 3 个数字到 Java -> 求和用户的工资、所有 child 的数量以及所选用户的平均年龄。
有人可以帮我什么是最佳解决方案吗?或者有没有办法将 JPA 规范与仅将聚合数据从 DB 返回到 Java 相结合?
非常感谢。
最佳答案
正如您所知,您不能将自定义查询与规范结合起来。可以通过 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...
}
您可以执行以下操作(我对您的 User 实体做了一些假设...):
更新:请注意,UserDetailsFacade
类名的“Facade”部分指的是《分析模式》(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_
关于java - 使用 JPA 规范过滤优化聚合值选择,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60102136/