Hibernate Search 6 中 LocalDateTime 的范围查询

Range query on LocalDateTime in Hibernate Search 6

我打算从 Hibernate Search 5.11 切换到 6,但找不到在 LocalDateTime 上查询 DSL 以进行范围查询的方法。我更喜欢使用原生的 Lucene QueryParser。在以前的版本中,我使用了 NumericRangeQuery,因为使用了@FieldBridge(转换为长值)。

这是我以前的版本代码。

    @Entity 
    ...
       @NumericField //convert to long value
        @FieldBridge(impl = LongLocalDateTimeFieldBridge.class) 
        @Field(index = Index.YES, analyze = Analyze.NO, store = Store.NO)
        private LocalDateTime createDate;
...

这些是 QueryParser

     public class NumericLocalDateRangeQueryParser extends QueryParser {
    
        private static final Logger logger = LogManager.getLogger();
        private String f;
        private static final Long DEFAULT_DATE = -1L;
        private String dateFormat;
        public NumericLocalDateRangeQueryParser(final String f, final Analyzer a) {
            super(f, a);
            this.f = f;
        }
    
        public NumericLocalDateRangeQueryParser(final String dateFormat,final String f, Analyzer a) {
            super(f, a);
            this.f = f;
            this.dateFormat = dateFormat;
            logger.debug("date formate: {}", ()->dateFormat);
        }
    
          
        //check a field if found, have to set to -1
        @Override
        protected Query newFieldQuery(Analyzer analyzer, String field, String queryText, boolean quoted) throws ParseException {
            if (f.equals(field)) {
                 try {
                     return NumericRangeQuery.newLongRange(
                            field,
                            stringToTime(queryText).toEpochDay(), stringToTime(queryText).toEpochDay(),
                            true,
                            true
                    );
                } catch (final DateTimeParseException ex) {
                    
                    return super.newFieldQuery(analyzer, field, queryText, quoted);
                     
                }
            }
          
            return super.newFieldQuery(analyzer, field, queryText, quoted);  
        }
    
        /**
         *
         * @param field = filed when indexing
         * @param part1 = date 1 e.g. date 1 to date 2 in string
         * @param part2 = date 2
         * @param startInclusive
         * @param endInclusive
         * @return
         */
        @Override
        protected Query newRangeQuery(final String field, final String part1, final String part2,
                final boolean startInclusive, final boolean endInclusive) {
    
            if (f.equals(field)) {
    
                try {
                    return NumericRangeQuery.newLongRange(
                            field,
                            stringToTime(part1).toEpochDay(), stringToTime(part2).toEpochDay(),
                            true,
                            true
                    );
    
                } catch (final DateTimeParseException ex) {
                    return NumericRangeQuery.newLongRange(field, DEFAULT_DATE, DEFAULT_DATE, true, true);
                }
            } else {
                return super.newRangeQuery(field, part1, part2, startInclusive, endInclusive);
            }
        }
    
        @Override
        protected org.apache.lucene.search.Query newTermQuery(final Term term) { 
 if (term.field().equals(f)) {
                try {
                    return NumericRangeQuery.newLongRange(term.field(),
                            stringToTime(term.text()).toEpochDay(), stringToTime(term.text()).toEpochDay(), true, true);
    
                } catch (final DateTimeParseException ex) {
                    logger.debug("it's not numeric: {}", () -> ex.getMessage());
                    return NumericRangeQuery.newLongRange(field, DEFAULT_DATE,DEFAULT_DATE, true, true);
                }
            } else {
                logger.debug("normal query term");
                return super.newTermQuery(term);
            }
        }
    
        private LocalDate stringToTime(final String date) throws DateTimeParseException {
           final  DateTimeFormatter formatter = DateTimeFormatter.ofPattern(dateFormat);
            return LocalDate.parse(date, formatter);
    
        }
       
    }

首先,在映射方面,您只需要:

       @GenericField
       private LocalDateTime createDate;

二、查询。如果您真的想编写本机查询并跳过整个 Search DSL,我想您有自己的理由。你介意在评论中分享它们吗?也许它会给我一些改进 Hibernate Search 的想法。

无论如何,底层查询在 Lucene 5 和 8 之间发生了很大变化。您可以找到我们如何查询基于 long 的字段(例如 LocalDateTimehere, and how we convert a LocalDateTime to a long here.

所以,像这样的东西应该可以工作:

long part1AsLong = stringToTime(part1).toInstant(ZoneOffset.UTC).toEpochMilli();
long part2AsLong = stringToTime(part2).toInstant(ZoneOffset.UTC).toEpochMilli();
Query query = LongPoint.newRangeQuery(field, part1AsLong, part2AsLong);

或者,如果您可以依赖搜索 DSL,您可以这样做:

SearchSession searchSession = Search.session(entityManager);
SearchScope<MyEntity> scope = searchSession.scope(MyEntity.class);
// Pass the scope to your query parser somehow
MyQueryParser parser = new MyQueryParser(..., scope);

// Then in your parser, do this to create a range query on a `LocalDateTime` field:
LocalDateTime part1AsDateTime = stringToTime(part1);
LocalDateTime part2AsDateTime = stringToTime(part2);
Query query = LuceneMigrationUtils.toLuceneQuery(scope.predicate().range()
        .field(field)
        .between(part1AsDateTime, part2AsDateTime)
        .toPredicate());

但是请注意,LuceneMigrationUtils 是 SPI,因此它可能会在以后的版本中更改或删除。如果您认为它有用,我们可以在未来的版本中将其公开为 API,这样它就可以保证保留在那里。


不过,我想我们可以通过向 Hibernate Search 添加一些内容来更好地解决您的问题。为什么您需要依赖查询解析器?

这是我的代码(从以前的版本修改而来):

    public class LongPointLocalDateTimeRangeQueryParser extends QueryParser {
    
        private static final Logger logger = LogManager.getLogger();
        private String f;
        private static final Long DEFAULT_DATE = -1L;
        private String dateFormat;
    
        public LongPointLocalDateTimeRangeQueryParser(final String f, final Analyzer a) {
            super(f, a);
            this.f = f;
        }
    
        public LongPointLocalDateTimeRangeQueryParser(final String dateFormat, final String f, Analyzer a) {
            super(f, a);
            this.f = f;
            this.dateFormat = dateFormat; 
        }
     
        @Override
        protected Query newFieldQuery(Analyzer analyzer, String field, String queryText, boolean quoted) throws ParseException {
            if (f.equals(field)) {
                logger.debug("newFieldQuery, with field: {}, queryText: {}, quoted: {}", () -> f, () -> queryText, () -> quoted);
                try {      
                    return LongPoint.newRangeQuery(
                            field,
                            stringToTime(queryText).toInstant(ZoneOffset.UTC).toEpochMilli(), stringToTime(queryText).plusDays(1).toInstant(ZoneOffset.UTC).toEpochMilli()
                    );
    
                } catch (final DateTimeParseException ex) {
                    logger.debug("it's not date format, error: {}", () -> ex.getMessage());
                    return super.newFieldQuery(analyzer, field, queryText, quoted);
                    //return null;
                }
            }
            logger.debug("newFieldQuery, normal, queryText: {}, quoted: {}", () -> queryText, () -> quoted);
            return super.newFieldQuery(analyzer, field, queryText, quoted); //To change body of generated methods, choose Tools | Templates.
        }
    
        /**
         *
         * @param field = filed when indexing
         * @param part1 = date 1 in string
         * @param part2 = date 2
         * @param startInclusive
         * @param endInclusive
         * @return
         */
        @Override
        protected Query newRangeQuery(final String field, final String part1, final String part2,
                final boolean startInclusive, final boolean endInclusive) {
            if (f.equals(field)) {
                try {
            logger.debug("date 1: {}, str: {}", () -> stringToTime(part1).toInstant(ZoneOffset.UTC).toEpochMilli(), () -> part1);
            logger.debug("date 2: {}, str: {}", () -> stringToTime(part2).plusDays(1).toInstant(ZoneOffset.UTC).toEpochMilli(), () -> part2);
                    return LongPoint.newRangeQuery(
                            field,
                            stringToTime(part1).toInstant(ZoneOffset.UTC).toEpochMilli(), stringToTime(part2).plusDays(1).toInstant(ZoneOffset.UTC).toEpochMilli()
                    );
                } catch (final DateTimeParseException ex) {
                    logger.debug("it's not date format, error: {}", () -> ex.getMessage());
                    return LongPoint.newRangeQuery(field, DEFAULT_DATE, DEFAULT_DATE);
                }
            } else {
                logger.debug("normal query range");
                return super.newRangeQuery(field, part1, part2, startInclusive, endInclusive);
            }
        }
    
        private LocalDateTime stringToTime(final String date) throws DateTimeParseException {
   //... same as previous posted
        }
}

这是查询解析器

//... 
    final SearchQuery<POSProcessInventory> result = searchSession.search(POSProcessInventory.class).extension(LuceneExtension.get())
                        .where(f -> f.bool(b -> {
                     b.must(f.bool(b1 -> {
    //...
      try {
                            if (searchWord.contains("createDate:")) {
                                logger.info("doing queryParser for LocalDateTime: {}", () -> searchWord);
                                b1.should(f.fromLuceneQuery(queryLocalDateTime("createDate", searchWord)));
                            }
                        } catch (ParseException ex) {
                            logger.error("#3 this is not localDateTime");
                        }

还有另一种方法

 private org.apache.lucene.search.Query queryLocalDateTime(final String field, final String dateTime)
            throws org.apache.lucene.queryparser.classic.ParseException {
        final LongPointLocalDateTimeRangeQueryParser createDateQ = new LongPointLocalDateTimeRangeQueryParser(accessCompanyInfo.getDateTimeFormat().substring(0, 10), field, new KeywordAnalyzer());
        createDateQ.setAllowLeadingWildcard(false);  
        final org.apache.lucene.search.Query queryLocalDate = createDateQ.parse(dateTime);
        logger.debug(field + "query field: {} query Str: {}", () -> field, () -> queryLocalDate);
        return queryLocalDate;
    }