Генерация фильтрации в spring boot

#java #spring #jpa

#java #весна #jpa

Вопрос:

Я пытаюсь обобщить процесс фильтрации, чтобы иметь возможность применять фильтры к любому из моих объектов.

Используя некоторый онлайн-код, я создаю это:

 public enum FilterOperation {
    // @formatter:off
    LESS_EQUAL_THAN("<=", CriteriaBuilder::lessThanOrEqualTo),
    GREATER_EQUAL_THAN(">=", CriteriaBuilder::greaterThanOrEqualTo),
    CONTAINS(":>", CriteriaBuilder::like),
    GREATER_THAN(">", CriteriaBuilder::greaterThan),
    LESS_THAN("<", CriteriaBuilder::lessThan),
    EQUALS("::", CriteriaBuilder::equal);
    // @formatter:on

    private final String operationName;
    private final FilterPredicateFunction operation;

    private static Pattern separator = Pattern.compile("([\S\s] )§([\S\s] )");

    FilterOperation(String operationName, FilterPredicateFunction operation) {
        this.operationName = operationName;
        this.operation = operation;
    }

    public String getOperationName() {
        return operationName;
    }

    public Predicate build(CriteriaBuilder builder, Root<?> entity, String key, String value) {
        // Split value by character §
        Matcher m = separator.matcher(value);
        if (m.matches()) {
            String type = m.group(2);
            if (type.equals("UUID")) {
                return builder.equal(entity.get(key), UUID.fromString(m.group(1)));
            } else {
                throw new WrongFilterException("Filter operation not found");
            }
        } else {
            // No § found we expect a string search
            return operation.predicate(builder, entity.get(key), value.toString());
        }
    }

    static FilterOperation parse(String str) {
        for (FilterOperation filter : FilterOperation.values()) {
            if (str.equals(filter.getOperationName())) {
                return filter;
            }
        }

        throw new WrongFilterException(String.format("Filter operation not found", str));
    }

    @FunctionalInterface
    interface FilterPredicateFunction {
        Predicate predicate(CriteriaBuilder builder, Path<String> key, String value);
    }
}
 

Вот пример URL с фильтрацией:

 http://localhost:8080/api/type/284c5f72-559d-4e62-a316-6bd23259c213/slot/filter?filter=name:>Samediamp;filter=id::773f7313-0f37-4465-a707-7295c2bb87a3§UUID
 

Как вы можете видеть, у меня есть два случая: один только с полем, операцией и значением name:>Samedi , а второй случай, когда я добавляю тип с символом-разделителем: id::773f7313-0f37-4465-a707-7295c2bb87a3§UUID

Просто для уточнения, другой класс вызывает мою функцию сборки следующим образом:

 return (Specification<T>) (root, query, cb) -> op.build(cb, root, key, value);
 

Например, с ключом как 'id' и значением как '773f7313-0f37-4465-a707-7295c2bb87a3§UUID'

На самом деле в моем коде я использую builder.equal функцию для создания предикатов для моего пользовательского типа (здесь UUID ), а не для операции, определенной в начале моего перечисления, но теперь я хочу добавить поддержку для LocalTime типа и использовать правильную операцию, чтобы иметь возможность использовать a greaterThan или меньше.

  if (type.equals("UUID")) {
     return builder.equal(entity.get(key), UUID.fromString(m.group(1)));
 } else {
     throw new WrongFilterException("Filter operation not found");
 }
 

Для этого:

 if (type.equals("UUID")) {
  return operation.predicate(builder, entity.get(key), UUID.fromString(m.group(1)));
} else {
  throw new WrongFilterException("Filter operation not found");
}
 

Но тогда у меня возникает эта ошибка:

 Required type: String Provided: UUID
 

Очевидно, что эта ошибка возникла из моего FilterPredicateFunction , который может принимать только строку, я пытаюсь изменить ее на object, но затем все мои CriteriaBuilder::methodName получают эту ошибку: Cannot resolve method 'greaterThanOrEqualTo'

What can I do to use any operation on any type of data?
Any idea on how I can achieve this?

