Спецификация / Предикат для поиска различных полей на основе типа экземпляра Spring - PullRequest
0 голосов
/ 17 декабря 2018

Допустим, у меня есть абстрактный класс Product, и у меня есть еще две сущности, которые расширяют его ProductA и ProductB с различными полями.Я использую репозиторий Product для поиска всех их, используя 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;
        }
    }
}

И мои классы по продукту

@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 голосов
/ 24 декабря 2018

Насколько я знаю, нет "хорошего способа" сделать это (если вы хотите сохранить тип), но вы можете использовать одно из следующих решений:

0.Переключатель регистра обычно я действительно против switch и case в Java, но у него есть свое место ... Самое простое решение - создать запрос в отдельных случаях ... ониэто отдельные случаи (каламбур не предназначен).

Исходя из вашего вопроса, я предполагаю, что у вас гораздо более сложная структура, чем представленная, поэтому далее у меня есть два возможных решения для вас.

1.Программное решение Вы можете создать решение на основе отражения.У вас может быть полное решение, реализующее некоторую интересную логику.В основном у вас должен быть список из объектов, которые «доступны для поиска» в этой функции.В цикле вы можете использовать отражение, чтобы определить, является ли поле «приемлемым» в сущности.(Например, «сток» допустим только в PRODUCT_A, поэтому создание критериев будет основано на этом Entity. Я определенно не рекомендую этот подход , так как это может создать много проблем.(Например, как бы вы справились, если в вашем DTO одновременно установлены stock и featured?

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 

Будьте осторожны, хотя поле идентификатора может запутать вас, если вы используете кэширование, добавите сгенерированный идентификатор или включите ORIG_TABLE в ваше отображение @Id.

TL.DR.: Второй способ, которым вы можете отобразить все, что вам нужно, и (по моему мнению), это приемлемый способ получить результаты.хороший момент, который вы не должны принимать во внимание, если задано больше параметров вопроса, чемвозможно для одного entity.(Например: FIELD_A='EXAMPLE' AND FIELD_B='STUFF' просто не будет никакого результата.) Хотя есть и другие способы для достижения этой цели, такие как JPQL и левое соединение и отображение конструктора Я думаю, что второй вариант является наиболее понятным и наиболееремонтопригодный.

...