Hibernate Search 中 BigDecimal 的范围查询

Range query on BigDecimal in Hibernate Search

我正在尝试对 BigDecimal 类型的实体字段进行范围查询,但无法使其工作。这是我到目前为止所做的。

这是实体 class。

public class Deal {
    @Field(store = Store.YES)
    @Field(name = "budget_Sort", store = Store.YES, normalizer= @Normalizer(definition = SearchConstants.LOWER_CASE_NORMALIZER))
    @FieldBridge(impl = BigDecimalNumericFieldBridge.class)
    @SortableField(forField = "budget_Sort")
    @Column(name = "BUDGET", precision = 10, scale = 2)
    private BigDecimal budget = new BigDecimal(0);

    //other fields and methods omitted for brevity
}

BigDecimal的自定义FieldBridge如下。我选择类型 DOUBLE 作为转换类型。

public class BigDecimalNumericFieldBridge implements MetadataProvidingFieldBridge, TwoWayFieldBridge {

    private static final BigDecimal storeFactor = BigDecimal.valueOf( 100 );

    @Override
    public void set(String name, Object value, Document document, LuceneOptions luceneOptions) {
        if ( value != null ) {
            BigDecimal decimalValue = (BigDecimal) value;
            Double indexedValue = decimalValue.multiply( storeFactor ).doubleValue();
            luceneOptions.addNumericFieldToDocument( name, indexedValue, document );
            luceneOptions.addNumericDocValuesFieldToDocument(name, indexedValue, document);
        }
    }

    @Override
    public Object get(String name, Document document) {
        String fromLucene = document.get( name );
        if (Objects.nonNull(fromLucene)) {
            BigDecimal storedBigDecimal = new BigDecimal(fromLucene);
            return storedBigDecimal.divide(storeFactor);
        } else {
            return null;
        }
    }

    @Override
    public String objectToString(Object object) {
        return object.toString();
    }

    @Override
    public void configureFieldMetadata(String name, FieldMetadataBuilder builder) {
        builder.field( name, FieldType.DOUBLE );
    }
}

然后我定义了一个class范围来传递下限、上限和字段数据类型。

public class Range {
    private String lowerBound;
    private String upperBound;
    private String dataType;
}

并创建了以下对象来测试此范围查询。

Range budgetRange = new Range("9500", "10500",SearchConstants.BIGDECIMAL);

实际查询构建逻辑如下

    protected Query rangeFilterBuilder(String key, String field) {
        Query rangeQuery = null;
        String lowerBound = searchRequest.getRangeFilters().get(key).getLowerBound();
        String upperBound = searchRequest.getRangeFilters().get(key).getUpperBound();
        String dataType = searchRequest.getRangeFilters().get(key).getDataType();
        switch (dataType) {
            case SearchConstants.INTEGER:
                rangeQuery = queryBuilder.range().onField(field).from(Integer.valueOf(lowerBound)).to(Integer.valueOf(upperBound)).createQuery();
                break;
            case SearchConstants.LONG:
                rangeQuery = queryBuilder.range().onField(field).from(Long.valueOf(lowerBound)).to(Long.valueOf(upperBound)).createQuery();
                break;
            case SearchConstants.DOUBLE:
                rangeQuery = queryBuilder.range().onField(field).from(Double.valueOf(lowerBound)).to(Double.valueOf(upperBound)).createQuery();
                break;
            case SearchConstants.STRING:
                rangeQuery = queryBuilder.range().onField(field).from((lowerBound)).to(upperBound).createQuery();
                break;
            case SearchConstants.DATE:
                rangeQuery = queryBuilder.range().onField(field).from(LocalDateTime.parse(lowerBound)).to(LocalDateTime.parse(upperBound)).createQuery();
                break;
            case SearchConstants.BIGDECIMAL:
                rangeQuery = queryBuilder.range().onField(field).from(Double.valueOf(lowerBound)).to(Double.valueOf(upperBound)).createQuery();
        }
        return rangeQuery;
    }```

This always return 0 matching result regardless of the range. I've tried other fields of type int or long and they work as expected. Is there something that I missed for BigDecimal range query?

问题是您在编制索引时将值乘以 100,但在查询时却没有。因此,当您搜索值 42 时,范围 [40, 45] 不会匹配,但范围 [4000, 4500] 会匹配。

对于 "BigDecimal" 的情况,解决方案是在函数中将边界乘以 100:

        switch (dataType) {
            case SearchConstants.INTEGER:
                rangeQuery = queryBuilder.range().onField(field).from(Integer.valueOf(lowerBound)).to(Integer.valueOf(upperBound)).createQuery();
                break;
            case SearchConstants.LONG:
                rangeQuery = queryBuilder.range().onField(field).from(Long.valueOf(lowerBound)).to(Long.valueOf(upperBound)).createQuery();
                break;
            case SearchConstants.DOUBLE:
                rangeQuery = queryBuilder.range().onField(field).from(Double.valueOf(lowerBound)).to(Double.valueOf(upperBound)).createQuery();
                break;
            case SearchConstants.STRING:
                rangeQuery = queryBuilder.range().onField(field).from((lowerBound)).to(upperBound).createQuery();
                break;
            case SearchConstants.DATE:
                rangeQuery = queryBuilder.range().onField(field).from(LocalDateTime.parse(lowerBound)).to(LocalDateTime.parse(upperBound)).createQuery();
                break;
            case SearchConstants.BIGDECIMAL:
                rangeQuery = queryBuilder.range().onField(field).from(Double.valueOf(lowerBound) * 100.0).to(Double.valueOf(upperBound) * 100.0).createQuery();
        }

顺便说一下,在 BigDecimal 的情况下,我真的认为您的函数应该将数字解析为 BigDecimal,而不是 Double。然后你可以将它相乘,然后将它转换为 Double,类似于你在你的函数中所做的。

此外,在索引(和查询)缩放的 BigDecimal 时,您可能希望使用 Long 而不是 Double:您将获得更高的性能并且肯定索引数字正是您想要的数字(没有四舍五入)。无论如何,您不需要 double 的货币金额范围,除非您处理的金额高于地球上所有资产的总价值:)