spring-boot-starter-data-elasticsearch

依賴

spring-boot 1.5.10.RELEASEjava

pom

... ...
<dependency>
    <groupId>com.sun.jna</groupId>
    <artifactId>jna</artifactId>
    <version>3.0.9</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
... ...

application.properties

#
# ES
#
spring.data.elasticsearch.cluster-name=elasticsearch-dev
spring.data.elasticsearch.cluster-nodes=47.94.5.129:9300
spring.data.elasticsearch.repositories.enabled=true

QuestionAnswerRecord.java

@Document(indexName = "question_answer_record", type = "question_answer_record", indexStoreType = "fs", shards = 5, replicas = 1, refreshInterval = "30s")
public class QuestionAnswerRecord {
    @Id
    @Field(type = FieldType.String, index = FieldIndex.not_analyzed)
    private String id;
    private Integer lessonId;
    private Integer classroomId;
    private Integer smallClassId;
    private Integer questionId;
    private Integer studentId;
    private String studentName;
    @Field(type = FieldType.Date)
    private Date createDate;
    /**
     * 是否答對 0錯誤 1正確
     */
    private Integer isRight;
    @Field(type = FieldType.String, index = FieldIndex.not_analyzed)
    /**
     * 答案編號
     */
    private String answerCid;
    /**
     * 獎勵數量
     */
    @Field(type = FieldType.Integer,index = FieldIndex.no)
    private Integer awardsCount;
    /**
     * 獎勵單位
     */
    @Field(type = FieldType.String, index = FieldIndex.no)
    private String awardsUnit;
... ...
}

QuestionAnswerRecordRepository.java

@Repository
public interface QuestionAnswerRecordRepository extends ElasticsearchRepository<QuestionAnswerRecord, String> {
    List<QuestionAnswerRecord> findAllByCreateDateBetween(Date start, Date end);
    List<QuestionAnswerRecord> findAllByQuestionIdEquals(Integer questionId);
    List<QuestionAnswerRecord> findAllByClassroomIdAndLessonIdAndStudentIdOrderBySequenceAsc(Integer classroomId,
                                                                                             Integer lessonId,
                                                                                             Integer studentId);
    /**
     * <pre>
     * 執行語句
     * {
     *        "from" : 0,
     *            "size" : 3,
     *            "query" : {
     *        "bool" : {
     *            "must" : [ {
     *                "query_string" : {
     *                    "query" : "7777",
     *                            "fields" : [ "lessonId" ],
     *                    "default_operator" : "and"
     *                }
     *            }, {
     *                "query_string" : {
     *                    "query" : "5555",
     *                            "fields" : [ "questionId" ],
     *                    "default_operator" : "and"
     *                }
     *            }, {
     *                "query_string" : {
     *                    "query" : "1",
     *                            "fields" : [ "isRight" ],
     *                    "default_operator" : "and"
     *                }
     *            } ]
     *        }
     *    },
     *        "sort" : [ {
     *        "speedRank" : {
     *            "order" : "asc"
     *        }
     *    }, {
     *        "createDate" : {
     *            "order" : "asc"
     *        }
     *    } ]
     *    }
     *    </pre>
     * ↓OrderBySpeedRankAscCreateDateAsc
     * 會先按照SpeedRank排序而後再按照CreateDate排序
     */
    Page<QuestionAnswerRecord> findByLessonIdAndQuestionIdAndIsRightOrderBySpeedRankAscCreateDateAsc(Integer lessonId,
                                                                                                     Integer questionId,
                                                                                                     Integer isRight,
                                                                                                     Pageable pageable);
}

ApplicationInit .java

