有关elasticsearch + kibana

Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

index ==》索引 ==》Mysql中的一个库,库里面可以建立很多表,存储不同类型的数据,而表在ES中就是type。

type ==》类型 ==》相当于Mysql中的一张表,存储json类型的数据

document  ==》文档 ==》一个文档相当于Mysql一行的数据

field ==》列 ==》相当于mysql中的列,也就是一个属性

 

二 springboot 对应的Es版本关系

springboot  elasticsearch
2.0.0.RELEASE 2.2.0
1.4.0.M1 1.7.3
1.3.0.RELEASE 1.5.2
1.2.0.RELEASE 1.4.4
1.1.0.RELEASE 1.3.2
1.0.0.RELEASE 1.1.1

三 环境构建

maven依赖:前提是依赖

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>1.5.9.RELEASE</version>
   <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

配置文件:

# ES

#开启 Elasticsearch 仓库(默认值:true)

spring.data.elasticsearch.repositories.enabled=true

#默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口

spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300

#spring.data.elasticsearch.cluster-name Elasticsearch 集群名(默认值: elasticsearch)

#spring.data.elasticsearch.cluster-nodes 集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点

#spring.data.elasticsearch.propertie 用来配置客户端的额外属性

#存储索引的位置

spring.data.elasticsearch.properties.path.home=/data/project/target/elastic

#连接超时的时间

spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s

 

 四 es索引实体类

Spring-data-elasticsearch为我们提供了@Document@Field等注解,如果某个实体需要建立索引,只需要加上这些注解即可

1.类上注解:@Document (相当于Hibernate实体的@Entity/@Table)(必写),加上了@Document注解之后默认情况下这个实体中所有的属性都会被建立索引、并且分词。

类型 属性名 默认值 说明
String indexName 索引库的名称,建议以项目的名称命名
String type “” 类型,建议以实体的名称命名
short shards 5 默认分区数
short replica 1 每个分区默认的备份数
String refreshInterval “1s” 刷新间隔
String indexStoreType “fs” 索引文件存储类型

2.主键注解:@Id (相当于Hibernate实体的主键@Id注解)(必写)

只是一个标识,并没有属性。

3.属性注解 @Field (相当于Hibernate实体的@Column注解)

@Field默认是可以不加的,默认所有属性都会添加到ES中。

加上@Field之后,@document默认把所有字段加上索引失效,只有家@Field 才会被索引(同时也看设置索引的属性是否为no)

 

类型 属性名 默认值 说明
FieldType type FieldType.Auto 自动检测属性的类型
FieldIndex index FieldIndex.analyzed 默认情况下分词
boolean store false 默认情况下不存储原文
String searchAnalyzer “” 指定字段搜索时使用的分词器
String indexAnalyzer “” 指定字段建立索引时指定的分词器
String[] ignoreFields {} 如果某个字段需要被忽略
 

五 相关查询方法

 官网参考

  实现方式比较多,已经存在的接口,使用根据需要继承即可:

  1、CrudRepository接口 

public interface CrudRepository<T, ID extends Serializable>
  extends Repository<T, ID> {

  <S extends T> S save(S entity);      

  Optional<T> findById(ID primaryKey); 

  Iterable<T> findAll();               

  long count();                        

  void delete(T entity);               

  boolean existsById(ID primaryKey);   

  // … more functionality omitted.
}

 

 

  2、PagingAndSortingRepository接口

public interface PagingAndSortingRepository<T, ID extends Serializable>
  extends CrudRepository<T, ID> {

  Iterable<T> findAll(Sort sort);

  Page<T> findAll(Pageable pageable);
}

 

 

 例子:

分页:

PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));

 

计数:

interface UserRepository extends CrudRepository<User, Long> {
  long countByLastname(String lastname);
}  


3.自定义查询实现

只要使用特定的单词对方法名进行定义,那么Spring就会对我们写的方法名进行解析, 

该机制条前缀find…Byread…Byquery…Bycount…By,和get…By从所述方法和开始分析它的其余部分。

引入子句可以包含进一步的表达式,如Distinct在要创建的查询上设置不同的标志。然而,

第一个By作为分隔符来指示实际标准的开始。在非常基础的层次上,您可以定义实体属性的条件并将它们与And和连接起来Or

interface PersonRepository extends Repository<User, Long> {

  List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);

  // Enables the distinct flag for the query
  List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
  List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);

  // Enabling ignoring case for an individual property
  List<Person> findByLastnameIgnoreCase(String lastname);
  // Enabling ignoring case for all suitable properties
  List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);

  // Enabling static ORDER BY for a query
  List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
  List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
}

