如何为多个实体编写单个 jpa 规范
how to write a single jpa specification for multiple entities
我正在开发 Spring Boot - 具有多个实体的应用程序,这些实体具有一些相同的过滤列。
目前,我在多个存储库中定义了相同的查询,所以在做了一些研究之后,我偶然发现了一篇关于 JPA 的文章 - 规范:https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
所以我制作了一个通用的 class 来构建 JPA 规范:
public final class GenericSpecifications<T>
{
public Specification whereNameLikeAndDateGreatherThan(String fieldName, String fieldDate, String name, LocalDate date)
{
return (root, query, builder) -> builder.lessThan(root.get(columnName), date);
}
}
所以在服务中我可以使用:
repository.findAll(whereNameLikeAndDateGreatherThan(Person_.name, Person_.date, "Max", LocalDate.now());
通过这种方式,我在中央位置有一个 query/specification,我不需要 write/maintain 对所有存储库进行相同的查询。
但是,我有更复杂的查询,我需要在其中过滤多个列。
这意味着我的方法在我的 GenericSpecification-Class 中变得过于臃肿,因为我需要传递多个列名和搜索值,所以我最终可能会得到带有 6 个或更多参数的方法。
我可以定义一个由所有其他 entities.This 扩展的抽象实体 class 抽象实体将具有所有公共字段,以确保所有实体都具有相同的列。
然后我可以使用这些名称进行过滤,所以我根本不必通过 field/coulmn-names。
但是,我不确定这是否是解决我的问题的最干净的方法。
你知道有没有更好的方法来做到这一点?
我认为最干净的方法是使用继承,但在规范创建者中,而不是在实体中。因此,例如类似的东西(没有尝试编译,所以它可能不会,但应该给出想法):
class BasicSpecificationBuilder<T> {
public Specification<T> stringEqual(String fieldName, String value) {
// root is Root<T> here, don't know if this needs to be specified
return (root, query, builder) ->
builder.equal(root.<String>get(fieldName), value);
}
}
public Specification<T> dateAfter(String fieldName, LocalDate value) {
return (root, query, builder) ->
builder.<LocalDate>greaterThan(root.get(fieldName), value);
}
}
// extend per entity type and required queries
class ContractSpecificationBuilder<Contract> extends BasicSpecificationBuilder<Contract> {
public Specification<Contract> contractsCreatedAfter(String partner, LocalDate date) {
return (root, query, builder) ->
stringEqual(Contract_.partnerName, partner)
.and(
dateAfter(Contract_.closeDate, date));
}
}
class EmployeeSpecificationBuilder<Employee> extends BasicSpecificationBuilder<Employee> {
public Specification<Employee> employeesJoinedAfter(String name, LocalDate date) {
return (root, query, builder) ->
stringEqual(Employee_.name, name)
.and(
dateAfter(Employee_.entryDate, date));
}
}
通过这种方式,您可以在基础 class 中拥有一组构建器方法,您可以重复使用这些方法,并且查询不会爆炸,因为它们是按实体分开的。上面的示例中可能会有一些代码重复 - 如果重复的代码太多,您可以将这些常见组合重构到基础 class.
中
class BasicSpecificationBuilder<T> {
public Specification<T> stringEqualAndDateAfter(String stringField, String stringValue, String dateField, LocalDate dateValue) {
public Specification<Employee> employeesJoinedAfter(String name, LocalDate date) {
return (root, query, builder) ->
stringEqual(stringField, name)
.and(
dateAfter(dateField, date));
}
}
class ContractSpecificationBuilder<Contract> extends BasicSpecificationBuilder<Contract> {
public Specification<Contract> contractsCreatedAfter(String partner, LocalDate date) {
return stringEqualAndDateAfter(Contract_.partnerName, partner, Contract_.closeDate, date);
}
}
这是品味和代码质量设置的问题(我们在 SonarQube 中有一个代码重复措施有一个限制,但我认为这不会超过限制)。
因为这些都是工厂方法,您可以用 classes 提供静态方法和包含基本方法的“基础”class 做几乎相同的事情作为静态实用方法。不过,我有点不喜欢通用静态方法的语法。
以上都是假设您阅读了 Baeldung intro on how to use Specification 并且不喜欢这种方法。
我正在开发 Spring Boot - 具有多个实体的应用程序,这些实体具有一些相同的过滤列。
目前,我在多个存储库中定义了相同的查询,所以在做了一些研究之后,我偶然发现了一篇关于 JPA 的文章 - 规范:https://spring.io/blog/2011/04/26/advanced-spring-data-jpa-specifications-and-querydsl/
所以我制作了一个通用的 class 来构建 JPA 规范:
public final class GenericSpecifications<T>
{
public Specification whereNameLikeAndDateGreatherThan(String fieldName, String fieldDate, String name, LocalDate date)
{
return (root, query, builder) -> builder.lessThan(root.get(columnName), date);
}
}
所以在服务中我可以使用:
repository.findAll(whereNameLikeAndDateGreatherThan(Person_.name, Person_.date, "Max", LocalDate.now());
通过这种方式,我在中央位置有一个 query/specification,我不需要 write/maintain 对所有存储库进行相同的查询。
但是,我有更复杂的查询,我需要在其中过滤多个列。 这意味着我的方法在我的 GenericSpecification-Class 中变得过于臃肿,因为我需要传递多个列名和搜索值,所以我最终可能会得到带有 6 个或更多参数的方法。
我可以定义一个由所有其他 entities.This 扩展的抽象实体 class 抽象实体将具有所有公共字段,以确保所有实体都具有相同的列。 然后我可以使用这些名称进行过滤,所以我根本不必通过 field/coulmn-names。
但是,我不确定这是否是解决我的问题的最干净的方法。 你知道有没有更好的方法来做到这一点?
我认为最干净的方法是使用继承,但在规范创建者中,而不是在实体中。因此,例如类似的东西(没有尝试编译,所以它可能不会,但应该给出想法):
class BasicSpecificationBuilder<T> {
public Specification<T> stringEqual(String fieldName, String value) {
// root is Root<T> here, don't know if this needs to be specified
return (root, query, builder) ->
builder.equal(root.<String>get(fieldName), value);
}
}
public Specification<T> dateAfter(String fieldName, LocalDate value) {
return (root, query, builder) ->
builder.<LocalDate>greaterThan(root.get(fieldName), value);
}
}
// extend per entity type and required queries
class ContractSpecificationBuilder<Contract> extends BasicSpecificationBuilder<Contract> {
public Specification<Contract> contractsCreatedAfter(String partner, LocalDate date) {
return (root, query, builder) ->
stringEqual(Contract_.partnerName, partner)
.and(
dateAfter(Contract_.closeDate, date));
}
}
class EmployeeSpecificationBuilder<Employee> extends BasicSpecificationBuilder<Employee> {
public Specification<Employee> employeesJoinedAfter(String name, LocalDate date) {
return (root, query, builder) ->
stringEqual(Employee_.name, name)
.and(
dateAfter(Employee_.entryDate, date));
}
}
通过这种方式,您可以在基础 class 中拥有一组构建器方法,您可以重复使用这些方法,并且查询不会爆炸,因为它们是按实体分开的。上面的示例中可能会有一些代码重复 - 如果重复的代码太多,您可以将这些常见组合重构到基础 class.
中class BasicSpecificationBuilder<T> {
public Specification<T> stringEqualAndDateAfter(String stringField, String stringValue, String dateField, LocalDate dateValue) {
public Specification<Employee> employeesJoinedAfter(String name, LocalDate date) {
return (root, query, builder) ->
stringEqual(stringField, name)
.and(
dateAfter(dateField, date));
}
}
class ContractSpecificationBuilder<Contract> extends BasicSpecificationBuilder<Contract> {
public Specification<Contract> contractsCreatedAfter(String partner, LocalDate date) {
return stringEqualAndDateAfter(Contract_.partnerName, partner, Contract_.closeDate, date);
}
}
这是品味和代码质量设置的问题(我们在 SonarQube 中有一个代码重复措施有一个限制,但我认为这不会超过限制)。
因为这些都是工厂方法,您可以用 classes 提供静态方法和包含基本方法的“基础”class 做几乎相同的事情作为静态实用方法。不过,我有点不喜欢通用静态方法的语法。
以上都是假设您阅读了 Baeldung intro on how to use Specification 并且不喜欢这种方法。