命令查询职责分离模式(Command Query Responsibility Segregation,CQRS)从业务上分离修改 (Command,增,删,改,会对系统状态进行修改)和查询(Query,查,不会对系统状态进行修改)的行为。从而使得逻辑更加清晰,便于对不同部分进行针对性的优化。
CQRS有以下几点有点:
因此Command使用数据库,Query使用效率查询效率更高的Elasticsearch。
如何确保数据库和Elasticsearch的数据的一致性?
当老数据库有大量数据需要导入Elasticsearch时,可参考博客: http://www.wisely.top/2018/02/24/spring-batch-elasticsearch/
Spring Data Elasticsearch使用的是transport client,而Elasticsearch官网推荐使用REST client。阿里云的Elasticsearch使用transport client目前还在存在问题,阿里云推荐使用REST client。
本示例使用的是Spring Data Jest链接Elasticsearch(目前只有spring boot2.0以上版本支持),Elasticsearch的版本为:5.5.3
<dependency> <groupId>com.github.vanroy</groupId> <artifactId>spring-boot-starter-data-jest</artifactId> <version>3.0.0.RELEASE</version> </dependency> <dependency> <groupId>io.searchbox</groupId> <artifactId>jest</artifactId> <version>5.3.2</version> </dependency>
spring: data: jest: uri: http://127.0.0.1:9200 username: elastic password: changeme
以简单的实体类为例
package com.hfcsbc.esetl.domain; import lombok.Data; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.OneToOne; import java.util.Date; import java.util.List; /** * Create by pengchao on 2018/2/23 */ @Document(indexName = "person", type = "person", shards = 1, replicas = 0, refreshInterval = "-1") @Entity @Data public class Person { @Id private Long id; private String name; @OneToOne @Field(type = FieldType.Nested) private List<Address> address; private Integer number; private Integer status; private Date birthDay; }
package com.hfcsbc.esetl.domain; import lombok.Data; import javax.persistence.Entity; import javax.persistence.Id; /** * Create by pengchao on 2018/2/23 */ @Entity @Data public class Address { @Id private Long id; private String name; private Integer number; }
BoolQueryBuilder orderStatusCondition = QueryBuilders.boolQuery() .should(QueryBuilders.termQuery("status", 1)) .should(QueryBuilders.termQuery("status", 2)) .should(QueryBuilders.termQuery("status", 3)) .should(QueryBuilders.termQuery("status", 4)) .should(QueryBuilders.termQuery("status", 5));
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery(); queryBuilder .must(queryBuilder1) .must(queryBuilder2) .must(queryBuilder3);
QueryBuilder rangeQuery = QueryBuilders.rangeQuery("birthDay").from(yesterday).to(today);
QueryBuilder queryBuilder = QueryBuilders.nestedQuery("nested", QueryBuilders.termQuery("address.id", 100001), ScoreMode.None);
ScoreMode: 定义other join side中score是如何被使用的。如果不关注scoring,我们只需要设置成ScoreMode.None,此种方式会忽略评分因此会更高效和节约内存
SumAggregationBuilder sumBuilder = AggregationBuilders.sum("sum").field("number"); SearchQuery searchQuery = new NativeSearchQueryBuilder() .withIndices(QUERY_INDEX) .withTypes(QUERY_TYPE) .withQuery(boolQueryBuilder) .addAggregation(sumBuilder).build(); AggregatedPage<ParkingOrder> account = (AggregatedPage<ParkingOrder>) esParkingOrderRepository.search(EsQueryBuilders.buildYesterdayArrearsSumQuery(employeeId)); int sum = account.getAggregation("sum", SumAggregation.class).getSum().intValue();
SumAggregationBuilder sumBuilder = AggregationBuilders.sum("sum").field("adress.num"); AggregationBuilder aggregationBuilder = AggregationBuilders.nested("nested", "adress").subAggregation(sumBuilder); SearchQuery searchQuery = new NativeSearchQueryBuilder() .withIndices(QUERY_INDEX) .withTypes(QUERY_TYPE) .withQuery(boolQueryBuilder) .addAggregation((AbstractAggregationBuilder) aggregationBuilder).build(); AggregatedPage<ParkingOrder> account = (AggregatedPage<ParkingOrder>) esParkingOrderRepository.search(EsQueryBuilders.buildYesterdayArrearsSumQuery(employeeId)); int sum = account.getAggregation("nested", SumAggregation.class).getAggregation("sum", SumAggregation.class).getSum().intValue();