@Component
@Profile({"release", "test", "pro"})
public class ApplicationInit {
    private final Logger logger = LoggerFactory.getLogger(ApplicationInit.class);
    /**
     * 寫了index沒有寫type
     * 結果爲 整個Document初始化索引失敗(沒有索引)
     * ↓默認不會阻止發佈
     * 會報failed to load elasticsearch nodes : org.elasticsearch.index.mapper.MapperParsingException: No type specified for field [testLong]
     * ↓顯示調用putMapping會阻止發佈(建議初始化時顯示調用)
     * ElasticsearchTemplate elasticsearchTemplate.putMapping(QuestionAnswerRecord.class);
     * 如插入:
     * 整個Document都會按照默認規則創建index
     * 查詢not_analyzed不會生效!not_analyzed不會生效!not_analyzed不會生效!
     */
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
    @PostConstruct
    private void init() {
        try {
            elasticsearchTemplate.putMapping(QuestionAnswerRecord.class);
        } catch (Exception e) {
            logger.error(" QuestionAnswerRecord 初始化失敗!", e);
        }
    }
}

Test.java

@Autowired
private QuestionAnswerRecordRepository questionAnswerRecordRepository;

public StatusResponse test() {
    Integer lessonId = 7777, classroomId = 6666, questionId = 5555;
    String[] aList = {"A", "B", "C", "D", "MISS"};
    List<QuestionAnswerRecord> saveListTemp = new ArrayList<>();
    questionAnswerRecordRepository.deleteAll();
    Date start = null;
    for (int i = 0; i < 100; i++) {
        if (null == start) {
            start = new Date();
        }
        QuestionAnswerRecord t = new QuestionAnswerRecord();
        t.setLessonId(lessonId);
        t.setClassroomId(classroomId);
        t.setSmallClassId(8888);
        t.setQuestionId(questionId);
        t.setAnswerHead(aList[i % aList.length]);
        if (i % aList.length == 0) {
            t.setIsRight(Const.YES);
        } else {
            t.setIsRight(Const.NO);
        }
        t.setStudentId(i * 10);
        t.setStudentName(String.format("I am %s", t.getId()));
        t.setCreateDate(DateUtil.getNow());
        t.setId(String.format("%s:%s:%s", t.getLessonId(), t.getQuestionId(), t.getStudentId()));
        saveListTemp.add(t);
    }
    questionAnswerRecordRepository.save(saveListTemp);
    Integer classroomId = 8, lessonId = 11, questionId = 2;
    QueryBuilder queryBuilder = QueryBuilders.boolQuery()
            //↓ and
            .must(QueryBuilders.matchQuery("classroomId", classroomId))
            .must(QueryBuilders.matchQuery("lessonId", lessonId))
            .must(QueryBuilders.matchQuery("questionId", questionId));
    SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(queryBuilder)
            //↓根據answerCid分組統計統計後別名爲terms_4_cid
            // 如不設置size默認返回10條terms數據!!!
            .addAggregation(AggregationBuilders.terms("terms_4_cid").field("answerCid").size(100))
            //↓求id數量別名total_count
            .addAggregation(AggregationBuilders.count("total_count").field("id")).build();
    AggregatedPage<QuestionAnswerRecord > answerRecords = (AggregatedPage<QuestionAnswerRecord>) questionAnswerRecordRepository.search(searchQuery);
    Terms terms4cidData = answerRecords.getAggregations().get("terms_4_cid");
    if (!(terms4cidData instanceof UnmappedTerms)) {
        for (Terms.Bucket bucketEle : terms4cidData.getBuckets()) {
            System.out.println(bucketEle.getKey().toString() + "||" + bucketEle.getDocCount());
        }
    }
    InternalValueCount totalCountData = answerRecords.getAggregations().get("total_count");
    System.out.println(totalCountData.getValue());
}

order by

@Autowired
private QuestionAnswerRecordRepository questionAnswerRecordRepository;

