在 JPA Criteria API 之上构建更简洁的投影层
Source: Dev.to
介绍
如果你使用 Java 和 JPA 开发系统,可能已经遇到过只需要返回给定实体的部分属性的查询需求。
乍一看这似乎很简单,但如果处理不当,系统可能会累积不必要的查询,或出现包含永远不会使用的属性的查询。
在许多真实场景中,开发者只需要获取实体的 id 和 name。由于系统规模和复杂度,往往难以判断是否已经存在返回恰好这些数据的查询。还有的情况下,开发者会重复使用加载整个实体的方法,随后再提取实际需要的少量属性。
ProjectionQuery 的创建旨在简化和组织基于投影的查询,提供一种更清晰、更具表达力的方式,只选择应用真正需要的数据。
定义投影
在项目中添加依赖后,创建一个类(或 record),并为需要投影的字段加上注解。
@Projection(of = Customer.class)
public record CustomerBasicData(
@ProjectionField Long id,
@ProjectionField String name,
@ProjectionField("address.city.name") String city,
@ProjectionField("address.city.state.name") String state
) { }
该 record 表示查询结果的最终结构。无论 Customer 实体中有多少属性,只有 id、name、city 和 state 会从数据库中被选取。
需要注意的是,city 和 state 是通过 Customer 实体中定义的关系获取的嵌套属性。
生成的 SQL(简化)
上述投影对应的 SQL 大致如下:
SELECT
c.id,
c.name,
city.name AS city,
state.name AS state
FROM customer c
INNER JOIN address a ON c.address = a.id
INNER JOIN city ci ON a.city = ci.id
INNER JOIN state s ON ci.state = s.id;
执行投影
简化执行
ProjectionProcessor processor = new ProjectionProcessor(entityManager);
List customers = processor.execute(CustomerBasicData.class);
在高级场景中使用 ProjectionQuery
ProjectionQuery 帮助构建更复杂的查询,支持添加过滤、排序、分页以及其他配置。
ProjectionProcessor processor = new ProjectionProcessor(entityManager);
ProjectionQuery query = ProjectionQuery
.fromTo(Customer.class, CustomerBasicData.class)
.filter("address.city.name", ProjectionFilterOperator.EQUAL, "São Paulo")
.order("name", OrderDirection.ASC)
.paging(0, 20)
.distinct();
List customers = processor.execute(query);
ProjectionQuery 可以独立使用,也可以集成到 Spring Boot 应用中。
更多资源
欲了解更多细节、示例以及完整文档,请访问 GitHub 上的项目页面。