EDIT:
I add all other class that I use to better understand the process:

 public class EntitySpecificationBuilder<T> {

    @SuppressWarnings({"rawtypes", "unchecked"})
    public static <T> Optional<Specification<T>> parse(List<String> filters) {
        if (filters == null || filters.isEmpty()) {
            return Optional.empty();
        }

        List<Specification> criterias = mapSpecifications(filters);
        if (criterias.isEmpty()) {
            return Optional.empty();
        }

        Specification<T> root = Specification.where(criterias.get(0));
        for (int index = 1; index < criterias.size(); index  ) {
            root = Specification.where(root).and(criterias.get(index));
        }
        return Optional.of(root);
    }

    @SuppressWarnings("rawtypes")
    private static <T> List<Specification> mapSpecifications(List<String> filters) {
        return filters.stream().map(str -> {
            for (FilterOperation op : FilterOperation.values()) {
                int index = str.indexOf(op.getOperationName());
                if (index > 0) {
                    String key = str.substring(0, index);
                    String value = str.substring(index   op.getOperationName().length());

                        return (Specification<T>) (root, query, cb) -> op.build(cb, root, key, value);

                }
            }

            return null;
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }
}
 

The builder:

 public class FindAllBuilder<E, R extends PagingAndSortingRepository<E, ?> amp; JpaSpecificationExecutor<E>> {

    private final R repository;

    private Specification<E> filters;

    private Sort sort = Sort.unsorted();

    public static <E, R extends PagingAndSortingRepository<E, ?> amp; JpaSpecificationExecutor<E>> FindAllBuilder<E, R> usingRepository(
            R repository) {
        return new FindAllBuilder<>(repository);
    }

    private FindAllBuilder(R repository) {
        this.repository = repository;
    }

    public Page<E> findAll(Pageable pageable) {
        return repository.findAll(filters, pageable);
    }

    public FindAllBuilder<E, R> filterBy(List<String> listFilters) {
        Optional<Specification<E>> opFilters = EntitySpecificationBuilder.parse(listFilters);
        if (opFilters.isPresent()) {
            if (filters == null) {
                filters = Specification.where(opFilters.get());
            } else {
                filters = filters.and(opFilters.get());
            }
        }

        return this;
    }

    public FindAllBuilder<E, R> sortBy(String orderBy, String orderDir) {
        if (!orderBy.isEmpty()) {
            sort = Sort.by(Direction.fromOptionalString(orderDir).orElse(Direction.ASC), orderBy);
        }

        return this;
    }
}
 

И как я вызываю это в своем контроллере:

 FindAllBuilder.usingRepository(this.repository).filterBy(filters).findAll(pageable).map(MyMapper.INSTANCE::toDTO);
 

С фильтром в виде массива строк, который выглядит следующим образом: name:>test

Ужасный способ сделать это — преобразовать мою функцию buildChild во что-то вроде этого:

 public Predicate buildChild(CriteriaBuilder builder, Root<?> entity, String pKey, String cKey, String value) {
    Matcher m = separator.matcher(value);
    if (m.matches()) {
        String type = m.group(2);
        if (type.equals("UUID")) {
            if(operationName.equals(FilterOperation.EQUALS.getOperationName())) {
                return builder.equal(entity.get(pKey).get(cKey), UUID.fromString(m.group(1)));
            } else if (operationName.equals(FilterOperation.CONTAINS.getOperationName())) {
                return builder.like(...);
            } etc...
            
        } else if (type.equals("LocalTime")) {
            ....
        }
        
        else {
            throw new WrongFilterException("Filter operation not found");
        }
    } else {
        // No § found we expect a string search
        return operation.predicate(builder, entity.get(pKey).get(cKey), value.toString());
    }
}
 

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

Ответ №1:

Я пытаюсь получить то, чего вы пытаетесь достичь, но с первого взгляда, если ваша логика в порядке, но функция ожидает строку, и вы передаете UUID, поэтому вместо того, чтобы приводить его к объекту, почему бы не заменить :

 UUID.fromString(m.group(1)) 

с

 m.group(1) 

таким образом, вы передаете то же значение, но со строковым типом, и если вы используете

 UUID.fromString() 

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

дайте мне знать, если это сработает с вами.

Комментарии:

1. Да, моя функция предиката в FunctionalInterface ожидает строку, но это проблема, если я приведу свой UUID к string, у меня будет ошибка, которая выглядит так: невозможно сравнить UUID со строкой. Я хочу изменить эту строку на object, чтобы иметь возможность передавать данные любого типа, но это приводит к тому, что мой CriteriaBuilder::method выдает ошибки