构建查询属性算法原理

如上例所示。在查询创建时,确保解析的属性托管类的属性

但是,你也可以通过遍历嵌套属性来定义约束。

假设Person  x有一个Address和 ZipCode。在这种情况下,方法名称为

List<Person> findByAddressZipCode(ZipCode zipCode);

创建属性遍历x.address.zipCode

解析算法如下:

  1. 首先将整个part(AddressZipCode)作为属性进行解释,然后检查具有该名称属性的类。如果皮匹配成功,则使用该属性。
  2. 如果不是属性,则算法拆分从右侧的驼峰部分头部和尾部,并试图找出相应的属性,在我们的例子,AddressZipCode。如果算法找到具有该头部的属性,它将采用尾部并继续从那里构建树,然后按照刚刚描述的方式分割尾部。
  3. 如果第一个分割不匹配,则算法将分割点移动到左侧AddressZipCode)并继续。

虽然这应该适用于大多数情况,但算法仍可能会选择错误的属性。假设这个Person类也有一个addressZip属性。

该算法将在第一轮拆分中匹配,并且基本上选择错误的属性并最终失败(因为addressZip可能没有code属性的类型)。

为了解决这个歧义,你可以在你的方法名称中使用手动定义遍历点。所以我们的方法名称会像这样结束:

List<Person> findByAddress_ZipCode(ZipCode zipCode)由于我们将下划线视为保留字符,因此我们强烈建议遵循标准的Java命名约定(即,不要在属性名称中使用下划线,而应使用驼峰大小写)

Page<User> findByLastname(String lastname, Pageable pageable);

Slice<User> findByLastname(String lastname, Pageable pageable);

List<User> findByLastname(String lastname, Sort sort);



//也可以用Java8 Stream查询和sql语句查询

@Query("select u from User u")
Stream<User> findAllByCustomQueryAndStream();
 
Stream<User> readAllByFirstnameNotNull();
 
@Query("select u from User u")
Stream<User> streamAllPaged(Pageable pageable);

有些在复杂的可以使用es查询语句

我们可以使用@Query注解进行查询,这样要求我们需要自己写ES的查询语句


public interface BookRepository extends ElasticsearchRepository<Book, String> {
    @Query("{"bool" : {"must" : {"field" : {"name" : "?0"}}}}")
    Page<Book> findByName(String name,Pageable pageable);
}

方法和es查询转换:

Keyword Sample Elasticsearch Query String

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(Collection<String>names)

{"bool" : {"must" : {"bool" : {"should" : [ {"field" : {"name" : "?"}}, {"field" : {"name" : "?"}} ]}}}}

NotIn

findByNameNotIn(Collection<String>names)

{"bool" : {"must_not" : {"bool" : {"should" : {"field" : {"name" : "?"}}}}}}

Near

findByStoreNear

Not Supported Yet !

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}}}}

 

六 between使用注意

  在使用的时候没有找到直接的例子,由于between是转换成range,所以需要范围参数from和to

 举例如下:

Page<Recruit> findByRecruitWorkAndRecruitCitysAndWorkTypeAndXjTimeBetween(
String recruitWork, 
String recruitCitys, 
Integer workType, 
Date fromXjTime, 
Date toXjTime,
Pageable pageable);

注意:

只要使用了between参数,****XjTimeBetween(......,from,to) 必须要传递范围参数from,to,不能同时为空

否则异常:因为底层要求参数不能同时为空

七  es时间类型注意

来源: http://www.cnblogs.com/guozp/p/8686904.html 

对于Elasticsearch原生支持date类型,json格式通过字符来表示date类型。所以在

用json提交日期至elasticsearch的时候,es会隐式转换,把es认为是date类型的字符串直接转为date类型

字段内容实际上就是转换成long类型作为内部存储的(所以完全可以接受其他时间格式作为时间字段的内容)。至于什么样的字符串es会认为可以转换成date类型,参考elasticsearch官网介绍

date类型是包含时区信息的,如果我们没有在json代表日期的字符串中显式指定时区,对es来说没什么问题,但是对于我们来说可能会发现一些时间差8个小时的问题。

Elastic本身的时间格式为ISO8601标准,其形式如"2016-01-25T00:00:00"。具体时间日期格式要求可以参见es官方文档

我们在计算日期间隔,甚至按日分类的时候,往往需要把这个String时间转化为Unix时间戳(Unix Timestamp(时间戳))的形式,再进行计算。

而通常,这个时间戳会以毫秒的形式(Java)保存在一个long类型里面,这就涉及到了String与long类型的相互转化。

