spring-boot 1.5.10.RELEASEjava
... ... <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> ... ...
# # ES # spring.data.elasticsearch.cluster-name=elasticsearch-dev spring.data.elasticsearch.cluster-nodes=47.94.5.129:9300 spring.data.elasticsearch.repositories.enabled=true
@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; ... ... }
@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); }
@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); } } }
@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()); }
@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); } } }
/** * 先根據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查詢語句 |
---|---|---|
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