public void findFastestRightAnswer(final Integer size, final Integer lessonId, final Integer questionId) {
    //如下兩種等價
    //(1
    SearchQuery searchQuery = new NativeSearchQueryBuilder()
            //↓where
            .withQuery(QueryBuilders.boolQuery()
                    .must(QueryBuilders.matchQuery("lessonId", lessonId))
                    .must(QueryBuilders.matchQuery("questionId", questionId))
                    .must(QueryBuilders.matchQuery("isRight", Const.YES))
            )
            //↓order by
            .withSort(SortBuilders.fieldSort("speedRank").order(SortOrder.ASC))
            .withSort(SortBuilders.fieldSort("createDate").order(SortOrder.ASC))
            //↓limit
            .withPageable(new PageRequest(0, size))
            .build();
    Page<QuestionAnswerRecord> records = questionAnswerRecordRepository.search(searchQuery);
    //(2
    Page<QuestionAnswerRecord> records = questionAnswerRecordRepository
            .findByLessonIdAndQuestionIdAndIsRightOrderBySpeedRankAscCreateDateAsc(
                    lessonId,
                    questionId,
                    Const.YES,
                    new PageRequest(0, size));
    // ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ----
    if (null != records) {
        for (QuestionAnswerRecord record : records) {
            System.out.println(record);
        }
    }
}

order by 多級

/**
 * 先根據questionId分組
 * 以後再根據相關維度分組
 * */
@Autowired
private QuestionAnswerRecordRepository questionAnswerRecordRepository;

public void batchTermsAnswerCount() {
    Set<String> questionIds = new HashSet<>();
    Integer classroomId = 11, lessonId = 19;
    QueryBuilder queryBuilder = QueryBuilders.boolQuery()
            .must(QueryBuilders.matchQuery("classroomId", classroomId))
            .must(QueryBuilders.matchQuery("lessonId", lessonId))
            .must(QueryBuilders.termsQuery("questionId", questionIds));
    //↓獲取結果後進行解析
    Terms termsData = ((AggregatedPage<SchoolroomQuestionAnswerRecord>) questionAnswerRecordRepository.search(
            new NativeSearchQueryBuilder()
                    .withQuery(queryBuilder)
                    .addAggregation(
                            AggregationBuilders.terms("terms_4_question_id").field("questionId").size(100)
                                    .subAggregation(AggregationBuilders.terms("terms_4_cid").field("answerCid"))
                                    .subAggregation(AggregationBuilders.terms("terms_4_tof").field("isRight"))
                                    .subAggregation(AggregationBuilders.terms("terms_4_accuracy_level").field("accuracyLevel"))
                    ).build()
    )).getAggregations().get("terms_4_question_id");
    if (!(termsData instanceof UnmappedTerms)) {
        for (Terms.Bucket bucket : termsData.getBuckets()) {
            String questionId = bucket.getKeyAsString();
            System.out.println("questionId:" + questionId);
            Terms terms4Cid = bucket.getAggregations().get("terms_4_cid");
            Terms terms4Tof = bucket.getAggregations().get("terms_4_tof");
            Terms terms4AccuracyLevel = bucket.getAggregations().get("terms_4_accuracy_level");
            if (!(terms4Cid instanceof UnmappedTerms)) {
                System.out.println("forCid:" + questionId);
                for (Terms.Bucket bucketTemp : terms4Cid.getBuckets()) {
                    System.out.println(bucketTemp.getKeyAsString() + "||" + bucketTemp.getDocCount());
                }
            }
            if (!(terms4Tof instanceof UnmappedTerms)) {
                System.out.println("forTof:" + questionId);
                for (Terms.Bucket bucketTemp : terms4Tof.getBuckets()) {
                    System.out.println(bucketTemp.getKeyAsString() + "||" + bucketTemp.getDocCount());
                }
            }
            if (!(terms4AccuracyLevel instanceof UnmappedTerms)) {
                System.out.println("forAccuracyLevel:" + questionId);
                for (Terms.Bucket bucketTemp : terms4AccuracyLevel.getBuckets()) {
                    System.out.println(bucketTemp.getKeyAsString() + "||" + bucketTemp.getDocCount());
                }
            }
        }
    }
}

Elasticsearch支持的關鍵字

