规范/谓词以基于实例类型Spring搜索不同的字段

时间:2018-12-17 03:08:45

标签: java spring spring-boot predicate specifications

让我们说我有一个抽象类Product,我还有另外两个实体用不同的字段扩展了ProductA和ProductB。我正在使用“产品”存储库,使用“规范”和“ 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;
        }
    }
}

我的产品分类

@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; }

如有任何疑问,请随时提问。任何形式的帮助将不胜感激!预先谢谢你。

1 个答案:

答案 0 :(得分:1)

据我所知,这样做没有“好办法”(如果要保留类型),但是可以采用以下解决方案之一:

0。切换大小写 通常,我确实反对switch中的caseJava,但是它有自己的位置...最简单的解决方案是在也是分开的情况...它们是分开的情况(不是故意的)。

根据您的问题,我认为您的结构要比提出的结构复杂得多,因此接下来我为您提供两种可能的解决方案。

1。程序化解决方案 您可以基于反射来创建解决方案。您可以拥有实现一些有趣逻辑的完整解决方案。主要是您必须具有此功能中“可搜索”的对象的列表。在循环中,您可以使用反射来确定该字段在实体中是否“可接受”。 (例如,˙stock˙仅在PRODUCT_A中可以接受,因此标准的创建将基于该Entity我绝对不推荐这种方法,因为这可能会引入很多问题。(例如,如果在DTO中同时设置stockfeatured,您将如何处理?

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并使用左联接和constructor mapping,但我认为第二个选择最清晰,最容易维护的。