写在前面
ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于[云计算]中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。
可以这样来对比elasticsearch和数据库
索引(indices) ~~~~~~~~ 数据库(databases)
类型(type) ~~~~~~~~ 数据表(table)
文档(Document)~~~~~~~~ 行(row)
字段(Field) ~~~~~~~~ 列(Columns )
shards:分片数量,默认5
replicas:副本数量,默认1
引入库
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-data-elasticsearch', version: '2.7.5' implementation group: 'org.springframework.data', name: 'spring-data-elasticsearch', version: '4.4.17';
版本对应要求见如下表格: 传送门
Spring Data Release Train | Spring Data Elasticsearch | Elasticsearch | Spring Framework | Spring Boot |
2023.0 (Ullmann) | 5.1.x | 8.7.1 | 6.0.x | 3.1.x |
2022.0 (Turing) | 5.0.x | 8.5.3 | 6.0.x | 3.0.x |
2021.2 (Raj) | 4.4.x[1] | 7.17.3 | 5.3.x | 2.7.x |
2021.1 (Q) | 4.3.x[1] | 7.15.2 | 5.3.x | 2.6.x |
2021.0 (Pascal) | 4.2.x[1] | 7.12.0 | 5.3.x | 2.5.x |
2020.0 (Ockham) | 4.1.x[1] | 7.9.3 | 5.3.2 | 2.4.x |
Neumann | 4.0.x[1] | 7.6.2 | 5.2.12 | 2.3.x |
Moore | 3.2.x[1] | 6.8.12 | 5.2.12 | 2.2.x |
Lovelace | 3.1.x[1] | 6.2.2 | 5.1.19 | 2.1.x |
Kay | 3.0.x[1] | 5.5.0 | 5.0.13 | 2.0.x |
Ingalls | 2.1.x[1] | 2.4.0 | 4.3.25 | 1.5.x |
配置连接
spring: elasticsearch: rest: uris: - 10.10.80.162:9200 - 10.10.80.163:9200 - 10.10.80.164:9200 username: root password: ******** timeout: 60000
配置信息读取:
@RefreshScope @ConfigurationProperties(ESProperties.PREFIX) public class ESProperties { public static final String PREFIX = "spring.elasticsearch.rest"; private Boolean enable = true; private String[] uris; private String userName; /** * Secret key是你账户的密码 */ private String password; private String deviceId; /**请求超时*/ private long timeout; }
连接初始化:
@AutoConfiguration @EnableConfigurationProperties(ESProperties.class) @ConditionalOnProperty(value = ESProperties.PREFIX + ".enabled", havingValue = "true", matchIfMissing = true) public class ElasticSearchConfig extends AbstractElasticsearchConfiguration { private static final Logger logger = LogManager.getLogger(ElasticSearchConfig.class); @Resource private ESProperties esProperties; @Override @Bean(destroyMethod = "close") public RestHighLevelClient elasticsearchClient() { final ClientConfiguration clientConfiguration = ClientConfiguration.builder() .connectedTo(esProperties.getUris()) .withBasicAuth(esProperties.getUserName(), esProperties.getPassword()) .withConnectTimeout(RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MILLIS) .withSocketTimeout(esProperties.getTimeout()) .build(); RestHighLevelClient client = RestClients.create(clientConfiguration).rest(); try { logger.info("connect to elasticsearch:{} ", client.getLowLevelClient().getNodes()); MainResponse response = client.info(RequestOptions.DEFAULT); MainResponse.Version version = response.getVersion(); logger.info("elasticsearch version:{},lucene version:{}", version.getNumber(), version.getLuceneVersion()); } catch (Exception e) { throw new RuntimeException(e); } return client; } }
文件配置 spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=io.gamioo.core.elasticsearch.config.ElasticSearchConfig
DAO层:
public interface OperationLogRepository extends ElasticsearchRepository<OperationLog, String> { //时间匹配 Page<OperationLog> findByAddTimeBetween(LocalDateTime startTime, LocalDateTime endTime, Pageable pageable); //字段后匹配 Page<OperationLog> findByDeviceIdLike(String deviceId, Pageable pageable); } //字段前后匹配 Page<OperationLog> findByDeviceIdContaining(String deviceId, Pageable pageable); }
更复杂一点的操作
我们可以使用@Query注解进行查询,这样要求我们需要自己写ES的查询语句,需要会ES查询才可以,其实也很简单,不会写查就是了。 看看官方给的例子
public interface OperationLogRepository extends ElasticsearchRepository<OperationLog, String> { @Query("{\"bool\" : {\"must\" : {\"field\" : {\"name\" : \"?0\"}}}}") Page<OperationLog> findByName(String name,Pageable pageable); }
操作对象:
@ApiModel(value = "operation_log", description = "操作日志") @Document(indexName = "operation_log_*") public class OperationLog { @Id private String id; @ApiModelProperty("名称") @Field(name = "name") private String name; @ApiModelProperty("创建时间") @Field(name = "addTime", type = FieldType.Date, format = DateFormat.custom, pattern = "yyyy-MM-dd'T'HH:mm:ssz") private Date addTime; }
逻辑操作:
@Resource private OperationLogRepository repository; @Override public Page<OperationLog> findAll(Pageable pageable) { return repository.findAll(pageable); } @Override public Page<OperationLog> findByAddTimeBetween(LocalDateTime startTime, LocalDateTime endTime, Pageable pageable) { return repository.findByAddTimeBetween(startTime, endTime, pageable); }
用JPA的话,返回的是整条数据,就会抛出数据量过大的问题,但有时候你只需要部分极少的字段,那时我们就需要自定义EQL进行复杂查询:
public Page<OperationLog> findByAndAddTimeBetweenAndDevUidIn(LocalDateTime startTime, LocalDateTime endTime, List<String> devUid, Pageable pageable) { /* 1 查询结果 */ NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder(); // 1.2 source过滤 builder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "deviceId", "channel", "fileSize"}, new String[0])); // 1.3 搜索条件 BoolQueryBuilder query = QueryBuilders.boolQuery().must(QueryBuilders.termsQuery("deviceId", devUid)).must(QueryBuilders.rangeQuery("created") // .format("yyyy-MM-dd'T'HH:mm:ss.SSSXXXZ") // .format("yyyy-MM-dd HH:mm:ss") .timeZone("GMT+8") .gte(startTime) .lte(endTime) .includeLower(true).includeUpper(true)); //时间范围 builder.withQuery(query); // builder.withQuery(QueryBuilders.matchQuery("title", "手机")); // 1.4 分页及排序条件 builder.withPageable(pageable); SearchHits<OperationLog> searchHits = template.search(builder.build(), OperationLog.class); List<OperationLog> array = new ArrayList<>(); for (SearchHit<OperationLog> e : searchHits) { array.add(e.getContent()); } return new PageImpl<>(array, pageable, searchHits.getTotalHits()); }
其中,QueryBuilders是ES中的查询条件构造器
类型 | 说明 |
QueryBuilders.boolQuery | 子方法must可多条件联查 |
QueryBuilders.termQuery | 精确查询指定字段 |
QueryBuilders.matchQuery | 按分词器进行模糊查询 |
QueryBuilders.rangeQuery | 按指定字段进行区间范围查询 |
Q&A
1.实际使用中一直报错: missing authentication credentials for REST request
经过多方查证,最后发现报错原因是:配置ES时没添加用户名密码验证
2.org.apache.http.ContentTooLongException: entity content is too long [450975428] for the configured buffer limit [104857600]
3.如何打印ES日志
#es日志 <Logger name="org.springframework.data.elasticsearch.client.WIRE" level="trace" includeLocation="true"> </Logger>
4.NativeSearchQueryBuilder多条件查询,会覆盖前面的条件
// 1.3 搜索条件 query.withQuery(QueryBuilders.termsQuery("devUid", devUid)); //时间范围 query.withQuery(QueryBuilders.rangeQuery("created") .format("yyyy-MM-dd'T'HH:mm:ssz") .timeZone("GMT+8") .gte(startTime) .lte(endTime) .includeLower(true).includeUpper(true));
调用了多个withQuery,通过底层的代码可以看出会覆盖前面withQuery,只保留一个withQuery,因为源码实现这样的,
public NativeSearchQueryBuilder withQuery(QueryBuilder queryBuilder) { this.queryBuilder = queryBuilder; return this; }
所以得改成如下:
BoolQueryBuilder query = QueryBuilders.boolQuery().must(QueryBuilders.termsQuery("devUid", devUid)).must(QueryBuilders.rangeQuery("created") .format("yyyy-MM-dd'T'HH:mm:ssz") .timeZone("GMT+8") .gte(startTime) .lte(endTime) .includeLower(true).includeUpper(true)); //时间范围 builder.withQuery(query);
- Jet Brains DataGrip 连接到 Elasticsearch 目前指定的版本或许可以连上,但自己定义的版本却连不上,有谁可以帮我?
驱动下载地址为:https://www.elastic.co/cn/downloads/jdbc-client
总结
以上就是springboot集成es后的一个简单使用,spring封装过后的spring-boot-starter-data-elasticsearch使用起来还是非常方便简单的。
JPA自带的这些方法肯定是不能满足我们的业务需求的,那么我们如何自定义方法呢?我们只要使用特定的单词对方法名进行定义,那么Spring就会对我们写的方法名进行解析, 生成对应的实例进行数据处理,有木有很简单?那么接下来就使用Spring官方文档中的实例进行演示,先来看下关键字的说明
附上JPA操作的表:
关键字 | 使用示例 | 等同于的ES查询 |
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” : “?”}}}} |
Between | findByPriceBetween | {“bool” : {“must” : {“range” : {“price” : {“from” : ?,“to” : ?,“include_lower” : true,“include_upper” : true}}}}} |
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” : “?”}}}}}} |
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}}}} |
参考: