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 的字段(例如 LocalDateTime
)here, 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;
}
我打算从 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 的字段(例如 LocalDateTime
)here, 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;
}