Specification/Predicate 根据实例类型搜索不同字段 Spring
Specification/Predicate to search different fields based on instance type Spring
假设我有一个抽象的 class 产品,我还有两个实体用不同的字段扩展它 ProductA 和 ProductB。我正在使用产品存储库使用 Specification 和 SpecificationBuilder 使用一些公共字段搜索所有这些产品。我想要完成的是检测它是什么类型的产品(A 或 B)并基于此使用不同的搜索查询。
例如,如果我插入搜索条件数量并尝试对产品回购进行规范,那么 ProductA 有库存而 ProductB 自然具有特色,我希望 api 搜索 ProductA。这种形式的搜索有可能吗?
这是我当前的代码:
public interface ProductRepo extends JpaRepository<Product, Integer>,JpaSpecificationExecutor<Product>{
}
public class ProductSpecificationBuilder {
public final ProductSpecificationBuilder with(final String orPredicate, final String key
, final String operation, Object value, final String prefix, final String suffix) {
System.out.println("called "+value);
//Fixing boolean value
if (!key.equals("visible") || key.equals("modified")) {
System.out.println("not equal visible");
SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0));
if (op != null) {
if (op == SearchOperation.EQUALITY) { // the operation may be complex operation
final boolean startWithAsterisk = prefix != null && prefix.contains(SearchOperation.ZERO_OR_MORE_REGEX);
final boolean endWithAsterisk = suffix != null && suffix.contains(SearchOperation.ZERO_OR_MORE_REGEX);
System.out.println("prefix "+startWithAsterisk+" "+endWithAsterisk);
if (startWithAsterisk && endWithAsterisk) {
op = SearchOperation.CONTAINS;
} else if (startWithAsterisk) {
op = SearchOperation.ENDS_WITH;
} else if (endWithAsterisk) {
op = SearchOperation.STARTS_WITH;
}
}else if (op == SearchOperation.LIKE) {
System.out.println("we got like in builder");
}
params.add(new SearchCriteria(orPredicate, key, op, value));
}
}
return this;
}
}
public class ProductSpecification implements Specification<Product>{
@Override
public Predicate toPredicate(final Root<Product> root, final CriteriaQuery<?> query, final CriteriaBuilder builder) {
//TODO JOIN class for nested search for promotion
switch (criteria.getOperation()) {
case EQUALITY:
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
case NEGATION:
return builder.notEqual(root.get(criteria.getKey()), criteria.getValue());
case GREATER_THAN:
return builder.greaterThan(root.get(criteria.getKey()), criteria.getValue().toString());
case LESS_THAN:
return builder.lessThan(root.get(criteria.getKey()), criteria.getValue().toString());
case LIKE:
return builder.like(root.get(criteria.getKey()), criteria.getValue().toString());
case STARTS_WITH:
return builder.like(root.get(criteria.getKey()), criteria.getValue() + "%");
case ENDS_WITH:
return builder.like(root.get(criteria.getKey()), "%" + criteria.getValue());
case CONTAINS:
return builder.like(root.get(criteria.getKey()), "%" + criteria.getValue() + "%");
default:
return null;
}
}
}
public enum SearchOperation {
EQUALITY, NEGATION, GREATER_THAN, LESS_THAN, LIKE, STARTS_WITH, ENDS_WITH, CONTAINS;
public static final String[] SIMPLE_OPERATION_SET = { ":", "!", ">", "<", "~","@"};
public static final String OR_PREDICATE_FLAG = "'";
public static final String ZERO_OR_MORE_REGEX = "*";
public static final String OR_OPERATOR = "OR";
public static final String AND_OPERATOR = "AND";
public static final String LEFT_PARANTHESIS = "(";
public static final String RIGHT_PARANTHESIS = ")";
public static SearchOperation getSimpleOperation(final char input) {
switch (input) {
case ':':
return EQUALITY;
case '!':
return NEGATION;
case '>':
return GREATER_THAN;
case '<':
return LESS_THAN;
case '~':
return LIKE;
case '@':{
return CONTAINS;
}
default:
return null;
}
}
}
我的产品 classes
@Entity
@Table(name = "products")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
@GenericGenerator(name = "native", strategy = "native")
@Column(name = "id")
private int id;
-----Common fields----
}
public class ProductA extends Product{ int stock; }
public class ProductB extends Product{ int featured; }
如果您有任何问题,请随时提问。任何形式的帮助将不胜感激!提前谢谢你。
据我所知,没有 "nice way" 这样做(如果你想保留类型),但你可以采用以下解决方案之一:
0。 Switch case 通常我真的反对 Java
中的 switch
和 case
但它有它自己的位置......最简单的解决方案是构建你的在不同的情况下查询......它们是分开的案例(双关语不是故意的)。
从你的问题来看,我假设你的结构比所提供的结构复杂得多,所以接下来我为你提供了两种可能的解决方案。
1.程序化解决方案 您可以创建基于反射的解决方案。你可以有一个完整的解决方案来实现一些有趣的逻辑。主要是您必须拥有此功能中 "searchable" 对象的列表。在循环中,您可以使用反射来确定实体中的字段是否为 "acceptable"。 (例如 ˙stock˙ 仅在 PRODUCT_A
中可接受,因此创建标准将基于 Entity
。我绝对不推荐这种方法 因为这可能会带来很多问题。(例如,如果 stock
和 featured
在您的 DTO 中同时设置,您将如何处理?
2。创建一个视图来构造您的搜索 基本上您在 Spring 中创建一个数据库视图和一个具有所有可能参数的实体。
原始表格:
|TABLE_A | |TABLE_B |
|ID|FIELD_A| |ID|FIELD_B|
|__________| |__________|
| 1|EXAMPLE| | 1| STUFF|
查看:
|COMBINED_VIEW |
|ID|FIELD_A|FIELD_B|ORIG_TABLE|
|_____________________________|
| 1|EXAMPLE| null| TABLE_A|
| 1| null| STUFF| TABLE_B|
创作:
SELECT
ID as id, FIELD_A as field_a, null as field_b, 'TABLE_A' as ORIG_TABLE
FROM TABLE_A
UNION ALL
SELECT
ID as id, null as field_a, FIELD_B as field_b, 'TABLE_B' as ORIG_TABLE
FROM TABLE_B
不过要小心,如果您使用缓存、添加生成的 ID 或将 ORIG_TABLE
包含到您的 @Id
映射中,ID 字段可能会让您感到困惑。
TL.DR.: 第二种方法你可以映射你需要的所有东西并且(在我看来)它是获得结果的可接受方式。如果设置的问题参数多于单个 entity
可能设置的问题参数,那么您也不必考虑这一点。 (例如:FIELD_A='EXAMPLE' AND FIELD_B='STUFF'
根本就没有结果。)虽然还有其他方法可以实现这一点,例如使用 JPQL
和 left join 以及 constructor mapping 我认为第二个选项是最清晰和最可维护的。
假设我有一个抽象的 class 产品,我还有两个实体用不同的字段扩展它 ProductA 和 ProductB。我正在使用产品存储库使用 Specification 和 SpecificationBuilder 使用一些公共字段搜索所有这些产品。我想要完成的是检测它是什么类型的产品(A 或 B)并基于此使用不同的搜索查询。
例如,如果我插入搜索条件数量并尝试对产品回购进行规范,那么 ProductA 有库存而 ProductB 自然具有特色,我希望 api 搜索 ProductA。这种形式的搜索有可能吗? 这是我当前的代码:
public interface ProductRepo extends JpaRepository<Product, Integer>,JpaSpecificationExecutor<Product>{
}
public class ProductSpecificationBuilder {
public final ProductSpecificationBuilder with(final String orPredicate, final String key
, final String operation, Object value, final String prefix, final String suffix) {
System.out.println("called "+value);
//Fixing boolean value
if (!key.equals("visible") || key.equals("modified")) {
System.out.println("not equal visible");
SearchOperation op = SearchOperation.getSimpleOperation(operation.charAt(0));
if (op != null) {
if (op == SearchOperation.EQUALITY) { // the operation may be complex operation
final boolean startWithAsterisk = prefix != null && prefix.contains(SearchOperation.ZERO_OR_MORE_REGEX);
final boolean endWithAsterisk = suffix != null && suffix.contains(SearchOperation.ZERO_OR_MORE_REGEX);
System.out.println("prefix "+startWithAsterisk+" "+endWithAsterisk);
if (startWithAsterisk && endWithAsterisk) {
op = SearchOperation.CONTAINS;
} else if (startWithAsterisk) {
op = SearchOperation.ENDS_WITH;
} else if (endWithAsterisk) {
op = SearchOperation.STARTS_WITH;
}
}else if (op == SearchOperation.LIKE) {
System.out.println("we got like in builder");
}
params.add(new SearchCriteria(orPredicate, key, op, value));
}
}
return this;
}
}
public class ProductSpecification implements Specification<Product>{
@Override
public Predicate toPredicate(final Root<Product> root, final CriteriaQuery<?> query, final CriteriaBuilder builder) {
//TODO JOIN class for nested search for promotion
switch (criteria.getOperation()) {
case EQUALITY:
return builder.equal(root.get(criteria.getKey()), criteria.getValue());
case NEGATION:
return builder.notEqual(root.get(criteria.getKey()), criteria.getValue());
case GREATER_THAN:
return builder.greaterThan(root.get(criteria.getKey()), criteria.getValue().toString());
case LESS_THAN:
return builder.lessThan(root.get(criteria.getKey()), criteria.getValue().toString());
case LIKE:
return builder.like(root.get(criteria.getKey()), criteria.getValue().toString());
case STARTS_WITH:
return builder.like(root.get(criteria.getKey()), criteria.getValue() + "%");
case ENDS_WITH:
return builder.like(root.get(criteria.getKey()), "%" + criteria.getValue());
case CONTAINS:
return builder.like(root.get(criteria.getKey()), "%" + criteria.getValue() + "%");
default:
return null;
}
}
}
public enum SearchOperation {
EQUALITY, NEGATION, GREATER_THAN, LESS_THAN, LIKE, STARTS_WITH, ENDS_WITH, CONTAINS;
public static final String[] SIMPLE_OPERATION_SET = { ":", "!", ">", "<", "~","@"};
public static final String OR_PREDICATE_FLAG = "'";
public static final String ZERO_OR_MORE_REGEX = "*";
public static final String OR_OPERATOR = "OR";
public static final String AND_OPERATOR = "AND";
public static final String LEFT_PARANTHESIS = "(";
public static final String RIGHT_PARANTHESIS = ")";
public static SearchOperation getSimpleOperation(final char input) {
switch (input) {
case ':':
return EQUALITY;
case '!':
return NEGATION;
case '>':
return GREATER_THAN;
case '<':
return LESS_THAN;
case '~':
return LIKE;
case '@':{
return CONTAINS;
}
default:
return null;
}
}
}
我的产品 classes
@Entity
@Table(name = "products")
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Product {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
@GenericGenerator(name = "native", strategy = "native")
@Column(name = "id")
private int id;
-----Common fields----
}
public class ProductA extends Product{ int stock; }
public class ProductB extends Product{ int featured; }
如果您有任何问题,请随时提问。任何形式的帮助将不胜感激!提前谢谢你。
据我所知,没有 "nice way" 这样做(如果你想保留类型),但你可以采用以下解决方案之一:
0。 Switch case 通常我真的反对 Java
中的 switch
和 case
但它有它自己的位置......最简单的解决方案是构建你的在不同的情况下查询......它们是分开的案例(双关语不是故意的)。
从你的问题来看,我假设你的结构比所提供的结构复杂得多,所以接下来我为你提供了两种可能的解决方案。
1.程序化解决方案 您可以创建基于反射的解决方案。你可以有一个完整的解决方案来实现一些有趣的逻辑。主要是您必须拥有此功能中 "searchable" 对象的列表。在循环中,您可以使用反射来确定实体中的字段是否为 "acceptable"。 (例如 ˙stock˙ 仅在 PRODUCT_A
中可接受,因此创建标准将基于 Entity
。我绝对不推荐这种方法 因为这可能会带来很多问题。(例如,如果 stock
和 featured
在您的 DTO 中同时设置,您将如何处理?
2。创建一个视图来构造您的搜索 基本上您在 Spring 中创建一个数据库视图和一个具有所有可能参数的实体。
原始表格:
|TABLE_A | |TABLE_B |
|ID|FIELD_A| |ID|FIELD_B|
|__________| |__________|
| 1|EXAMPLE| | 1| STUFF|
查看:
|COMBINED_VIEW |
|ID|FIELD_A|FIELD_B|ORIG_TABLE|
|_____________________________|
| 1|EXAMPLE| null| TABLE_A|
| 1| null| STUFF| TABLE_B|
创作:
SELECT
ID as id, FIELD_A as field_a, null as field_b, 'TABLE_A' as ORIG_TABLE
FROM TABLE_A
UNION ALL
SELECT
ID as id, null as field_a, FIELD_B as field_b, 'TABLE_B' as ORIG_TABLE
FROM TABLE_B
不过要小心,如果您使用缓存、添加生成的 ID 或将 ORIG_TABLE
包含到您的 @Id
映射中,ID 字段可能会让您感到困惑。
TL.DR.: 第二种方法你可以映射你需要的所有东西并且(在我看来)它是获得结果的可接受方式。如果设置的问题参数多于单个 entity
可能设置的问题参数,那么您也不必考虑这一点。 (例如:FIELD_A='EXAMPLE' AND FIELD_B='STUFF'
根本就没有结果。)虽然还有其他方法可以实现这一点,例如使用 JPQL
和 left join 以及 constructor mapping 我认为第二个选项是最清晰和最可维护的。