Выполнить поиск JPA в строках с заданными идентификаторами - PullRequest
1 голос
/ 16 апреля 2019

Я работаю над тем, чтобы сделать поиск в моем приложении более эффективным. Вариант использования, который я пытаюсь решить, заключается в том, что при заданном наборе идентификаторов поиск выполняется только в указанных строках. Проблема в том, что размер этого идентификатора больше 1024. Когда я использую BooleanQuery.setMaxClauseCount(Ids.size(), я не получаю исключение tooManyClauses, но он не возвращает никакого результата из-за timeOut.

В идеале я хотел бы сделать это:

public ResponseEntity search(
            @RequestParam(Param.SEARCH) String query,
            @RequestParam(value = Param.PAGE, defaultValue = Pagination.DEFAULT_PAGE) int page,
            @RequestParam(value = Param.SIZE, defaultValue = Pagination.DEFAULT_SIZE) int size)
            throws Exception {
        log.info("Started searching for query : " + query);

        final Set<Long> accessibleIds = projectsServiceUtils
                .filterIdsAccessibleByLoggedInPerson(null);

        final FullTextQuery fullTextQuery = searchHelper
                .getPrefixSearchQuery(Project.class, SearchFields.PROJECT_BOOST_MAP, query, accessibleRadarIds);

        final List<Project> projects = fullTextQuery.setFirstResult(page * size)
                .setMaxResults(size).getResultList();

        return ResponseEntity.ok()
                .header(Pagination.PAGINATION_PAGE, String.valueOf(page))
                .header(Pagination.PAGINATION_SIZE, String.valueOf(size))
                .header(Pagination.PAGINATION_COUNT, Long.toString(fullTextQuery.getResultSize()))
                .body(projectSearchObjectWriter.writeValueAsString(projects));
    }

С этим как мой метод getPrefixSearchQuery:

public <T> FullTextQuery getPrefixSearchQuery(
            Class<T> typeClass, Map<String, Float> boostMap, String searchTerms, Set<Long> ids) {
        FullTextEntityManager fullTextEntityManager = Search
                .getFullTextEntityManager(entityManager);
        QueryBuilder qb = fullTextEntityManager
                .getSearchFactory()
                .buildQueryBuilder()
                .forEntity(typeClass).get();

        BooleanQuery.Builder luceneQuery = new BooleanQuery.Builder();

        String[] tokens = searchTerms.split("\\s+");

        for (String token : tokens) {
            if (!StopAnalyzer.ENGLISH_STOP_WORDS_SET.contains(token) || tokens.length == 1) {
                // If search term contains only digits then search substring (possibly phone number)
                final String matcher = token.toLowerCase() + "*";

                final WildcardContext wildcardContext = qb.keyword().wildcard();
                TermMatchingContext termMatchingContext = null;
                for (String field : boostMap.keySet()) {
                    if (termMatchingContext != null) {
                        termMatchingContext = termMatchingContext.andField(field).boostedTo(boostMap.get(field));
                    } else {
                        termMatchingContext = wildcardContext.onField(field).boostedTo(boostMap.get(field));
                    }
                }
                final Query subQuery = termMatchingContext.matching(matcher).createQuery();
                luceneQuery.add(subQuery, BooleanClause.Occur.MUST);
            }
        }
        // NEW CODE TO SUPPORT FILTERING
        if (ids != null) {
            BooleanQuery.setMaxClauseCount(ids.size() + (tokens.length*boostMap.size()));
            TermMatchingContext termMatchingContext2 = qb.keyword().wildcard().onField("id");

            for (Long id : ids) {
                luceneQuery.add(termMatchingContext2.matching(id).createQuery(), BooleanClause.Occur.FILTER);
            }
        }

        FullTextQuery jpaQuery = fullTextEntityManager
                .createFullTextQuery(luceneQuery.build(), typeClass);

        return jpaQuery;
    }

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

public ResponseEntity search(
            @RequestParam(Param.SEARCH) String query,
            @RequestParam(value = Param.PAGE, defaultValue = Pagination.DEFAULT_PAGE) int page,
            @RequestParam(value = Param.SIZE, defaultValue = Pagination.DEFAULT_SIZE) int size,
            @RequestParam(value = Param.SORT, defaultValue = SortDefault.BY_NAME) String sort)
            throws IOException, IdmsException {
        log.info("Started searching for query : " + query);

        final FullTextQuery fullTextQuery = searchHelper
                .getPrefixSearchQuery(Project.class, SearchFields.PROJECT, query);
        final List<Project> projects = fullTextQuery.getResultList();

        final ImmutableSet<Long> Ids = projects
                .stream()
                .map(Project::getId)
                .collect(collectingAndThen(Collectors.toSet(),
                        ImmutableSet::copyOf));

        final Set<Long> accessibleIds = projectsServiceUtils
                .filterIdsAccessibleByLoggedInPerson(Ids);

        PageRequest pageRequest = new PageRequest(
                page, size);

        final Page<Project> projectsFiltered = projectRepository.findByIdIn(
                accessibleIds, pageRequest);

        return ResponseEntity.ok()
                .header(Pagination.PAGINATION_PAGE, String.valueOf(page))
                .header(Pagination.PAGINATION_SIZE, String.valueOf(size))
                .header(Pagination.PAGINATION_COUNT, String.valueOf(accessibleIds.size()))
                .body(projectSearchObjectWriter.writeValueAsString(projectsFiltered.getContent()));
    }

Можно ли каким-либо образом выполнить поиск в строках с указанным идентификатором и разбить его на страницы?

1 Ответ

0 голосов
/ 17 апреля 2019
    if (ids != null) {
       BooleanQuery.setMaxClauseCount(ids.size() + (tokens.length*boostMap.size()));
       TermMatchingContext termMatchingContext2 = qb.keyword().wildcard().onField("id");

       for (Long id : ids) {
           luceneQuery.add(termMatchingContext2.matching(id).createQuery(), BooleanClause.Occur.FILTER);
       }
   }

Вы используете подстановочный запрос, но вам это не нужно.В зависимости от размера вашего списка идентификаторов, это может быть причиной того, что вы получаете тайм-аут.

Попробуйте вместо этого:

     if (ids != null) {
        BooleanQuery.setMaxClauseCount(ids.size() + (tokens.length*boostMap.size()));

        for (Long id : ids) {
            luceneQuery.add(qb.keyword().onField("id").matching(id).createQuery(), BooleanClause.Occur.FILTER);
        }
    }

Кроме того, вам не нужно устанавливать максимальноеколичество предложений каждый раз, когда вы выполняете запрос.Вы действительно не должны этого делать, поскольку несколько запросов могут выполняться параллельно.

Просто выполните что-то подобное при запуске приложения:

BooleanQuery.setMaxClauseCount(<some large number>);
...