關鍵字 例子 Elasticsearch查詢語句
And findByNameAndPrice {"bool" : {"must" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
Or findByNameOrPrice {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"price" : "?"}} ]}}
Is findByName {"bool" : {"must" : {"field" : {"name" : "?"}}}}
Not findByNameNot {"bool" : {"must_not" : {"field" : {"name" : "?"}}}}
LessThanEqual findByPriceLessThan {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
GreaterThanEqual findByPriceGreaterThan {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
Before findByPriceBefore {"bool" : {"must" : {"range" : {"price" : {"from" : null,"to" : ?,"include_lower" : true,"include_upper" : true}}}}}
After findByPriceAfter {"bool" : {"must" : {"range" : {"price" : {"from" : ?,"to" : null,"include_lower" : true,"include_upper" : true}}}}}
Like findByNameLike {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
StartingWith findByNameStartingWith {"bool" : {"must" : {"field" : {"name" : {"query" : "?*","analyze_wildcard" : true}}}}}
EndingWith findByNameEndingWith {"bool" : {"must" : {"field" : {"name" : {"query" : "*?","analyze_wildcard" : true}}}}}
Contains/Containing findByNameContaining {"bool" : {"must" : {"field" : {"name" : {"query" : "?","analyze_wildcard" : true}}}}}
In findByNameIn(Collectionnames) {"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}
NotIn findByNameNotIn(Collectionnames) {"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}
Near findByStoreNear 暫不支持
True findByAvailableTrue {"bool" : {"must" : {"field" : {"available" : true}}}}
False findByAvailableFalse {"bool" : {"must" : {"field" : {"available" : false}}}}
OrderBy findByAvailableTrueOrderByNameDesc {"sort" : [{ "name" : {"order" : "desc"} }],"bool" : {"must" : {"field" : {"available" : true}}}}

碰見的坑:

/**
 * 修改@Id字段類型後必定要在es服務器刪除索引不然操做該索引時會按照原索引類型parse!!!
 * 例:原來@Id爲Integer 改後@Id爲String且存儲了非數字內容會報錯
 * PS:ElasticsearchRepository<QuestionAnswerRecord, String> 這裏的泛型須要同步修改!
 */
@Document(indexName = "question_answer_record", type = "question_answer_record", indexStoreType = "fs", shards = 5, replicas = 1, refreshInterval = "-1")
public class QuestionAnswerRecord {
    @Id
    @Field(type = FieldType.String, index = FieldIndex.not_analyzed)
    private String id;
    ... ...

~~node

/**
 * 寫了index沒有寫type
 * 結果爲 整個Document初始化索引失敗(沒有索引)
 * ↓默認不會阻止發佈
 * 會報failed to load elasticsearch nodes : org.elasticsearch.index.mapper.MapperParsingException: No type specified for field [testLong]
 * ↓顯示調用putMapping會阻止發佈(建議初始化時顯示調用)
 * ElasticsearchTemplate elasticsearchTemplate.putMapping(QuestionAnswerRecord.class);
 * 如插入:
 * 整個Document都會按照默認規則創建index
 * 查詢not_analyzed不會生效!not_analyzed不會生效!not_analyzed不會生效!
 */
@Document(indexName = "test_a", type = "test_a", indexStoreType = "fs", shards = 5, replicas = 1, refreshInterval = "30s")
class A {
    @Field(index = FieldIndex.not_analyzed)
    private Long str1;
}
@Document(indexName = "test_b", type = "test_b", indexStoreType = "fs", shards = 5, replicas = 1, refreshInterval = "30s")
class B {
    @Field(type = FieldType.String, index = FieldIndex.not_analyzed)
    private Long okStr1;
}
public StatusResponse testES() {
    Aggregations aggregations = elasticsearchTemplate.query(
            new NativeSearchQueryBuilder()
                    .withQuery(QueryBuilders.matchAllQuery())
                    .addAggregation(AggregationBuilders.terms(TermsType.TERMS_4_TOF.getText()).field("str1"))
                    .build()
            , SearchResponse::getAggregations);
    //↓無index
    //  class org.elasticsearch.search.aggregations.bucket.terms.UnmappedTerms
    System.out.println(aggregations.get(TermsType.TERMS_4_TOF.getText()).getClass());
    Aggregations aggregations1 = elasticsearchTemplate.query(
            new NativeSearchQueryBuilder()
                    .withQuery(QueryBuilders.matchAllQuery())
                    .addAggregation(AggregationBuilders.terms(TermsType.TERMS_4_TOF.getText()).field("okStr1"))
                    .build()
            , SearchResponse::getAggregations);
    //↓有index
    //  class org.elasticsearch.search.aggregations.bucket.terms.StringTerms
    System.out.println(aggregations1.get(TermsType.TERMS_4_TOF.getText()).getClass());
    return StatusResponse.ok();
}

~~redis

@Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
    @Autowired
    private QuestionAnswerRecordRepository questionAnswerRecordRepository;

    public void testInit() {
        Integer lessonId = 7777, classroomId = 6666, questionId = 5555;
        String[] aList = {"A", "B", "C", "D", "MISS"};
        List<QuestionAnswerRecord> saveListTemp = new ArrayList<>();
        questionAnswerRecordRepository.deleteAll();
        Date start = null;
        for (int i = 0; i < 100; i++) {
            if (null == start) {
                start = new Date();
            }
            QuestionAnswerRecord t = new QuestionAnswerRecord();
            t.setLessonId(lessonId);
            t.setClassroomId(classroomId);
            t.setSmallClassId(8888);
            t.setQuestionId(questionId);
            t.setAnswerHead(aList[i % aList.length]);
            if (i % aList.length == 0) {
                t.setIsRight(Const.YES);
            } else {
                t.setIsRight(Const.NO);
            }
            t.setStudentId(i * 10);
            t.setStudentName(String.format("I am %s", t.getId()));
            t.setCreateDate(DateUtil.getNow());
            t.setId(String.format("%s:%s:%s", t.getLessonId(), t.getQuestionId(), t.getStudentId()));
            saveListTemp.add(t);
        }
        questionAnswerRecordRepository.save(saveListTemp);
    }

    public void test() {
        // OK 指定 Document 對應的 indexName
        Aggregations aggregations = elasticsearchTemplate.query(
                new NativeSearchQueryBuilder()
                        .withIndices("question_answer_record")
                        .withQuery(QueryBuilders.matchAllQuery())
                        .addAggregation(AggregationBuilders.terms("termsData").field("answerCid"))
                        .build()
                , SearchResponse::getAggregations);
        Terms termsData1 = aggregations.get("termsData");
        if (!(termsData1 instanceof UnmappedTerms)) {
            for (Terms.Bucket actionTypeBucket : termsData1.getBuckets()) {
                System.out.println(actionTypeBucket.getKeyAsString() + "||" + actionTypeBucket.getDocCount());
            }
        }
        System.out.println("---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ");
        // OK 使用 Document 對應的 ElasticsearchRepository
        AggregatedPage<QuestionAnswerRecord> answerRecords = (AggregatedPage<QuestionAnswerRecord>) questionAnswerRecordRepository.search(
                new NativeSearchQueryBuilder()
                        .withQuery(QueryBuilders.matchAllQuery())
                        .addAggregation(AggregationBuilders.terms("termsData").field("answerCid"))
                        .build()
        );
        if (answerRecords.hasAggregations()) {
            Terms termsData2 = answerRecords.getAggregations().get("termsData");
            if (!(termsData2 instanceof UnmappedTerms)) {
                for (Terms.Bucket actionTypeBucket : termsData2.getBuckets()) {
                    System.out.println(actionTypeBucket.getKeyAsString() + "||" + actionTypeBucket.getDocCount());
                }
            }
        }
        System.out.println("---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ");
        // 注意 全局查詢 容易出現髒讀
        Aggregations aggregationsError = elasticsearchTemplate.query(
                new NativeSearchQueryBuilder()
                        //.withIndices("user_answer_dashboard_v1")
                        .withQuery(QueryBuilders.matchAllQuery())
                        .addAggregation(AggregationBuilders.terms("termsData").field("answerCid"))
                        .build()
                , SearchResponse::getAggregations);
        Terms termsDataError = aggregationsError.get("termsData");
        if (!(termsDataError instanceof UnmappedTerms)) {
            for (Terms.Bucket actionTypeBucket : termsDataError.getBuckets()) {
                System.out.println(actionTypeBucket.getKeyAsString() + "||" + actionTypeBucket.getDocCount());
            }
        }
        return StatusResponse.ok();
    }

~~spring

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息