使用ElasticsearchTemplate 对Double类型进行Sum 聚合操作丢失精度

情景描述:为减轻mysql压力,采用了读写分离,统计的相关操作也移动到了ES上,但在对ES某些数据使用聚合函数时,发现精度丢失。

先来复现一下这个场景:
新建一个document类

package com.hclc.document;

import io.swagger.annotations.ApiModelProperty;
import lombok.*;
import lombok.experimental.Accessors;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

/**
 * @ClassName NumberTestDocument
 * @Author: yurj
 * @Mail:1638234804@qq.com
 * @Date: Create in 9:52 2020/6/15
 * @version: 1.0
 */
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@Document(indexName = "t_number_test_document", type = "docs", shards = 5, replicas = 1)
public class NumberTestDocument {
    /**
     * 主键
     */
    @Id
    @ApiModelProperty(value = "主键")
    @Builder.Default
    @Field(type = FieldType.Long)
    private Long id = 1L;

    /**
     * 数值
     */
    @ApiModelProperty(value = "数值")
    @Builder.Default
    @Field(type = FieldType.Double)
    private Double number = 0.62;

    /**
     * 字符
     */
    @ApiModelProperty(value = "字符")
    @Builder.Default
    @Field(type = FieldType.Keyword)
    private String character = "";

}

使用spring data ElasticsearchTemplate 插入50条模拟数据

package com.test;

import com.hclc.CustomerApplication;
import com.hclc.document.NumberTestDocument;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;


@RunWith(SpringRunner.class)
@SpringBootTest(classes = CustomerApplication.class)
@Slf4j
public class DemoApplicationTests {
    static {
        System.setProperty("es.set.netty.runtime.available.processors", "false");
    }

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Test
    public void contextLoads() {
        if (!elasticsearchTemplate.indexExists(NumberTestDocument.class)) {
            elasticsearchTemplate.createIndex(NumberTestDocument.class);
        }

        List<IndexQuery> queries = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            long mostSignificantBits = UUID.randomUUID().getMostSignificantBits();
            BigDecimal b = new BigDecimal(Math.random() * 100);
            double f1 = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
            String uuid = String.valueOf(mostSignificantBits);
            IndexQuery indexQuery = new IndexQuery();
            indexQuery.setId(uuid);
            indexQuery.setObject(new NumberTestDocument().setId(mostSignificantBits).setNumber(f1).setCharacter(uuid));
            indexQuery.setIndexName(NumberTestDocument.class.getAnnotation(Document.class).indexName());
            indexQuery.setType(NumberTestDocument.class.getAnnotation(Document.class).type());
            queries.add(indexQuery);
            elasticsearchTemplate.bulkIndex(queries);


        }
    }

}

数据插入效果图:
在这里插入图片描述
对应映射(注意number字段的type):
在这里插入图片描述
对number字段进行聚合操作:

@Test
    public void contextLoads() {
        //聚合条件
        SumAggregationBuilder doubleSum = AggregationBuilders.sum(BeanUtils.convertToFieldName(NumberTestDocument::getNumber))
                .field(BeanUtils.convertToFieldName(NumberTestDocument::getNumber));

        // 创建查询对象
        SearchQuery build = new NativeSearchQueryBuilder()
                .withIndices(NumberTestDocument.class.getAnnotation(Document.class).indexName())
                .withTypes(NumberTestDocument.class.getAnnotation(Document.class).type())
                .addAggregation(doubleSum) // 添加聚合条件
                .build();

        Aggregations aggregations = elasticsearchTemplate.query(build, (s) -> s.getAggregations());
        //拿到聚合对象
        InternalSum internalSum = (InternalSum) aggregations.asMap().get(BeanUtils.convertToFieldName(NumberTestDocument::getNumber));
        System.out.println(internalSum.getValue());

    }

打印结果会丢失精度,如下图:
在这里插入图片描述

如何解决呢?答案是将float类型转换为double即可,可以借用动态模板来实现。
官方动态模板介绍传送门戳我跳转

编写模板json

{
    "dynamic_templates":[
        {
            "all_to_double":{
                "match_mapping_type":"double",
                "mapping":{
                    "type":"double"
                }
            }
        }]
}

删除创建的index:
在这里插入图片描述
重新创建,在索引中加入dynamic_templates:

@Test
    public void contextLoads() {
        if (!elasticsearchTemplate.indexExists(NumberTestDocument.class)) {
            elasticsearchTemplate.createIndex(NumberTestDocument.class);
        }
        //加入动态模板json
        String templateJson ="{\"dynamic_templates\":[{\"all_to_double\":{\"match_mapping_type\":\"double\",\"mapping\":{\"type\":\"double\"}}}]}";
        //配置映射
        elasticsearchTemplate.putMapping(NumberTestDocument.class.getAnnotation(Document.class).indexName(),NumberTestDocument.class.getAnnotation(Document.class).type(),templateJson);

        List<IndexQuery> queries = new ArrayList<>();
        for (int i = 0; i < 50; i++) {
            long mostSignificantBits = UUID.randomUUID().getMostSignificantBits();
            BigDecimal b = new BigDecimal(Math.random() * 100);
            double f1 = b.setScale(2, BigDecimal.ROUND_HALF_UP).doubleValue();
            String uuid = String.valueOf(mostSignificantBits);
            IndexQuery indexQuery = new IndexQuery();
            indexQuery.setId(uuid);
            indexQuery.setObject(new NumberTestDocument().setId(mostSignificantBits).setNumber(f1).setCharacter(uuid));
            indexQuery.setIndexName(NumberTestDocument.class.getAnnotation(Document.class).indexName());
            indexQuery.setType(NumberTestDocument.class.getAnnotation(Document.class).type());
            queries.add(indexQuery);
            elasticsearchTemplate.bulkIndex(queries);


        }
    }

我们查看新的映射可以发现动态模板已经成功加入(这里同样注意number字段的type,已经更新为double):

试试聚合:

聚合结果正常,问题解决。

相同问题链接戳我跳转

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