此外在使用Java Client聚合查询日期的时候,需要注意时区问题,因为默认的es是按照UTC标准时区算的,所以不设置的聚合统计结果是不正确的。默认不设置时区参数,es是安装UTC的时间进行查询的,所以分组的结果可能与预期不一样。  

JSON 没有日期类型,因此在 Elasticsearch 中可以表达成:

  1. 日期格式化的字符串,比如: "2018-01-01" 或者 "2018/01/01 01:01:30";
  2. 毫秒级别的 long 类型或秒级别的 integer 类型,比如: 1515150699465, 1515150699;

实际上不管日期以何种格式写入,在 ES 内部都会先穿换成 UTC 时间并存储为 long 类型。日期格式可以自定义,如果没有指定的话会使用以下的默认格式:

  "strict_date_optional_time||epoch_millis"

 

  因此总结来说,不管哪种可以表示时间的格式写入,都可以用来表示时间

所以这里引出多种解决方案:

  1. es 默认的是 utc 时间,而国内服务器是 cst 时间,首先有时间上的差距需要转换。但是如果底层以及上层都统一用时间戳,完美解决时区问题。但是时间戳对我们来说不直观 
  2. 我们在往es提交日期数据的时候,直接提交带有时区信息的日期字符串,如:“2016-07-15T12:58:17.136+0800”
  3. 直接设置format为你想要的格式,比如 "yyyy-MM-dd HH:mm:ss" 然后存储的时候,指定格式,并且Mapping  也是指定相同的format 。
@Field( 
    type = FieldType.Date,
    format = DateFormat.custom,
    pattern = "date_optional_time"
)
private Date gmtCreate;

我这里是数据是从数据库直接读取,使用的datetime类型,原来直接使用的时候,抛异常

  MapperParsingException[failed to parse [***]]; nested: IllegalArgumentException[Invalid format: "格式"];

原因: jackson库在转换为json的时候,将Date类型转为为了long型的字符串表示,而我们定义的是date_optional_time格式的字符串,所以解析错误,

解决办法

去掉注解中的format=DateFormat.date_optional_time

让其使用默认的格式,也就是 'strict_date_optional_time||epoch_millis' , 既能接受 date_optional_time格式的,也能接受epoch_millis格式,由于为了查看更直观感受改为如下:

 @Field( 
        type = FieldType.Date,
        format = DateFormat.custom,
        pattern = "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis"
  )
private Date gmtCreate;

 改成这样后,底层多种格式都可以存储,如果没有根据时间进行范围查找,这里基本上已经就告一段落了。

时间范围查找需求:存储Date,和取出来也是Date

存储的时候利用各种JSON对象,如 Jackson 等。存储的时候就可以用JSON Format一下再存储,然后取出来后

用@jsonFormat注解,有了这个注解后,

    timezone="GMT+8" 主要是因为底层存放的数据日期时区是UTC,这里转换成GMT
@Field( 
    type = FieldType.Date,
    format = DateFormat.custom,
    pattern = "yyyy-MM-dd HH:mm:ss"
)
@JsonFormat (
    shape = JsonFormat.Shape.STRING, 
    pattern ="yyyy-MM-dd HH:mm:ss",
    timezone="GMT+8"
)
    private Date xjTime;

时间范围查找需求注意:

    根据条件查询的时候,时间范围需要传入range,这里涉及到了两种选择,底层查询方法实现的时候range的参数为

 

 

解决:

办法1.传入的date参数格式化成底层的类型 

实现参考:

Page<Recruit> findByRecruitWorkAndRecruitCitysAndWorkTypeAndXjTimeBetween(
String recruitWork, 
String recruitCitys, 
Integer workType, 
Date fromXjTime, 
Date toXjTime,
Pageable pageable);

办法2:参数直接使用string,避免上层转换成不合适的时间格式,使用框架底层自己转换,避免错误。

实现参考:

Page<Recruit> findByRecruitWorkAndRecruitCitysAndWorkTypeAndXjTimeBetween(
String recruitWork, 
String recruitCitys, 
Integer workType, 
String fromXjTime, 
String toXjTime,
Pageable pageable);

八 使用注意

  个人认为springboot 这种集成es的方法,最大的优点是开发速度快,不要求对es一些api要求熟悉,能快速上手,即使之前对es不胜了解,也能通过方法名或者sql快速写出自己需要的逻辑,而具体转换成api层的操作,则有框架底层帮你实现。

  缺点也显而易见首先,使用的springboot的版本对es的版本也有了要求,不能超过es的某些版本号,部署时需要注意。第二,速度提升的同时,也失去了一些实用api的灵活性。一些比较灵活的条件封装不能很容易的实现。各有利弊,各位权衡

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章