Spring Data

Spring Data

Spring Data Redis - Could not safely identify store assignment for repository

回复

Spring Datawayne 发起了问题 • 1 人关注 • 0 个回复 • 174 次浏览 • 2017-09-04 13:45 • 来自相关话题

Spring Data ElasticSearch 要视频吗?

Spring Data泥瓦匠BYSocket 回复了问题 • 14 人关注 • 14 个回复 • 998 次浏览 • 2017-07-21 00:31 • 来自相关话题

Spring Data JPA系列:数据更新(Update)

Spring Datawayne 发表了文章 • 0 个评论 • 451 次浏览 • 2017-07-11 11:15 • 来自相关话题

上次通过《Spring Data JPA系列:使用@Modifying修改(Modifying queries)》介绍了数据更新的方式,这种更新方式会很不方便,写的时候也比较麻烦,可以为更新密码、更新用户名等一些特殊的更新单独定义,但是对大多数数据操作是不方便的,比如我要更新一条有一百个字段的数据,这时候如果要通过Modifying方式就非常的不方便,因此,我们需要一种新的方式来解救。
通过阅读Spring-Data-JPA相关的文档和博客,找到了对应的解决方案,就是使用`save()`方法,经过测试,可用。
我们平时对`save()`方法的理解,大多是等同于`insert()`,主要是指新增一条数据,而JPA的`save()`方法包含了`merge()`的概念,就是说,如果save的对象不存在primary key或者primary key值在database内不存在的时候会新添加一条数据,如果primary key 存在并且primary key已经在database中存在,那就会依据primary key对该条数据进行更新,这是我们乐意见到的。

参考的文章:```java
https://stackoverflow.com/ques ... a-jpa
```

相关描述如下:

Identity of entities is defined by their primary keys. Since firstname and lastname are not parts of the primary key, you cannot tell JPA to treat Users with the same firstnames and lastnames as equal if they have different userIds.

So, if you want to update a User identified by its firstname and lastname, you need to find that User by a query, and then change appropriate fields of the object your found. These changes will be flushed to the database automatically at the end of transaction, so that you don't need to do anything to save these changes explicitly.

##### EDIT:

Perhaps I should elaborate on overall semantics of JPA. There are two main approaches to design of persistence APIs:

- insert/update approach. When you need to modify the database you should call methods of persistence API explicitly: you call insert to insert an object, or update to save new state of the object to the database.
- Unit of Work approach. In this case you have a set of objects managed by persistence library. All changes you make to these objects will be flushed to the database automatically at the end of Unit of Work (i.e. at the end of the current transaction in typical case). When you need to insert new record to the database, you make the corresponding object managed. Managed objects are identified by their primary keys, so that if you make an object with predefined primary key managed, it will be associated with the database record of the same id, and state of this object will be propagated to that record automatically.

JPA follows the later approach. save() in Spring Data JPA is backed by merge() in plain JPA, therefore it makes your entity managed as described above. It means that calling save() on an object with predefined id will update the corresponding database record rather than insert a new one, and also explains why save() is not called create().
 
 
参考:
官方文档,https://docs.spring.io/spring- ... /html 
DEMO,https://github.com/icnws/spring-data-jpa-demo
个人博客,http://www.icnws.com

版权归作者所有,如需转载,请与作者联系! 查看全部
上次通过《Spring Data JPA系列:使用@Modifying修改(Modifying queries)》介绍了数据更新的方式,这种更新方式会很不方便,写的时候也比较麻烦,可以为更新密码、更新用户名等一些特殊的更新单独定义,但是对大多数数据操作是不方便的,比如我要更新一条有一百个字段的数据,这时候如果要通过Modifying方式就非常的不方便,因此,我们需要一种新的方式来解救。
通过阅读Spring-Data-JPA相关的文档和博客,找到了对应的解决方案,就是使用`save()`方法,经过测试,可用。
我们平时对`save()`方法的理解,大多是等同于`insert()`,主要是指新增一条数据,而JPA的`save()`方法包含了`merge()`的概念,就是说,如果save的对象不存在primary key或者primary key值在database内不存在的时候会新添加一条数据,如果primary key 存在并且primary key已经在database中存在,那就会依据primary key对该条数据进行更新,这是我们乐意见到的。

参考的文章:```java
https://stackoverflow.com/ques ... a-jpa
```

相关描述如下:

Identity of entities is defined by their primary keys. Since firstname and lastname are not parts of the primary key, you cannot tell JPA to treat Users with the same firstnames and lastnames as equal if they have different userIds.

So, if you want to update a User identified by its firstname and lastname, you need to find that User by a query, and then change appropriate fields of the object your found. These changes will be flushed to the database automatically at the end of transaction, so that you don't need to do anything to save these changes explicitly.

##### EDIT:

Perhaps I should elaborate on overall semantics of JPA. There are two main approaches to design of persistence APIs:

- insert/update approach. When you need to modify the database you should call methods of persistence API explicitly: you call insert to insert an object, or update to save new state of the object to the database.
- Unit of Work approach. In this case you have a set of objects managed by persistence library. All changes you make to these objects will be flushed to the database automatically at the end of Unit of Work (i.e. at the end of the current transaction in typical case). When you need to insert new record to the database, you make the corresponding object managed. Managed objects are identified by their primary keys, so that if you make an object with predefined primary key managed, it will be associated with the database record of the same id, and state of this object will be propagated to that record automatically.

JPA follows the later approach. save() in Spring Data JPA is backed by merge() in plain JPA, therefore it makes your entity managed as described above. It means that calling save() on an object with predefined id will update the corresponding database record rather than insert a new one, and also explains why save() is not called create().
 
 
参考:
官方文档,https://docs.spring.io/spring- ... /html 
DEMO,https://github.com/icnws/spring-data-jpa-demo
个人博客,http://www.icnws.com


版权归作者所有,如需转载,请与作者联系!


关于Spring jpa得到的Page<T>的分页问题

Spring Bootmaling 回复了问题 • 2 人关注 • 1 个回复 • 229 次浏览 • 2017-06-29 14:47 • 来自相关话题

spring-data-es 视频公开课(二)《spring-data-elasticsearch 简单案例搭建》

开源项目泥瓦匠BYSocket 发表了文章 • 5 个评论 • 1469 次浏览 • 2017-06-27 20:46 • 来自相关话题

spring-data-es 视频公开课(二)《spring-data-elasticsearch 简单案例搭建》
视频如下:







一、前言
a. 版权声明 
本视频由 泥瓦匠BYSocket 支持 Spring For All 社区 www.spring4all.com ,版权为个人及社区所有,欢迎转发。
b. 视频主旨
学会 ElasticSearch 及 spring-data-elasticsearch项目实战教学

视频系列目录
spring-data-es 视频公开课(一)《elasticsearch 及 head 插件安装使用》spring-data-es 视频公开课(二)《spring-data-elasticsearch 简单案例搭建》spring-data-es 视频公开课(三)《spring-data-elasticsearch 实战案例搭建》

二、正文

本节提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、搭建 spring-data-elasticsearch-crud 工程

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
1.后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

2.添加 maven 依赖
pom.xml:$(document).ready(function() {$('pre code').each(function(i, block) { hljs.highlightBlock( block); }); });<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>


这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

3.新建启动 Application 类
Application.java:// Spring Boot 应用的标识
@SpringBootApplication
public class Application {
public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
SpringApplication.run(Application.class,args);
}
}

4.编写 ES 代码(重点)
application.properties
 配置 ES 地址# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)


ES domain 实体类
City.java/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}


注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase
 

ES 数据操作层
CityRepository.java/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}
接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot


城市 ES 业务接口类
CityService.java/**
* 城市 ES 业务接口类
*
*/
public interface CityService {
/**
* 新增 ES 城市信息
*
* @param city
* @return
*/
Long saveCity(City city);
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* @param description
* @return
*/
List<City> findByDescription(String description);
/**
* NOT 语句查询
*
* @param description
* @return
*/
List<City> findByDescriptionNot(String description);
/**
* LIKE 语句查询
*
* @param description
* @return
*/
List<City> findByDescriptionLike(String description);
}




城市 ES 业务逻辑实现类
CityESServiceImpl.java/**
* 城市 ES 业务逻辑实现类
* <p>
* Created by bysocket on 07/02/2017.
*/
@Service
public class CityESServiceImpl implements CityService {
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
// 分页参数 -> TODO 代码可迁移到具体项目的公共 common 模块
private static final Integer pageNumber = 0;
private static final Integer pageSize = 10;
Pageable pageable = new PageRequest(pageNumber, pageSize);
// ES 操作类
@Autowired
CityRepository cityRepository;
public Long saveCity(City city) {
City cityResult = cityRepository.save(city);
return cityResult.getId();
}
public List<City> findByDescriptionAndScore(String description, Integer score) {
return cityRepository.findByDescriptionAndScore(description, score);
}
public List<City> findByDescriptionOrScore(String description, Integer score) {
return cityRepository.findByDescriptionOrScore(description, score);
}
public List<City> findByDescription(String description) {
return cityRepository.findByDescription(description, pageable).getContent();
}
public List<City> findByDescriptionNot(String description) {
return cityRepository.findByDescriptionNot(description, pageable).getContent();
}
public List<City> findByDescriptionLike(String description) {
return cityRepository.findByDescriptionLike(description, pageable).getContent();
}
}




城市 Controller 实现 Restful HTTP 服务
CityRestController.java/**
* 城市 Controller 实现 Restful HTTP 服务
* <p>
* Created by bysocket on 03/05/2017.
*/
@RestController
public class CityRestController {
@Autowired
private CityService cityService;
/**
* 插入 ES 新城市
*
* @param city
* @return
*/
@RequestMapping(value = "/api/city", method = RequestMethod.POST)
public Long createCity(@RequestBody City city) {
return cityService.saveCity(city);
}
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
@RequestMapping(value = "/api/city/and/find", method = RequestMethod.GET)
public List<City> findByDescriptionAndScore(@RequestParam(value = "description") String description,
@RequestParam(value = "score") Integer score) {
return cityService.findByDescriptionAndScore(description, score);
}
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
@RequestMapping(value = "/api/city/or/find", method = RequestMethod.GET)
public List<City> findByDescriptionOrScore(@RequestParam(value = "description") String description,
@RequestParam(value = "score") Integer score) {
return cityService.findByDescriptionOrScore(description, score);
}
/**
* 查询城市描述
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/description/find", method = RequestMethod.GET)
public List<City> findByDescription(@RequestParam(value = "description") String description) {
return cityService.findByDescription(description);
}
/**
* NOT 语句查询
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/description/not/find", method = RequestMethod.GET)
public List<City> findByDescriptionNot(@RequestParam(value = "description") String description) {
return cityService.findByDescriptionNot(description);
}
/**
* LIKE 语句查询
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/like/find", method = RequestMethod.GET)
public List<City> findByDescriptionLike(@RequestParam(value = "description") String description) {
return cityService.findByDescriptionLike(description);
}
}


5.编译工程
mvn clean install

6.运行工程
右键运行 Application 应用启动类的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。

用 Postman 工具新增两个城市
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
    "id":"1",
    "score":"5",
    "name":"上海",
    "description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
    "id":"2",
    "score":"4",
    "name":"温岭",
    "description":"温岭是个沿海城市"
}

可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/。
 
下面是基本查询语句的接口:
a. 普通查询,查询城市描述
GET http://localhost:8080/api/city ... on%3D温岭
b. AND 语句查询
GET http://localhost:8080/api/city ... on%3D温岭&score=4
如果换成 score=5 ,就没有结果了。
c. OR 语句查询
GET http://localhost:8080/api/city ... on%3D上海&score=4
d. NOT 语句查询
GET http://localhost:8080/api/city ... on%3D温州
e. LIKE 语句查询
GET http://localhost:8080/api/city ... on%3D城市

三、视频小结
下一视频《spring-data-elasticsearch 实战案例搭建》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
  查看全部
spring-data-es 视频公开课(二)《spring-data-elasticsearch 简单案例搭建》
视频如下:








一、前言
a. 版权声明 
本视频由 泥瓦匠BYSocket 支持 Spring For All 社区 www.spring4all.com ,版权为个人及社区所有,欢迎转发。
b. 视频主旨
  • 学会 ElasticSearch 及 spring-data-elasticsearch
  • 项目实战教学


视频系列目录


二、正文

本节提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、搭建 spring-data-elasticsearch-crud 工程

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
1.后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

2.添加 maven 依赖
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>



这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

3.新建启动 Application 类
Application.java:
// Spring Boot 应用的标识
@SpringBootApplication
public class Application {
public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
SpringApplication.run(Application.class,args);
}
}


4.编写 ES 代码(重点)
application.properties
 配置 ES 地址
# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300

默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)


ES domain 实体类
City.java
/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}



注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase
 

ES 数据操作层
CityRepository.java
/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}

接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot


城市 ES 业务接口类
CityService.java
/**
* 城市 ES 业务接口类
*
*/
public interface CityService {
/**
* 新增 ES 城市信息
*
* @param city
* @return
*/
Long saveCity(City city);
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* @param description
* @return
*/
List<City> findByDescription(String description);
/**
* NOT 语句查询
*
* @param description
* @return
*/
List<City> findByDescriptionNot(String description);
/**
* LIKE 语句查询
*
* @param description
* @return
*/
List<City> findByDescriptionLike(String description);
}





城市 ES 业务逻辑实现类
CityESServiceImpl.java
/**
* 城市 ES 业务逻辑实现类
* <p>
* Created by bysocket on 07/02/2017.
*/
@Service
public class CityESServiceImpl implements CityService {
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
// 分页参数 -> TODO 代码可迁移到具体项目的公共 common 模块
private static final Integer pageNumber = 0;
private static final Integer pageSize = 10;
Pageable pageable = new PageRequest(pageNumber, pageSize);
// ES 操作类
@Autowired
CityRepository cityRepository;
public Long saveCity(City city) {
City cityResult = cityRepository.save(city);
return cityResult.getId();
}
public List<City> findByDescriptionAndScore(String description, Integer score) {
return cityRepository.findByDescriptionAndScore(description, score);
}
public List<City> findByDescriptionOrScore(String description, Integer score) {
return cityRepository.findByDescriptionOrScore(description, score);
}
public List<City> findByDescription(String description) {
return cityRepository.findByDescription(description, pageable).getContent();
}
public List<City> findByDescriptionNot(String description) {
return cityRepository.findByDescriptionNot(description, pageable).getContent();
}
public List<City> findByDescriptionLike(String description) {
return cityRepository.findByDescriptionLike(description, pageable).getContent();
}
}





城市 Controller 实现 Restful HTTP 服务
CityRestController.java
/**
* 城市 Controller 实现 Restful HTTP 服务
* <p>
* Created by bysocket on 03/05/2017.
*/
@RestController
public class CityRestController {
@Autowired
private CityService cityService;
/**
* 插入 ES 新城市
*
* @param city
* @return
*/
@RequestMapping(value = "/api/city", method = RequestMethod.POST)
public Long createCity(@RequestBody City city) {
return cityService.saveCity(city);
}
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
@RequestMapping(value = "/api/city/and/find", method = RequestMethod.GET)
public List<City> findByDescriptionAndScore(@RequestParam(value = "description") String description,
@RequestParam(value = "score") Integer score) {
return cityService.findByDescriptionAndScore(description, score);
}
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
@RequestMapping(value = "/api/city/or/find", method = RequestMethod.GET)
public List<City> findByDescriptionOrScore(@RequestParam(value = "description") String description,
@RequestParam(value = "score") Integer score) {
return cityService.findByDescriptionOrScore(description, score);
}
/**
* 查询城市描述
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/description/find", method = RequestMethod.GET)
public List<City> findByDescription(@RequestParam(value = "description") String description) {
return cityService.findByDescription(description);
}
/**
* NOT 语句查询
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/description/not/find", method = RequestMethod.GET)
public List<City> findByDescriptionNot(@RequestParam(value = "description") String description) {
return cityService.findByDescriptionNot(description);
}
/**
* LIKE 语句查询
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/like/find", method = RequestMethod.GET)
public List<City> findByDescriptionLike(@RequestParam(value = "description") String description) {
return cityService.findByDescriptionLike(description);
}
}



5.编译工程
mvn clean install

6.运行工程
右键运行 Application 应用启动类的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。

用 Postman 工具新增两个城市
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
    "id":"1",
    "score":"5",
    "name":"上海",
    "description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
    "id":"2",
    "score":"4",
    "name":"温岭",
    "description":"温岭是个沿海城市"
}

可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/
 
下面是基本查询语句的接口:
a. 普通查询,查询城市描述
GET http://localhost:8080/api/city ... on%3D温岭
b. AND 语句查询
GET http://localhost:8080/api/city ... on%3D温岭&score=4
如果换成 score=5 ,就没有结果了。
c. OR 语句查询
GET http://localhost:8080/api/city ... on%3D上海&score=4
d. NOT 语句查询
GET http://localhost:8080/api/city ... on%3D温州
e. LIKE 语句查询
GET http://localhost:8080/api/city ... on%3D城市

三、视频小结
下一视频《spring-data-elasticsearch 实战案例搭建》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 

spring-data-es 视频公开课(一)《elasticsearch 及 head 插件安装使用》

开源项目泥瓦匠BYSocket 发表了文章 • 1 个评论 • 1600 次浏览 • 2017-06-26 23:05 • 来自相关话题

spring-data-es 视频公开课(一)《elasticsearch 及 head 插件安装使用》 
视频如下:



 

一、前言
a. 版权声明 
本视频由 泥瓦匠BYSocket 支持 Spring For All 社区 www.spring4all.com ,版权为个人及社区所有,欢迎转发。

b. 视频主旨
学会 ElasticSearch 及 spring-data-elasticsearch项目实战教学

视频系列目录
spring-data-es 视频公开课(一)《elasticsearch 及 head 插件安装使用》spring-data-es 视频公开课(二)《spring-data-elasticsearch 简单案例搭建》spring-data-es 视频公开课(三)《spring-data-elasticsearch 实战案例搭建》

二、正文
本节提纲
一、Elasticsearch 安装
二、可视化插件 elasticsearch-head 安装
三、小结

运行环境
Mac OS 10.12.x
Elasticsearch 2.3.2

一、Elasticsearch 安装
什么是 Elasticsearch ?
Elasticsearch 是一个基于 Apache Lucene(TM) 的开源搜索引擎。无论在开源还是专有领域,Lucene 可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。并通过简单的 HTTP RESTful API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单。
Elasticsearch 不仅仅是 Lucene 和全文搜索,我们还能这样去描述它:
分布式的实时文件存储,每个字段都被索引并可被搜索分布式的实时分析搜索引擎可以扩展到上百台服务器,处理PB级结构化或非结构化数据


下面开始具体安装步骤:
1. .tar.gz 安装包安装 Elasticsearch 2.3.2
首先打开官网下载页 https://www.elastic.co/downloads/elasticsearch ,下载对应的 elasticsearch-2.3.2.tar.gz 文件,本文下载到 /java/es 目录。然后在文件的当前目录,通过 tar 命令解压安装包完成安装。tar -xzf elasticsearch-2.3.2.tar.gz
cd elasticsearch-2.3.2
2. 运行
一般在后台起守护线程启动 Elasticsearch,在命令行加入 -d 指定。自然,也可以加入 -p ,可将进程 ID 记录到文件中。./bin/elasticsearch -d








访问 http://localhost:9200/ ,可以看到成功运行的案例,返回的 JSON 页面。如图:
要关闭 Elasticsearch 进程,需要通过 ps 找到对应的 pid,在 kill pid 即可。ps aux |grep elasticsearch
kill -7 pid

二、可视化插件 elasticsearch-head 安装
官方 GitHub 地址:https://github.com/mobz/elasticsearch-head。
安装也很简单,安装 README 步骤走就好了:sudo ./bin/plugin install mobz/elasticsearch-head

访问 http://localhost:9200/_plugin/head/ ,右上角表示连接上了上小节启动的 Elasticsearch。
 

三、小结
本文写的比较基础,记录下主要两个点:
A、为啥不用高版本 ES?
- 版本问题,目前spring-data-elasticsearch 最高版本还只是 snapshot 版。所以 spring boot 的 starter 不敢用。详细点击 spring-data-elasticsearch Github 地址:
https://github.com/spring-proj ... earch
- 业界还是比较流行 2.3.2 和 最高版本 两种

B、为啥项目中不用 HTTP 去调用?
- 其实是可以的,HTTP 比较轻量级
- 本系列主要实践中总结 spring-data-elasticsearch ,spring-data-elasticsearch 提供对 ES 数据的访问



  查看全部
spring-data-es 视频公开课(一)《elasticsearch 及 head 插件安装使用》 
视频如下:




 

一、前言
a. 版权声明 
本视频由 泥瓦匠BYSocket 支持 Spring For All 社区 www.spring4all.com ,版权为个人及社区所有,欢迎转发。

b. 视频主旨
  • 学会 ElasticSearch 及 spring-data-elasticsearch
  • 项目实战教学


视频系列目录


二、正文
本节提纲
一、Elasticsearch 安装
二、可视化插件 elasticsearch-head 安装
三、小结

运行环境
Mac OS 10.12.x
Elasticsearch 2.3.2

一、Elasticsearch 安装
什么是 Elasticsearch ?
Elasticsearch 是一个基于 Apache Lucene(TM) 的开源搜索引擎。无论在开源还是专有领域,Lucene 可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。并通过简单的 HTTP RESTful API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单。
Elasticsearch 不仅仅是 Lucene 和全文搜索,我们还能这样去描述它:
  • 分布式的实时文件存储,每个字段都被索引并可被搜索
  • 分布式的实时分析搜索引擎
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据



下面开始具体安装步骤:
1. .tar.gz 安装包安装 Elasticsearch 2.3.2
首先打开官网下载页 https://www.elastic.co/downloads/elasticsearch ,下载对应的 elasticsearch-2.3.2.tar.gz 文件,本文下载到 /java/es 目录。然后在文件的当前目录,通过 tar 命令解压安装包完成安装。
tar -xzf elasticsearch-2.3.2.tar.gz
cd elasticsearch-2.3.2

2. 运行
一般在后台起守护线程启动 Elasticsearch,在命令行加入 -d 指定。自然,也可以加入 -p ,可将进程 ID 记录到文件中。
./bin/elasticsearch -d








访问 http://localhost:9200/ ,可以看到成功运行的案例,返回的 JSON 页面。如图:
要关闭 Elasticsearch 进程,需要通过 ps 找到对应的 pid,在 kill pid 即可。
ps aux |grep elasticsearch
kill -7 pid


二、可视化插件 elasticsearch-head 安装
官方 GitHub 地址:https://github.com/mobz/elasticsearch-head
安装也很简单,安装 README 步骤走就好了:
sudo ./bin/plugin install mobz/elasticsearch-head


访问 http://localhost:9200/_plugin/head/ ,右上角表示连接上了上小节启动的 Elasticsearch。
 

三、小结
本文写的比较基础,记录下主要两个点:
A、为啥不用高版本 ES?
- 版本问题,目前spring-data-elasticsearch 最高版本还只是 snapshot 版。所以 spring boot 的 starter 不敢用。详细点击 spring-data-elasticsearch Github 地址:
https://github.com/spring-proj ... earch
- 业界还是比较流行 2.3.2 和 最高版本 两种

B、为啥项目中不用 HTTP 去调用?
- 其实是可以的,HTTP 比较轻量级
- 本系列主要实践中总结 spring-data-elasticsearch ,spring-data-elasticsearch 提供对 ES 数据的访问



 

深入浅出 spring-data-elasticsearch - 实战案例详解(四)

Spring Data泥瓦匠BYSocket 发表了文章 • 5 个评论 • 1054 次浏览 • 2017-06-22 19:14 • 来自相关话题

『  热烈的爱情到订婚早已是定点,婚一结一切了结。现在订了婚,彼此间还留着情感发展的余地,这是桩好事。- 《我们仨》 』

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2

本文提纲
一、搜索实战场景需求
二、运行 spring-data-elasticsearch-query 工程
三、spring-data-elasticsearch-query 工程代码详解
 
一、搜索实战场景需求
搜索的场景会很多,常用的搜索场景,需要搜索的字段很多,但每个字段匹配到后所占的权重又不同。比如电商网站的搜索,搜到商品名称和商品描述,自然商品名称的权重远远大于商品描述。而且单词匹配肯定不如短语匹配。这样就出现了新的需求,如何确定这些短语,即自然分词。那就利用分词器,即可得到所需要的短语,然后进行搜索。
下面介绍短语如何进行按权重分匹配搜索。
 
二、运行 spring-data-elasticsearch-query 工程
1. 后台起守护线程启动 Elasticsearchcd elasticsearch-2.3.2/
./bin/elasticsearch -d
 
git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample。
下面开始运行工程步骤(Quick Start):
2. 项目结构介绍org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置
本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-query,运行 maven 指令:mvn clean install

4.运行工程
右键运行 Application 应用启动类(位置:org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 spring-data-elasticsearch-query 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
 
下面是实战搜索语句的接口:
GET http://localhost:8080/api/city ... nt%3D城市
获取返回结果:
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
 
应用的控制台中,日志打印出查询语句的 DSL : DSL =
{
"function_score" : {
"functions" : [ {
"filter" : {
"match" : {
"name" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 1000.0
}, {
"filter" : {
"match" : {
"description" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 500.0
} ],
"score_mode" : "sum",
"min_score" : 10.0
}
}
 
三、spring-data-elasticsearch-query 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)
 

3. ES 数据操作层/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
}
接口只要继承 ElasticsearchRepository 接口类即可,具体使用的是该接口的方法: Iterable<T> search(QueryBuilder query);
Page<T> search(QueryBuilder query, Pageable pageable);
Page<T> search(SearchQuery searchQuery);
Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);

4. 实体类/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase
 
 
5. 城市 ES 业务逻辑实现类
代码如下:/**
* 城市 ES 业务逻辑实现类
* <p>
* Created by bysocket on 20/06/2017.
*/
@Service
public class CityESServiceImpl implements CityService {
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
/* 分页参数 */
Integer PAGE_SIZE = 12; // 每页数量
Integer DEFAULT_PAGE_NUMBER = 0; // 默认当前页码
/* 搜索模式 */
String SCORE_MODE_SUM = "sum"; // 权重分求和模式
Float MIN_SCORE = 10.0F; // 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
@Autowired
CityRepository cityRepository; // ES 操作类
public Long saveCity(City city) {
City cityResult = cityRepository.save(city);
return cityResult.getId();
}
@Override
public List<City> searchCity(Integer pageNumber, Integer pageSize, String searchContent) {
// 校验分页参数
if (pageSize == null || pageSize <= 0) {
pageSize = PAGE_SIZE;
}
if (pageNumber == null || pageNumber < DEFAULT_PAGE_NUMBER) {
pageNumber = DEFAULT_PAGE_NUMBER;
}
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n ");
// 构建搜索查询
SearchQuery searchQuery = getCitySearchQuery(pageNumber,pageSize,searchContent);
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n " + searchQuery.getQuery().toString());
Page<City> cityPage = cityRepository.search(searchQuery);
return cityPage.getContent();
}
/**
* 根据搜索词构造搜索查询语句
*
* 代码流程:
* - 权重分查询
* - 短语匹配
* - 设置权重分最小值
* - 设置分页参数
*
* @param pageNumber 当前页码
* @param pageSize 每页大小
* @param searchContent 搜索内容
* @return
*/
private SearchQuery getCitySearchQuery(Integer pageNumber, Integer pageSize,String searchContent) {
// 短语匹配到的搜索词,求和模式累加权重分
// 权重分查询 https://www.elastic.co/guide/c ... .html
// - 短语匹配 https://www.elastic.co/guide/c ... .html
// - 字段对应权重分设置,可以优化成 enum
// - 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
.add(QueryBuilders.matchPhraseQuery("name", searchContent),
ScoreFunctionBuilders.weightFactorFunction(1000))
.add(QueryBuilders.matchPhraseQuery("description", searchContent),
ScoreFunctionBuilders.weightFactorFunction(500))
.scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);
// 分页参数
Pageable pageable = new PageRequest(pageNumber, pageSize);
return new NativeSearchQueryBuilder()
.withPageable(pageable)
.withQuery(functionScoreQueryBuilder).build();
}
}
可以看到该过程实现了,短语精准匹配以及匹配到根据字段权重分求和,从而实现按权重搜索查询。代码流程如下:
- 权重分查询
- 短语匹配
- 设置权重分最小值
- 设置分页参数

注意:
- 字段对应权重分设置,可以优化成 enum
- 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
 
权重分查询文档:https://www.elastic.co/guide/c ... .html。
短语匹配文档: https://www.elastic.co/guide/c ... .html。
 
四、小结
Elasticsearch 还提供很多高级的搜索功能。这里提供下需要经常逛的相关网站:
Elasticsearch 中文社区 https://elasticsearch.cn/topic/elasticsearch
Elasticsearch: 权威指南-在线版 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html 
 

摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 查看全部

『  热烈的爱情到订婚早已是定点,婚一结一切了结。现在订了婚,彼此间还留着情感发展的余地,这是桩好事。- 《我们仨》 』

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2

本文提纲
一、搜索实战场景需求
二、运行 spring-data-elasticsearch-query 工程
三、spring-data-elasticsearch-query 工程代码详解
 
一、搜索实战场景需求
搜索的场景会很多,常用的搜索场景,需要搜索的字段很多,但每个字段匹配到后所占的权重又不同。比如电商网站的搜索,搜到商品名称和商品描述,自然商品名称的权重远远大于商品描述。而且单词匹配肯定不如短语匹配。这样就出现了新的需求,如何确定这些短语,即自然分词。那就利用分词器,即可得到所需要的短语,然后进行搜索。
下面介绍短语如何进行按权重分匹配搜索。
 
二、运行 spring-data-elasticsearch-query 工程
1. 后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

 
git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample
下面开始运行工程步骤(Quick Start):
2. 项目结构介绍
org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置

本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-query,运行 maven 指令:
mvn clean install

4.运行工程
右键运行 Application 应用启动类(位置:org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 spring-data-elasticsearch-query 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}

 
下面是实战搜索语句的接口:
GET http://localhost:8080/api/city ... nt%3D城市
获取返回结果:
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

 
应用的控制台中,日志打印出查询语句的 DSL :
 DSL  = 
{
"function_score" : {
"functions" : [ {
"filter" : {
"match" : {
"name" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 1000.0
}, {
"filter" : {
"match" : {
"description" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 500.0
} ],
"score_mode" : "sum",
"min_score" : 10.0
}
}

 
三、spring-data-elasticsearch-query 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址
# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300

默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)
 

3. ES 数据操作层
/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
}

接口只要继承 ElasticsearchRepository 接口类即可,具体使用的是该接口的方法:
    Iterable<T> search(QueryBuilder query);
Page<T> search(QueryBuilder query, Pageable pageable);
Page<T> search(SearchQuery searchQuery);
Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);


4. 实体类
/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}

注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase
 
 
5. 城市 ES 业务逻辑实现类
代码如下:
/**
* 城市 ES 业务逻辑实现类
* <p>
* Created by bysocket on 20/06/2017.
*/
@Service
public class CityESServiceImpl implements CityService {
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
/* 分页参数 */
Integer PAGE_SIZE = 12; // 每页数量
Integer DEFAULT_PAGE_NUMBER = 0; // 默认当前页码
/* 搜索模式 */
String SCORE_MODE_SUM = "sum"; // 权重分求和模式
Float MIN_SCORE = 10.0F; // 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
@Autowired
CityRepository cityRepository; // ES 操作类
public Long saveCity(City city) {
City cityResult = cityRepository.save(city);
return cityResult.getId();
}
@Override
public List<City> searchCity(Integer pageNumber, Integer pageSize, String searchContent) {
// 校验分页参数
if (pageSize == null || pageSize <= 0) {
pageSize = PAGE_SIZE;
}
if (pageNumber == null || pageNumber < DEFAULT_PAGE_NUMBER) {
pageNumber = DEFAULT_PAGE_NUMBER;
}
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n ");
// 构建搜索查询
SearchQuery searchQuery = getCitySearchQuery(pageNumber,pageSize,searchContent);
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n " + searchQuery.getQuery().toString());
Page<City> cityPage = cityRepository.search(searchQuery);
return cityPage.getContent();
}
/**
* 根据搜索词构造搜索查询语句
*
* 代码流程:
* - 权重分查询
* - 短语匹配
* - 设置权重分最小值
* - 设置分页参数
*
* @param pageNumber 当前页码
* @param pageSize 每页大小
* @param searchContent 搜索内容
* @return
*/
private SearchQuery getCitySearchQuery(Integer pageNumber, Integer pageSize,String searchContent) {
// 短语匹配到的搜索词,求和模式累加权重分
// 权重分查询 https://www.elastic.co/guide/c ... .html
// - 短语匹配 https://www.elastic.co/guide/c ... .html
// - 字段对应权重分设置,可以优化成 enum
// - 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
.add(QueryBuilders.matchPhraseQuery("name", searchContent),
ScoreFunctionBuilders.weightFactorFunction(1000))
.add(QueryBuilders.matchPhraseQuery("description", searchContent),
ScoreFunctionBuilders.weightFactorFunction(500))
.scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);
// 分页参数
Pageable pageable = new PageRequest(pageNumber, pageSize);
return new NativeSearchQueryBuilder()
.withPageable(pageable)
.withQuery(functionScoreQueryBuilder).build();
}
}

可以看到该过程实现了,短语精准匹配以及匹配到根据字段权重分求和,从而实现按权重搜索查询。代码流程如下:
- 权重分查询
- 短语匹配
- 设置权重分最小值
- 设置分页参数

注意:
- 字段对应权重分设置,可以优化成 enum
- 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
 
权重分查询文档:https://www.elastic.co/guide/c ... .html
短语匹配文档: https://www.elastic.co/guide/c ... .html
 
四、小结
Elasticsearch 还提供很多高级的搜索功能。这里提供下需要经常逛的相关网站:
Elasticsearch 中文社区 https://elasticsearch.cn/topic/elasticsearch
Elasticsearch: 权威指南-在线版 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html 
 


摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢!


spring boot 学习系列

开源项目hong 发表了文章 • 8 个评论 • 1848 次浏览 • 2017-06-21 09:35 • 来自相关话题

学习记录 https://github.com/t-hong/springboot-examples 

spring boot 学习系列 (使用版本 1.5.3) 
1.chapter1> springboot hello world. 2.chapter2> 配置文件详解:自定义属性、随机数、多环境配置等(目前只使用properties,后续添加yml的配置方式用法) 3.chapter3> spring boot 构建restful api以及单元测试,替换默认json 解析框架,使用fastjson 4.chapter3-jersey> springboot 整合jersey 实现restful. 5.chapter4-thymeleaf> spring boot 国际化相关处理和thymeleaf 模板语法的基本使用 6.chapter4-freemarker> 使用freemarker模板引擎 7.chapter4-jsp> spring boot 对jsp 的支持 8.chapter4-velocity> spring boot 使用velocity 版本情况说明 9.chapter5-swagger> spring boot 使用swagger构建restful api10.chapter6-handle-exception> spring boot 统一异常处理(返回异常对象json 或者modelandview) 11.chapter6-servlet> spring boot 下使用servlet、filter、listener以及springmvc interceptor 的相关处理. 12.chapter6-aop-log> spring boot log4j 使用aop方式添加日志. 13.chapter7-jdbctemplate> spring boot 整合jdbctemplate 操作数据库. 14.chapter7-spring-data-jpa> spring boot 整合spring data jpa ,简化数据库操作 15.chapter7-mybatis> spring boot 整合mybatis 使用,提供两种方式:xml 和annotation的方式。16.chapter7-mybatis-mapper-plugin> spring boot 整合mybatis 第三方mapper 插件、mybatis自动生成工具 generator 简化开发. 17.chapter8-jdbctemplate-mutil-datasource> spring boot 整合jdbctemplate 多数据源 18.chapter8-jpa-mutil-datasource> spring boot 整合spring data jpa 多数据源 19.chapter8-mybatis-mutil-datasource> spring boot 使用druid 数据源 整合mybatis 多数据源 20.chapter9-redis> spring boot 整合redis 21.chapter9-redis-cache> spring boot 使用redis 作为缓存实例 22.chapter9-redis-cluster> 添加了注释说明了JedisCluster 与RedisTemplate 操作RedisCluster 的一些使用原理 . 23.chapter9-redis-session> spring boot 整合Redis . 24.chapter10-shiro> spring boot 整合shiro 基本实例 25.chapter10-jwt> spring boot 整合jwt 实现token 认证. 26.chapter10-security> spring boot 整合security 实现简单权限控制. 27.chapter10-security-jwt> spring boot security token 整合实现认证登陆. 28.chapter11-caching-ehcache> spring boot框架缓存系列 ehcache . 29.chapter12-rocketmq> spring boot 整合rocketmq 测试. 查看全部
学习记录 https://github.com/t-hong/springboot-examples 

spring boot 学习系列 (使用版本 1.5.3) 
  • 1.chapter1> springboot hello world. 
  • 2.chapter2> 配置文件详解:自定义属性、随机数、多环境配置等(目前只使用properties,后续添加yml的配置方式用法) 
  • 3.chapter3> spring boot 构建restful api以及单元测试,替换默认json 解析框架,使用fastjson 
  • 4.chapter3-jersey> springboot 整合jersey 实现restful. 
  • 5.chapter4-thymeleaf> spring boot 国际化相关处理和thymeleaf 模板语法的基本使用 
  • 6.chapter4-freemarker> 使用freemarker模板引擎 
  • 7.chapter4-jsp> spring boot 对jsp 的支持 
  • 8.chapter4-velocity> spring boot 使用velocity 版本情况说明 
  • 9.chapter5-swagger> spring boot 使用swagger构建restful api
  • 10.chapter6-handle-exception> spring boot 统一异常处理(返回异常对象json 或者modelandview) 
  • 11.chapter6-servlet> spring boot 下使用servlet、filter、listener以及springmvc interceptor 的相关处理. 
  • 12.chapter6-aop-log> spring boot log4j 使用aop方式添加日志. 
  • 13.chapter7-jdbctemplate> spring boot 整合jdbctemplate 操作数据库. 
  • 14.chapter7-spring-data-jpa> spring boot 整合spring data jpa ,简化数据库操作 
  • 15.chapter7-mybatis> spring boot 整合mybatis 使用,提供两种方式:xml 和annotation的方式。
  • 16.chapter7-mybatis-mapper-plugin> spring boot 整合mybatis 第三方mapper 插件、mybatis自动生成工具 generator 简化开发. 
  • 17.chapter8-jdbctemplate-mutil-datasource> spring boot 整合jdbctemplate 多数据源 
  • 18.chapter8-jpa-mutil-datasource> spring boot 整合spring data jpa 多数据源 
  • 19.chapter8-mybatis-mutil-datasource> spring boot 使用druid 数据源 整合mybatis 多数据源 
  • 20.chapter9-redis> spring boot 整合redis 
  • 21.chapter9-redis-cache> spring boot 使用redis 作为缓存实例 
  • 22.chapter9-redis-cluster> 添加了注释说明了JedisCluster 与RedisTemplate 操作RedisCluster 的一些使用原理 . 
  • 23.chapter9-redis-session> spring boot 整合Redis . 
  • 24.chapter10-shiro> spring boot 整合shiro 基本实例 
  • 25.chapter10-jwt> spring boot 整合jwt 实现token 认证. 
  • 26.chapter10-security> spring boot 整合security 实现简单权限控制. 
  • 27.chapter10-security-jwt> spring boot security token 整合实现认证登陆. 
  • 28.chapter11-caching-ehcache> spring boot框架缓存系列 ehcache . 
  • 29.chapter12-rocketmq> spring boot 整合rocketmq 测试.

深入浅出 spring-data-elasticsearch - 基本案例详解(三)

Spring Data泥瓦匠BYSocket 发表了文章 • 30 个评论 • 2201 次浏览 • 2017-06-20 11:37 • 来自相关话题

『  风云说:能分享自己职位的知识的领导是个好领导。 』
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2
本文提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、运行 spring-data-elasticsearch-crud 工程
三、spring-data-elasticsearch-crud 工程代码详解

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix; 

Spring Boot Version (x)    Spring Data Elasticsearch Version (y)    Elasticsearch Version (z)
x <= 1.3.5    y <= 1.3.4    z <= 1.7.2*
x >= 1.4.x    2.0.0 <=y < 5.0.0**    2.0.0 <= z < 5.0.0**
*  - 只需要你修改下对应的 pom 文件版本号 
** - 下一个 ES 的版本会有重大的更新
 
1. 后台起守护线程启动 Elasticsearchcd elasticsearch-2.3.2/
./bin/elasticsearch -d
git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample。
下面开始运行工程步骤(Quick Start):
 
2. 项目结构介绍org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-crud,运行 maven 指令:mvn clean install
 4.运行工程
右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/:
(如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 http://www.bysocket.com/?p=1744 。)
在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下:{
"_index": "cityindex",
"_type": "city",
"_id": "1",
"_version": 1,
"_score": 1,
"_source": {
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
}
下面是基本查询语句的接口:
a. 普通查询,查询城市描述GET http://localhost:8080/api/city ... on%3D温岭
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]

b. AND 语句查询GET http://localhost:8080/api/city ... on%3D温岭&score=4
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]如果换成 score=5 ,就没有结果了。

c. OR 语句查询GET http://localhost:8080/api/city ... on%3D上海&score=4
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
d. NOT 语句查询GET http://localhost:8080/api/city ... on%3D温州
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
e. LIKE 语句查询GET http://localhost:8080/api/city ... on%3D城市
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
三、spring-data-elasticsearch-crud 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)

3. ES 数据操作层/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}
接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot

4. 实体类/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase

四、小结
预告下
下一篇《深入浅出 spring-data-elasticsearch - 实战案例详解》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 

摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 查看全部
『  风云说:能分享自己职位的知识的领导是个好领导。 』
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2
本文提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、运行 spring-data-elasticsearch-crud 工程
三、spring-data-elasticsearch-crud 工程代码详解

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix; 

Spring Boot Version (x)    Spring Data Elasticsearch Version (y)    Elasticsearch Version (z)
x <= 1.3.5    y <= 1.3.4    z <= 1.7.2*
x >= 1.4.x    2.0.0 <=y < 5.0.0**    2.0.0 <= z < 5.0.0**
*  - 只需要你修改下对应的 pom 文件版本号 
** - 下一个 ES 的版本会有重大的更新
 
1. 后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample
下面开始运行工程步骤(Quick Start):
 
2. 项目结构介绍
org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置
本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-crud,运行 maven 指令:
mvn clean install

 4.运行工程
右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}

POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}

可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/
(如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 http://www.bysocket.com/?p=1744 。)
在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下:
{
"_index": "cityindex",
"_type": "city",
"_id": "1",
"_version": 1,
"_score": 1,
"_source": {
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
}

下面是基本查询语句的接口:
a. 普通查询,查询城市描述
GET http://localhost:8080/api/city ... on%3D温岭
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]


b. AND 语句查询
GET http://localhost:8080/api/city ... on%3D温岭&score=4
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]
如果换成 score=5 ,就没有结果了。

c. OR 语句查询
GET http://localhost:8080/api/city ... on%3D上海&score=4
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

d. NOT 语句查询
GET http://localhost:8080/api/city ... on%3D温州
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

e. LIKE 语句查询
GET http://localhost:8080/api/city ... on%3D城市
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

三、spring-data-elasticsearch-crud 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址
# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。

更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)

3. ES 数据操作层
/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}

接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot

4. 实体类
/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}

注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase

四、小结
预告下
下一篇《深入浅出 spring-data-elasticsearch - 实战案例详解》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 


摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢!


深入浅出 spring-data-elasticsearch 之 ElasticSearch 架构初探(一)

Spring Data泥瓦匠BYSocket 发表了文章 • 2 个评论 • 2532 次浏览 • 2017-06-04 17:20 • 来自相关话题

本文目录
一、Elasticsearch 基本术语
1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
二、Elasticsearch 工作原理
2.1 文档存储的路由
2.2 如何健康检查
2.3 如何水平扩容
三、小结

一、Elasticsearch 基本术语

1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
文档(Document)
文档,在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index、_type 和 _id 唯一标识。

索引(Index)
索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。

类型(Type)
类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

和关系型数据库 MySQL 做个类比:
Document 类似于 Record
Type 类似于 Table
Index 类似于 Database

1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
集群(Cluster)
服务器集群大家都知道,这里 ES 也是类似的。多个 ElasticSearch 运行实例(节点)组合的组合体是 ElasticSearch 集群。
ElasticSearch 是天然的分布式,通过水平扩容为集群添加更多节点。
集群是去中心化的,有一个主节点(Master)。主节点是动态选举,因此不会出现单点故障。

那分片和节点的配置呢?
节点(Node)
一个 ElasticSearch 运行实例就是节点。顺着集群来,任何节点都可以被选举成为主节点。主节点负责集群内所以变更,比如索引的增加、删除等。所以集群不会因为主节点流量的增大成为瓶颈。因为任何节点都会成为主节点。
下面有 3 个节点,第 1 个节点有:2 个主分片和 1 个副分片。如图:





那么,只有一个节点的 ElasticSearch 服务会存在瓶颈。如图:





分片(Shard)
分片,是 ES 节点中最小的工作单元。分片仅仅保存全部数据的一部分,分片的集合是 ES 的索引。分片包括主分片和副分片,主分片是副分片的拷贝。主分片和副分片地工作基本没有大的区别。
在索引中全文搜索,然后会查询到每个分片,将每个分配的结果进行全局地收集处理,并返回。

二、Elasticsearch 工作原理

2.1 文档存储的路由
当索引到一个文档(如:报价系统),具体的文档数据(如:报价数据)会存储到一个分片。具体文档数据会被切分,并分别存储在分片 1 或者 分片 2 … 
那么如何确定存在哪个分片呢?
存储路由过程由下面地公式决定:shard = hash(routing) % number_of_primary_shards
routing 是可变值,支持自定义,默认文档 _id。
hash 函数生成数字,经过取余算法得到余数,那么这个余数就是分片的位置。
这是不是有点负载均衡的类似。
 
2.2 如何健康检查
集群名,集群的健康状态GET http://127.0.0.1:9200/_cluster/stats
{
"cluster_name": "elasticsearch",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}
status 字段是需要我们关心的。状态可能是下列三个值之一:green
所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
yellow
所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。
red
至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。


active_primary_shards 集群中的主分片数量
active_shards 所有分片的汇总值
relocating_shards 显示当前正在从一个节点迁往其他节点的分片的数量。通常来说应该是 0,不过在 Elasticsearch 发现集群不太均衡时,该值会上涨。比如说:添加了一个新节点,或者下线了一个节点。
initializing_shards 刚刚创建的分片的个数。
unassigned_shards 已经在集群状态中存在的分片。
 
2.3 如何水平扩容
主分片在索引创建已经确定。读操作可以同时被主分片和副分片处理。因此,更多的分片,会拥有更高的吞吐量。自然,需要增加更多的硬件资源支持吞吐量。
说明,这里无法提高性能,因为每个分片获得的资源会变少。
动态调整副本分片数,按需伸缩集群,比如把副本数默认值为 1 增加到 2:PUT /blogs/_settings
{
"number_of_replicas" : 2
}

三、小结
简单初探了下 ElasticSearch 的相关内容。后面会主要落地到实战,关于  spring-data-elasticsearch 这块的实战。

最后,《 深入浅出 spring-data-elasticsearch 》小连载目录如下:
深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一)
深入浅出 spring-data-elasticsearch - 概述(二)
深入浅出 spring-data-elasticsearch - 基本案例详解(三)
深入浅出 spring-data-elasticsearch - 复杂案例详解(四)
深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五)
 

资料:
官方《Elasticsearch: 权威指南》
https://www.elastic.co/guide/c ... .html
 

本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处 查看全部
本文目录
一、Elasticsearch 基本术语
1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
二、Elasticsearch 工作原理
2.1 文档存储的路由
2.2 如何健康检查
2.3 如何水平扩容
三、小结

一、Elasticsearch 基本术语

1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
文档(Document)
文档,在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index、_type 和 _id 唯一标识。

索引(Index)
索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。

类型(Type)
类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

和关系型数据库 MySQL 做个类比
Document 类似于 Record
Type 类似于 Table
Index 类似于 Database

1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
集群(Cluster)
服务器集群大家都知道,这里 ES 也是类似的。多个 ElasticSearch 运行实例(节点)组合的组合体是 ElasticSearch 集群。
ElasticSearch 是天然的分布式,通过水平扩容为集群添加更多节点。
集群是去中心化的,有一个主节点(Master)。主节点是动态选举,因此不会出现单点故障。

那分片和节点的配置呢?
节点(Node
一个 ElasticSearch 运行实例就是节点。顺着集群来,任何节点都可以被选举成为主节点。主节点负责集群内所以变更,比如索引的增加、删除等。所以集群不会因为主节点流量的增大成为瓶颈。因为任何节点都会成为主节点。
下面有 3 个节点,第 1 个节点有:2 个主分片和 1 个副分片。如图:
elas_node.png


那么,只有一个节点的 ElasticSearch 服务会存在瓶颈。如图:
elas_0201.png


分片(Shard)
分片,是 ES 节点中最小的工作单元。分片仅仅保存全部数据的一部分,分片的集合是 ES 的索引。分片包括主分片和副分片,主分片是副分片的拷贝。主分片和副分片地工作基本没有大的区别。
在索引中全文搜索,然后会查询到每个分片,将每个分配的结果进行全局地收集处理,并返回。

二、Elasticsearch 工作原理

2.1 文档存储的路由
当索引到一个文档(如:报价系统),具体的文档数据(如:报价数据)会存储到一个分片。具体文档数据会被切分,并分别存储在分片 1 或者 分片 2 … 
那么如何确定存在哪个分片呢?
存储路由过程由下面地公式决定:
shard = hash(routing) % number_of_primary_shards

routing 是可变值,支持自定义,默认文档 _id。
hash 函数生成数字,经过取余算法得到余数,那么这个余数就是分片的位置。
这是不是有点负载均衡的类似。
 
2.2 如何健康检查
集群名,集群的健康状态
GET http://127.0.0.1:9200/_cluster/stats 
{
"cluster_name": "elasticsearch",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}

status 字段是需要我们关心的。状态可能是下列三个值之一:
green
所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
yellow
所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。
red
至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。


active_primary_shards 集群中的主分片数量
active_shards 所有分片的汇总值
relocating_shards 显示当前正在从一个节点迁往其他节点的分片的数量。通常来说应该是 0,不过在 Elasticsearch 发现集群不太均衡时,该值会上涨。比如说:添加了一个新节点,或者下线了一个节点。
initializing_shards 刚刚创建的分片的个数。
unassigned_shards 已经在集群状态中存在的分片。
 
2.3 如何水平扩容
主分片在索引创建已经确定。读操作可以同时被主分片和副分片处理。因此,更多的分片,会拥有更高的吞吐量。自然,需要增加更多的硬件资源支持吞吐量。
说明,这里无法提高性能,因为每个分片获得的资源会变少。
动态调整副本分片数,按需伸缩集群,比如把副本数默认值为 1 增加到 2:
PUT /blogs/_settings
{
"number_of_replicas" : 2
}


三、小结
简单初探了下 ElasticSearch 的相关内容。后面会主要落地到实战,关于  spring-data-elasticsearch 这块的实战。

最后,《 深入浅出 spring-data-elasticsearch 》小连载目录如下:
深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一)
深入浅出 spring-data-elasticsearch - 概述(二)
深入浅出 spring-data-elasticsearch - 基本案例详解(三)
深入浅出 spring-data-elasticsearch - 复杂案例详解(四)
深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五)
 

资料:
官方《Elasticsearch: 权威指南》
https://www.elastic.co/guide/c ... .html
 


本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处


深入浅出 spring-data-elasticsearch - 基本案例详解(三)

Spring Data泥瓦匠BYSocket 发表了文章 • 30 个评论 • 2201 次浏览 • 2017-06-20 11:37 • 来自相关话题

『  风云说:能分享自己职位的知识的领导是个好领导。 』
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2
本文提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、运行 spring-data-elasticsearch-crud 工程
三、spring-data-elasticsearch-crud 工程代码详解

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix; 

Spring Boot Version (x)    Spring Data Elasticsearch Version (y)    Elasticsearch Version (z)
x <= 1.3.5    y <= 1.3.4    z <= 1.7.2*
x >= 1.4.x    2.0.0 <=y < 5.0.0**    2.0.0 <= z < 5.0.0**
*  - 只需要你修改下对应的 pom 文件版本号 
** - 下一个 ES 的版本会有重大的更新
 
1. 后台起守护线程启动 Elasticsearchcd elasticsearch-2.3.2/
./bin/elasticsearch -d
git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample。
下面开始运行工程步骤(Quick Start):
 
2. 项目结构介绍org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-crud,运行 maven 指令:mvn clean install
 4.运行工程
右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/:
(如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 http://www.bysocket.com/?p=1744 。)
在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下:{
"_index": "cityindex",
"_type": "city",
"_id": "1",
"_version": 1,
"_score": 1,
"_source": {
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
}
下面是基本查询语句的接口:
a. 普通查询,查询城市描述GET http://localhost:8080/api/city ... on%3D温岭
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]

b. AND 语句查询GET http://localhost:8080/api/city ... on%3D温岭&score=4
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]如果换成 score=5 ,就没有结果了。

c. OR 语句查询GET http://localhost:8080/api/city ... on%3D上海&score=4
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
d. NOT 语句查询GET http://localhost:8080/api/city ... on%3D温州
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
e. LIKE 语句查询GET http://localhost:8080/api/city ... on%3D城市
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
三、spring-data-elasticsearch-crud 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)

3. ES 数据操作层/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}
接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot

4. 实体类/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase

四、小结
预告下
下一篇《深入浅出 spring-data-elasticsearch - 实战案例详解》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 

摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 查看全部
『  风云说:能分享自己职位的知识的领导是个好领导。 』
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2
本文提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、运行 spring-data-elasticsearch-crud 工程
三、spring-data-elasticsearch-crud 工程代码详解

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix; 

Spring Boot Version (x)    Spring Data Elasticsearch Version (y)    Elasticsearch Version (z)
x <= 1.3.5    y <= 1.3.4    z <= 1.7.2*
x >= 1.4.x    2.0.0 <=y < 5.0.0**    2.0.0 <= z < 5.0.0**
*  - 只需要你修改下对应的 pom 文件版本号 
** - 下一个 ES 的版本会有重大的更新
 
1. 后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample
下面开始运行工程步骤(Quick Start):
 
2. 项目结构介绍
org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置
本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-crud,运行 maven 指令:
mvn clean install

 4.运行工程
右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}

POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}

可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/
(如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 http://www.bysocket.com/?p=1744 。)
在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下:
{
"_index": "cityindex",
"_type": "city",
"_id": "1",
"_version": 1,
"_score": 1,
"_source": {
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
}

下面是基本查询语句的接口:
a. 普通查询,查询城市描述
GET http://localhost:8080/api/city ... on%3D温岭
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]


b. AND 语句查询
GET http://localhost:8080/api/city ... on%3D温岭&score=4
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]
如果换成 score=5 ,就没有结果了。

c. OR 语句查询
GET http://localhost:8080/api/city ... on%3D上海&score=4
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

d. NOT 语句查询
GET http://localhost:8080/api/city ... on%3D温州
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

e. LIKE 语句查询
GET http://localhost:8080/api/city ... on%3D城市
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

三、spring-data-elasticsearch-crud 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址
# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。

更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)

3. ES 数据操作层
/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}

接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot

4. 实体类
/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}

注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase

四、小结
预告下
下一篇《深入浅出 spring-data-elasticsearch - 实战案例详解》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 


摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢!


深入浅出 spring-data-elasticsearch 之 ElasticSearch 架构初探(一)

Spring Data泥瓦匠BYSocket 发表了文章 • 2 个评论 • 2532 次浏览 • 2017-06-04 17:20 • 来自相关话题

本文目录
一、Elasticsearch 基本术语
1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
二、Elasticsearch 工作原理
2.1 文档存储的路由
2.2 如何健康检查
2.3 如何水平扩容
三、小结

一、Elasticsearch 基本术语

1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
文档(Document)
文档,在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index、_type 和 _id 唯一标识。

索引(Index)
索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。

类型(Type)
类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

和关系型数据库 MySQL 做个类比:
Document 类似于 Record
Type 类似于 Table
Index 类似于 Database

1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
集群(Cluster)
服务器集群大家都知道,这里 ES 也是类似的。多个 ElasticSearch 运行实例(节点)组合的组合体是 ElasticSearch 集群。
ElasticSearch 是天然的分布式,通过水平扩容为集群添加更多节点。
集群是去中心化的,有一个主节点(Master)。主节点是动态选举,因此不会出现单点故障。

那分片和节点的配置呢?
节点(Node)
一个 ElasticSearch 运行实例就是节点。顺着集群来,任何节点都可以被选举成为主节点。主节点负责集群内所以变更,比如索引的增加、删除等。所以集群不会因为主节点流量的增大成为瓶颈。因为任何节点都会成为主节点。
下面有 3 个节点,第 1 个节点有:2 个主分片和 1 个副分片。如图:





那么,只有一个节点的 ElasticSearch 服务会存在瓶颈。如图:





分片(Shard)
分片,是 ES 节点中最小的工作单元。分片仅仅保存全部数据的一部分,分片的集合是 ES 的索引。分片包括主分片和副分片,主分片是副分片的拷贝。主分片和副分片地工作基本没有大的区别。
在索引中全文搜索,然后会查询到每个分片,将每个分配的结果进行全局地收集处理,并返回。

二、Elasticsearch 工作原理

2.1 文档存储的路由
当索引到一个文档(如:报价系统),具体的文档数据(如:报价数据)会存储到一个分片。具体文档数据会被切分,并分别存储在分片 1 或者 分片 2 … 
那么如何确定存在哪个分片呢?
存储路由过程由下面地公式决定:shard = hash(routing) % number_of_primary_shards
routing 是可变值,支持自定义,默认文档 _id。
hash 函数生成数字,经过取余算法得到余数,那么这个余数就是分片的位置。
这是不是有点负载均衡的类似。
 
2.2 如何健康检查
集群名,集群的健康状态GET http://127.0.0.1:9200/_cluster/stats
{
"cluster_name": "elasticsearch",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}
status 字段是需要我们关心的。状态可能是下列三个值之一:green
所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
yellow
所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。
red
至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。


active_primary_shards 集群中的主分片数量
active_shards 所有分片的汇总值
relocating_shards 显示当前正在从一个节点迁往其他节点的分片的数量。通常来说应该是 0,不过在 Elasticsearch 发现集群不太均衡时,该值会上涨。比如说:添加了一个新节点,或者下线了一个节点。
initializing_shards 刚刚创建的分片的个数。
unassigned_shards 已经在集群状态中存在的分片。
 
2.3 如何水平扩容
主分片在索引创建已经确定。读操作可以同时被主分片和副分片处理。因此,更多的分片,会拥有更高的吞吐量。自然,需要增加更多的硬件资源支持吞吐量。
说明,这里无法提高性能,因为每个分片获得的资源会变少。
动态调整副本分片数,按需伸缩集群,比如把副本数默认值为 1 增加到 2:PUT /blogs/_settings
{
"number_of_replicas" : 2
}

三、小结
简单初探了下 ElasticSearch 的相关内容。后面会主要落地到实战,关于  spring-data-elasticsearch 这块的实战。

最后,《 深入浅出 spring-data-elasticsearch 》小连载目录如下:
深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一)
深入浅出 spring-data-elasticsearch - 概述(二)
深入浅出 spring-data-elasticsearch - 基本案例详解(三)
深入浅出 spring-data-elasticsearch - 复杂案例详解(四)
深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五)
 

资料:
官方《Elasticsearch: 权威指南》
https://www.elastic.co/guide/c ... .html
 

本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处 查看全部
本文目录
一、Elasticsearch 基本术语
1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
二、Elasticsearch 工作原理
2.1 文档存储的路由
2.2 如何健康检查
2.3 如何水平扩容
三、小结

一、Elasticsearch 基本术语

1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
文档(Document)
文档,在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index、_type 和 _id 唯一标识。

索引(Index)
索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。

类型(Type)
类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

和关系型数据库 MySQL 做个类比
Document 类似于 Record
Type 类似于 Table
Index 类似于 Database

1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
集群(Cluster)
服务器集群大家都知道,这里 ES 也是类似的。多个 ElasticSearch 运行实例(节点)组合的组合体是 ElasticSearch 集群。
ElasticSearch 是天然的分布式,通过水平扩容为集群添加更多节点。
集群是去中心化的,有一个主节点(Master)。主节点是动态选举,因此不会出现单点故障。

那分片和节点的配置呢?
节点(Node
一个 ElasticSearch 运行实例就是节点。顺着集群来,任何节点都可以被选举成为主节点。主节点负责集群内所以变更,比如索引的增加、删除等。所以集群不会因为主节点流量的增大成为瓶颈。因为任何节点都会成为主节点。
下面有 3 个节点,第 1 个节点有:2 个主分片和 1 个副分片。如图:
elas_node.png


那么,只有一个节点的 ElasticSearch 服务会存在瓶颈。如图:
elas_0201.png


分片(Shard)
分片,是 ES 节点中最小的工作单元。分片仅仅保存全部数据的一部分,分片的集合是 ES 的索引。分片包括主分片和副分片,主分片是副分片的拷贝。主分片和副分片地工作基本没有大的区别。
在索引中全文搜索,然后会查询到每个分片,将每个分配的结果进行全局地收集处理,并返回。

二、Elasticsearch 工作原理

2.1 文档存储的路由
当索引到一个文档(如:报价系统),具体的文档数据(如:报价数据)会存储到一个分片。具体文档数据会被切分,并分别存储在分片 1 或者 分片 2 … 
那么如何确定存在哪个分片呢?
存储路由过程由下面地公式决定:
shard = hash(routing) % number_of_primary_shards

routing 是可变值,支持自定义,默认文档 _id。
hash 函数生成数字,经过取余算法得到余数,那么这个余数就是分片的位置。
这是不是有点负载均衡的类似。
 
2.2 如何健康检查
集群名,集群的健康状态
GET http://127.0.0.1:9200/_cluster/stats 
{
"cluster_name": "elasticsearch",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}

status 字段是需要我们关心的。状态可能是下列三个值之一:
green
所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
yellow
所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。
red
至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。


active_primary_shards 集群中的主分片数量
active_shards 所有分片的汇总值
relocating_shards 显示当前正在从一个节点迁往其他节点的分片的数量。通常来说应该是 0,不过在 Elasticsearch 发现集群不太均衡时,该值会上涨。比如说:添加了一个新节点,或者下线了一个节点。
initializing_shards 刚刚创建的分片的个数。
unassigned_shards 已经在集群状态中存在的分片。
 
2.3 如何水平扩容
主分片在索引创建已经确定。读操作可以同时被主分片和副分片处理。因此,更多的分片,会拥有更高的吞吐量。自然,需要增加更多的硬件资源支持吞吐量。
说明,这里无法提高性能,因为每个分片获得的资源会变少。
动态调整副本分片数,按需伸缩集群,比如把副本数默认值为 1 增加到 2:
PUT /blogs/_settings
{
"number_of_replicas" : 2
}


三、小结
简单初探了下 ElasticSearch 的相关内容。后面会主要落地到实战,关于  spring-data-elasticsearch 这块的实战。

最后,《 深入浅出 spring-data-elasticsearch 》小连载目录如下:
深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一)
深入浅出 spring-data-elasticsearch - 概述(二)
深入浅出 spring-data-elasticsearch - 基本案例详解(三)
深入浅出 spring-data-elasticsearch - 复杂案例详解(四)
深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五)
 

资料:
官方《Elasticsearch: 权威指南》
https://www.elastic.co/guide/c ... .html
 


本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处


SpringData ES中一些底层原理的分析

Spring Dataformat_coder 发表了文章 • 7 个评论 • 958 次浏览 • 2017-06-02 18:31 • 来自相关话题

之前写过一篇SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题,顺便深入学习下Spring Data Elasticsearch。
 
Spring Data Elasticsearch是Spring Data针对Elasticsearch的实现。

它跟Spring Data一样,提供了Repository接口,我们只需要定义一个新的接口并继承这个Repository接口,然后就可以注入这个新的接口使用了。
 
定义接口:
 
@Repository
public interface TaskRepository extends ElasticsearchRepository<Task, String> { }
注入接口进行使用:
 
@Autowired
private TaskRepository taskRepository;

....
taskRepository.save(task);
Repository接口的代理生成
 
上面的例子中TaskRepository是个接口,而我们却直接注入了这个接口并调用方法;很明显,这是错误的。

其实SpringData ES内部基于这个TaskRepository接口构造一个SimpleElasticsearchRepository,真正被注入的是这个SimpleElasticsearchRepository。

这个过程是如何实现的呢?  来分析一下。

ElasticsearchRepositoriesAutoConfiguration自动化配置类会导入ElasticsearchRepositoriesRegistrar这个ImportBeanDefinitionRegistrar。

ElasticsearchRepositoriesRegistrar继承自AbstractRepositoryConfigurationSourceSupport,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器调用registerBeanDefinitions进行自定义bean的注册。

ElasticsearchRepositoriesRegistrar委托给RepositoryConfigurationDelegate完成bean的解析。

整个解析过程可以分3个步骤:
 
找出模块中的org.springframework.data.repository.Repository接口的实现类或者org.springframework.data.repository.RepositoryDefinition注解的修饰类,并会过滤掉org.springframework.data.repository.NoRepositoryBean注解的修饰类。找出后封装到RepositoryConfiguration中遍历这些RepositoryConfiguration,然后构造成BeanDefinition并注册到Spring容器中。需要注意的是这些RepositoryConfiguration会以beanClass为ElasticsearchRepositoryFactoryBean这个类的方式被注册,并把对应的Repository接口当做构造参数传递给ElasticsearchRepositoryFactoryBean,还会设置相应的属性比如elasticsearchOperations、evaluationContextProvider、namedQueries、repositoryBaseClass、lazyInitqueryLookupStrategyKeyElasticsearchRepositoryFactoryBean被实例化的时候设置对应的构造参数和属性。设置完毕以后调用afterPropertiesSet方法(实现了InitializingBean接口)。在afterPropertiesSet方法内部会去创建RepositoryFactorySupport类,并进行一些初始化,比如namedQueries、repositoryBaseClass等。然后通过这个RepositoryFactorySupport的getRepository方法基于Repository接口创建出代理类,并使用AOP添加了几个MethodInterceptor
 
// 遍历基于第1步条件得到的RepositoryConfiguration集合
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {
// 构造出BeanDefinitionBuilder
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);

extension.postProcess(definitionBuilder, configurationSource);

if (isXml) {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource);
} else {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
}

// 使用命名策略生成bean的名字
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName,
configuration.getRepositoryInterface(), extension.getRepositoryFactoryClassName());
}

beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
// 注册到Spring容器中
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}

// build方法
public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {

Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
// 得到factoryBeanName,这里会使用extension.getRepositoryFactoryClassName()去获得
// extension.getRepositoryFactoryClassName()返回的正是ElasticsearchRepositoryFactoryBean
String factoryBeanName = configuration.getRepositoryFactoryBeanName();
factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName
: extension.getRepositoryFactoryClassName();
// 基于factoryBeanName构造BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(factoryBeanName);

builder.getRawBeanDefinition().setSource(configuration.getSource());
// 设置ElasticsearchRepositoryFactoryBean的构造参数,这里是对应的Repository接口
// 设置一些的属性值
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.addPropertyValue("repositoryBaseClass", configuration.getRepositoryBaseClassName());

NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder(
extension.getDefaultNamedQueryLocation());

if (StringUtils.hasText(configuration.getNamedQueriesLocation())) {
definitionBuilder.setLocations(configuration.getNamedQueriesLocation());
}

builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource()));
// 查找是否有对应Repository接口的自定义实现类
String customImplementationBeanName = registerCustomImplementation(configuration);
// 存在自定义实现类的话,设置到属性中
if (customImplementationBeanName != null) {
builder.addPropertyReference("customImplementation", customImplementationBeanName);
builder.addDependsOn(customImplementationBeanName);
}

RootBeanDefinition evaluationContextProviderDefinition = new RootBeanDefinition(
ExtensionAwareEvaluationContextProvider.class);
evaluationContextProviderDefinition.setSource(configuration.getSource());
// 设置一些的属性值
builder.addPropertyValue("evaluationContextProvider", evaluationContextProviderDefinition);

return builder;
}

// RepositoryFactorySupport的getRepository方法,获得Repository接口的代理类
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {

// 获取Repository的元数据
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
// 获取Repository的自定义实现类
Class<?> customImplementationClass = null == customImplementation ? null : customImplementation.getClass();
// 根据元数据和自定义实现类得到Repository的RepositoryInformation信息类
// 获取信息类的时候如果发现repositoryBaseClass是空的话会根据meta中的信息去自动匹配
// 具体匹配过程在下面的getRepositoryBaseClass方法中说明
RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass);
// 验证
validate(information, customImplementation);
// 得到最终的目标类实例,会通过repositoryBaseClass去查找
Object target = getTargetRepository(information);

// 创建代理工厂
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(new Class[] { repositoryInterface, Repository.class });
// 进行aop相关的设置
result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);

if (TRANSACTION_PROXY_TYPE != null) {
result.addInterface(TRANSACTION_PROXY_TYPE);
}
// 使用RepositoryProxyPostProcessor处理
for (RepositoryProxyPostProcessor processor : postProcessors) {
processor.postProcess(result, information);
}

if (IS_JAVA_8) {
// 如果是JDK8的话,添加DefaultMethodInvokingMethodInterceptor
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}

// 添加QueryExecutorMethodInterceptor
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target));
// 使用代理工厂创建出代理类,这里是使用jdk内置的代理模式
return (T) result.getProxy(classLoader);
}

// 目标类的获取
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// 如果Repository接口属于QueryDsl,抛出异常。目前还不支持
if (isQueryDslRepository(metadata.getRepositoryInterface())) {
throw new IllegalArgumentException("QueryDsl Support has not been implemented yet.");
}
// 如果主键是数值类型的话,repositoryBaseClass为NumberKeyedRepository
if (Integer.class.isAssignableFrom(metadata.getIdType())
|| Long.class.isAssignableFrom(metadata.getIdType())
|| Double.class.isAssignableFrom(metadata.getIdType())) {
return NumberKeyedRepository.class;
} else if (metadata.getIdType() == String.class) {
// 如果主键是String类型的话,repositoryBaseClass为SimpleElasticsearchRepository
return SimpleElasticsearchRepository.class;
} else if (metadata.getIdType() == UUID.class) {
// 如果主键是UUID类型的话,repositoryBaseClass为UUIDElasticsearchRepository
return UUIDElasticsearchRepository.class;
} else {
// 否则报错
throw new IllegalArgumentException("Unsupported ID type " + metadata.getIdType());
}
}
ElasticsearchRepositoryFactoryBean是一个FactoryBean接口的实现类,getObject方法返回的上面提到的getRepository方法返回的代理对象;getObjectType方法返回的是对应Repository接口类型。

我们文章一开始提到的注入TaskRepository的时候,实际上这个对象是ElasticsearchRepositoryFactoryBean类型的实例,只不过ElasticsearchRepositoryFactoryBean实现了FactoryBean接口,所以注入的时候会得到一个代理对象,这个代理对象是由jdk内置的代理生成的,并且它的target对象是SimpleElasticsearchRepository(主键是String类型)。
 
 
SpringData ES中ElasticsearchOperations的介绍
 
ElasticsearchTemplate实现了ElasticsearchOperations接口。

ElasticsearchOperations接口是SpringData对Elasticsearch操作的一层封装,比如有创建索引createIndex方法、获取索引的设置信息getSetting方法、查询对象queryForObject方法、分页查询方法queryForPage、删除文档delete方法、更新文档update方法等等。

ElasticsearchTemplate是具体的实现类,它有这些属性:
 
// elasticsearch提供的基于java的客户端连接接口。java对es集群的操作使用这个接口完成
private Client client;
// 一个转换器接口,定义了2个方法,分别可以获得MappingContext和ConversionService
// MappingContext接口用于获取所有的持久化实体和这些实体的属性
// ConversionService目前在SpringData ES中没有被使用
private ElasticsearchConverter elasticsearchConverter;
// 内部使用EntityMapper完成对象到json字符串和json字符串到对象的映射。默认使用jackson完成映射,可自定义
private ResultsMapper resultsMapper;
// 查询超时时间
private String searchTimeout;
Client接口在ElasticsearchAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public Client elasticsearchClient() {
try {
return createClient();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
ElasticsearchTemplate、ElasticsearchConverter以及SimpleElasticsearchMappingContext在ElasticsearchDataAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public ElasticsearchTemplate elasticsearchTemplate(Client client,
ElasticsearchConverter converter) {
try {
return new ElasticsearchTemplate(client, converter);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

@Bean
@ConditionalOnMissingBean
public ElasticsearchConverter elasticsearchConverter(
SimpleElasticsearchMappingContext mappingContext) {
return new MappingElasticsearchConverter(mappingContext);
}

@Bean
@ConditionalOnMissingBean
public SimpleElasticsearchMappingContext mappingContext() {
return new SimpleElasticsearchMappingContext();
}
 需要注意的是这个bean被自动化配置类构造的前提是它们在Spring容器中并不存在。
 
Repository的调用过程
 
以自定义的TaskRepository的save方法为例,大致的执行流程如下所示:



SimpleElasticsearchRepository的save方法具体的分析在SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题中分析过。

像自定义的Repository查询方法,或者Repository接口的自定义实现类的操作这些底层,可以去QueryExecutorMethodInterceptor中查看,大家有兴趣的可以自行查看源码。
  查看全部
之前写过一篇SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题,顺便深入学习下Spring Data Elasticsearch。
 
Spring Data ElasticsearchSpring Data针对Elasticsearch的实现。

它跟Spring Data一样,提供了Repository接口,我们只需要定义一个新的接口并继承这个Repository接口,然后就可以注入这个新的接口使用了。
 
定义接口:
 
@Repository
public interface TaskRepository extends ElasticsearchRepository<Task, String> { }

注入接口进行使用:
 
@Autowired
private TaskRepository taskRepository;

....
taskRepository.save(task);

Repository接口的代理生成
 
上面的例子中TaskRepository是个接口,而我们却直接注入了这个接口并调用方法;很明显,这是错误的。

其实SpringData ES内部基于这个TaskRepository接口构造一个SimpleElasticsearchRepository,真正被注入的是这个SimpleElasticsearchRepository。

这个过程是如何实现的呢?  来分析一下。

ElasticsearchRepositoriesAutoConfiguration自动化配置类会导入ElasticsearchRepositoriesRegistrar这个ImportBeanDefinitionRegistrar。

ElasticsearchRepositoriesRegistrar继承自AbstractRepositoryConfigurationSourceSupport,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器调用registerBeanDefinitions进行自定义bean的注册。

ElasticsearchRepositoriesRegistrar委托给RepositoryConfigurationDelegate完成bean的解析。

整个解析过程可以分3个步骤:
 
  1. 找出模块中的org.springframework.data.repository.Repository接口的实现类或者org.springframework.data.repository.RepositoryDefinition注解的修饰类,并会过滤掉org.springframework.data.repository.NoRepositoryBean注解的修饰类。找出后封装到RepositoryConfiguration中
  2. 遍历这些RepositoryConfiguration,然后构造成BeanDefinition并注册到Spring容器中。需要注意的是这些RepositoryConfiguration会以beanClass为ElasticsearchRepositoryFactoryBean这个类的方式被注册,并把对应的Repository接口当做构造参数传递给ElasticsearchRepositoryFactoryBean,还会设置相应的属性比如elasticsearchOperations、evaluationContextProvider、namedQueries、repositoryBaseClass、lazyInitqueryLookupStrategyKey
  3. ElasticsearchRepositoryFactoryBean被实例化的时候设置对应的构造参数和属性。设置完毕以后调用afterPropertiesSet方法(实现了InitializingBean接口)。在afterPropertiesSet方法内部会去创建RepositoryFactorySupport类,并进行一些初始化,比如namedQueries、repositoryBaseClass等。然后通过这个RepositoryFactorySupport的getRepository方法基于Repository接口创建出代理类,并使用AOP添加了几个MethodInterceptor

 
// 遍历基于第1步条件得到的RepositoryConfiguration集合
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {
// 构造出BeanDefinitionBuilder
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);

extension.postProcess(definitionBuilder, configurationSource);

if (isXml) {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource);
} else {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
}

// 使用命名策略生成bean的名字
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName,
configuration.getRepositoryInterface(), extension.getRepositoryFactoryClassName());
}

beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
// 注册到Spring容器中
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}

// build方法
public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {

Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
// 得到factoryBeanName,这里会使用extension.getRepositoryFactoryClassName()去获得
// extension.getRepositoryFactoryClassName()返回的正是ElasticsearchRepositoryFactoryBean
String factoryBeanName = configuration.getRepositoryFactoryBeanName();
factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName
: extension.getRepositoryFactoryClassName();
// 基于factoryBeanName构造BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(factoryBeanName);

builder.getRawBeanDefinition().setSource(configuration.getSource());
// 设置ElasticsearchRepositoryFactoryBean的构造参数,这里是对应的Repository接口
// 设置一些的属性值
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.addPropertyValue("repositoryBaseClass", configuration.getRepositoryBaseClassName());

NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder(
extension.getDefaultNamedQueryLocation());

if (StringUtils.hasText(configuration.getNamedQueriesLocation())) {
definitionBuilder.setLocations(configuration.getNamedQueriesLocation());
}

builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource()));
// 查找是否有对应Repository接口的自定义实现类
String customImplementationBeanName = registerCustomImplementation(configuration);
// 存在自定义实现类的话,设置到属性中
if (customImplementationBeanName != null) {
builder.addPropertyReference("customImplementation", customImplementationBeanName);
builder.addDependsOn(customImplementationBeanName);
}

RootBeanDefinition evaluationContextProviderDefinition = new RootBeanDefinition(
ExtensionAwareEvaluationContextProvider.class);
evaluationContextProviderDefinition.setSource(configuration.getSource());
// 设置一些的属性值
builder.addPropertyValue("evaluationContextProvider", evaluationContextProviderDefinition);

return builder;
}

// RepositoryFactorySupport的getRepository方法,获得Repository接口的代理类
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {

// 获取Repository的元数据
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
// 获取Repository的自定义实现类
Class<?> customImplementationClass = null == customImplementation ? null : customImplementation.getClass();
// 根据元数据和自定义实现类得到Repository的RepositoryInformation信息类
// 获取信息类的时候如果发现repositoryBaseClass是空的话会根据meta中的信息去自动匹配
// 具体匹配过程在下面的getRepositoryBaseClass方法中说明
RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass);
// 验证
validate(information, customImplementation);
// 得到最终的目标类实例,会通过repositoryBaseClass去查找
Object target = getTargetRepository(information);

// 创建代理工厂
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(new Class[] { repositoryInterface, Repository.class });
// 进行aop相关的设置
result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);

if (TRANSACTION_PROXY_TYPE != null) {
result.addInterface(TRANSACTION_PROXY_TYPE);
}
// 使用RepositoryProxyPostProcessor处理
for (RepositoryProxyPostProcessor processor : postProcessors) {
processor.postProcess(result, information);
}

if (IS_JAVA_8) {
// 如果是JDK8的话,添加DefaultMethodInvokingMethodInterceptor
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}

// 添加QueryExecutorMethodInterceptor
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target));
// 使用代理工厂创建出代理类,这里是使用jdk内置的代理模式
return (T) result.getProxy(classLoader);
}

// 目标类的获取
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// 如果Repository接口属于QueryDsl,抛出异常。目前还不支持
if (isQueryDslRepository(metadata.getRepositoryInterface())) {
throw new IllegalArgumentException("QueryDsl Support has not been implemented yet.");
}
// 如果主键是数值类型的话,repositoryBaseClass为NumberKeyedRepository
if (Integer.class.isAssignableFrom(metadata.getIdType())
|| Long.class.isAssignableFrom(metadata.getIdType())
|| Double.class.isAssignableFrom(metadata.getIdType())) {
return NumberKeyedRepository.class;
} else if (metadata.getIdType() == String.class) {
// 如果主键是String类型的话,repositoryBaseClass为SimpleElasticsearchRepository
return SimpleElasticsearchRepository.class;
} else if (metadata.getIdType() == UUID.class) {
// 如果主键是UUID类型的话,repositoryBaseClass为UUIDElasticsearchRepository
return UUIDElasticsearchRepository.class;
} else {
// 否则报错
throw new IllegalArgumentException("Unsupported ID type " + metadata.getIdType());
}
}

ElasticsearchRepositoryFactoryBean是一个FactoryBean接口的实现类,getObject方法返回的上面提到的getRepository方法返回的代理对象;getObjectType方法返回的是对应Repository接口类型。

我们文章一开始提到的注入TaskRepository的时候,实际上这个对象是ElasticsearchRepositoryFactoryBean类型的实例,只不过ElasticsearchRepositoryFactoryBean实现了FactoryBean接口,所以注入的时候会得到一个代理对象,这个代理对象是由jdk内置的代理生成的,并且它的target对象是SimpleElasticsearchRepository(主键是String类型)。
 
 
SpringData ES中ElasticsearchOperations的介绍
 
ElasticsearchTemplate实现了ElasticsearchOperations接口。

ElasticsearchOperations接口是SpringData对Elasticsearch操作的一层封装,比如有创建索引createIndex方法、获取索引的设置信息getSetting方法、查询对象queryForObject方法、分页查询方法queryForPage、删除文档delete方法、更新文档update方法等等。

ElasticsearchTemplate是具体的实现类,它有这些属性:
 
// elasticsearch提供的基于java的客户端连接接口。java对es集群的操作使用这个接口完成
private Client client;
// 一个转换器接口,定义了2个方法,分别可以获得MappingContext和ConversionService
// MappingContext接口用于获取所有的持久化实体和这些实体的属性
// ConversionService目前在SpringData ES中没有被使用
private ElasticsearchConverter elasticsearchConverter;
// 内部使用EntityMapper完成对象到json字符串和json字符串到对象的映射。默认使用jackson完成映射,可自定义
private ResultsMapper resultsMapper;
// 查询超时时间
private String searchTimeout;

Client接口在ElasticsearchAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public Client elasticsearchClient() {
try {
return createClient();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

ElasticsearchTemplate、ElasticsearchConverter以及SimpleElasticsearchMappingContext在ElasticsearchDataAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public ElasticsearchTemplate elasticsearchTemplate(Client client,
ElasticsearchConverter converter) {
try {
return new ElasticsearchTemplate(client, converter);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

@Bean
@ConditionalOnMissingBean
public ElasticsearchConverter elasticsearchConverter(
SimpleElasticsearchMappingContext mappingContext) {
return new MappingElasticsearchConverter(mappingContext);
}

@Bean
@ConditionalOnMissingBean
public SimpleElasticsearchMappingContext mappingContext() {
return new SimpleElasticsearchMappingContext();
}

 需要注意的是这个bean被自动化配置类构造的前提是它们在Spring容器中并不存在。
 
Repository的调用过程
 
以自定义的TaskRepository的save方法为例,大致的执行流程如下所示:



SimpleElasticsearchRepository的save方法具体的分析在SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题中分析过。

像自定义的Repository查询方法,或者Repository接口的自定义实现类的操作这些底层,可以去QueryExecutorMethodInterceptor中查看,大家有兴趣的可以自行查看源码。
 

Spring Data Redis - Could not safely identify store assignment for repository

回复

Spring Datawayne 发起了问题 • 1 人关注 • 0 个回复 • 174 次浏览 • 2017-09-04 13:45 • 来自相关话题

Spring Data ElasticSearch 要视频吗?

回复

Spring Data泥瓦匠BYSocket 回复了问题 • 14 人关注 • 14 个回复 • 998 次浏览 • 2017-07-21 00:31 • 来自相关话题

关于Spring jpa得到的Page<T>的分页问题

回复

Spring Bootmaling 回复了问题 • 2 人关注 • 1 个回复 • 229 次浏览 • 2017-06-29 14:47 • 来自相关话题

Spring Data JPA系列:数据更新(Update)

Spring Datawayne 发表了文章 • 0 个评论 • 451 次浏览 • 2017-07-11 11:15 • 来自相关话题

上次通过《Spring Data JPA系列:使用@Modifying修改(Modifying queries)》介绍了数据更新的方式,这种更新方式会很不方便,写的时候也比较麻烦,可以为更新密码、更新用户名等一些特殊的更新单独定义,但是对大多数数据操作是不方便的,比如我要更新一条有一百个字段的数据,这时候如果要通过Modifying方式就非常的不方便,因此,我们需要一种新的方式来解救。
通过阅读Spring-Data-JPA相关的文档和博客,找到了对应的解决方案,就是使用`save()`方法,经过测试,可用。
我们平时对`save()`方法的理解,大多是等同于`insert()`,主要是指新增一条数据,而JPA的`save()`方法包含了`merge()`的概念,就是说,如果save的对象不存在primary key或者primary key值在database内不存在的时候会新添加一条数据,如果primary key 存在并且primary key已经在database中存在,那就会依据primary key对该条数据进行更新,这是我们乐意见到的。

参考的文章:```java
https://stackoverflow.com/ques ... a-jpa
```

相关描述如下:

Identity of entities is defined by their primary keys. Since firstname and lastname are not parts of the primary key, you cannot tell JPA to treat Users with the same firstnames and lastnames as equal if they have different userIds.

So, if you want to update a User identified by its firstname and lastname, you need to find that User by a query, and then change appropriate fields of the object your found. These changes will be flushed to the database automatically at the end of transaction, so that you don't need to do anything to save these changes explicitly.

##### EDIT:

Perhaps I should elaborate on overall semantics of JPA. There are two main approaches to design of persistence APIs:

- insert/update approach. When you need to modify the database you should call methods of persistence API explicitly: you call insert to insert an object, or update to save new state of the object to the database.
- Unit of Work approach. In this case you have a set of objects managed by persistence library. All changes you make to these objects will be flushed to the database automatically at the end of Unit of Work (i.e. at the end of the current transaction in typical case). When you need to insert new record to the database, you make the corresponding object managed. Managed objects are identified by their primary keys, so that if you make an object with predefined primary key managed, it will be associated with the database record of the same id, and state of this object will be propagated to that record automatically.

JPA follows the later approach. save() in Spring Data JPA is backed by merge() in plain JPA, therefore it makes your entity managed as described above. It means that calling save() on an object with predefined id will update the corresponding database record rather than insert a new one, and also explains why save() is not called create().
 
 
参考:
官方文档,https://docs.spring.io/spring- ... /html 
DEMO,https://github.com/icnws/spring-data-jpa-demo
个人博客,http://www.icnws.com

版权归作者所有,如需转载,请与作者联系! 查看全部
上次通过《Spring Data JPA系列:使用@Modifying修改(Modifying queries)》介绍了数据更新的方式,这种更新方式会很不方便,写的时候也比较麻烦,可以为更新密码、更新用户名等一些特殊的更新单独定义,但是对大多数数据操作是不方便的,比如我要更新一条有一百个字段的数据,这时候如果要通过Modifying方式就非常的不方便,因此,我们需要一种新的方式来解救。
通过阅读Spring-Data-JPA相关的文档和博客,找到了对应的解决方案,就是使用`save()`方法,经过测试,可用。
我们平时对`save()`方法的理解,大多是等同于`insert()`,主要是指新增一条数据,而JPA的`save()`方法包含了`merge()`的概念,就是说,如果save的对象不存在primary key或者primary key值在database内不存在的时候会新添加一条数据,如果primary key 存在并且primary key已经在database中存在,那就会依据primary key对该条数据进行更新,这是我们乐意见到的。

参考的文章:```java
https://stackoverflow.com/ques ... a-jpa
```

相关描述如下:

Identity of entities is defined by their primary keys. Since firstname and lastname are not parts of the primary key, you cannot tell JPA to treat Users with the same firstnames and lastnames as equal if they have different userIds.

So, if you want to update a User identified by its firstname and lastname, you need to find that User by a query, and then change appropriate fields of the object your found. These changes will be flushed to the database automatically at the end of transaction, so that you don't need to do anything to save these changes explicitly.

##### EDIT:

Perhaps I should elaborate on overall semantics of JPA. There are two main approaches to design of persistence APIs:

- insert/update approach. When you need to modify the database you should call methods of persistence API explicitly: you call insert to insert an object, or update to save new state of the object to the database.
- Unit of Work approach. In this case you have a set of objects managed by persistence library. All changes you make to these objects will be flushed to the database automatically at the end of Unit of Work (i.e. at the end of the current transaction in typical case). When you need to insert new record to the database, you make the corresponding object managed. Managed objects are identified by their primary keys, so that if you make an object with predefined primary key managed, it will be associated with the database record of the same id, and state of this object will be propagated to that record automatically.

JPA follows the later approach. save() in Spring Data JPA is backed by merge() in plain JPA, therefore it makes your entity managed as described above. It means that calling save() on an object with predefined id will update the corresponding database record rather than insert a new one, and also explains why save() is not called create().
 
 
参考:
官方文档,https://docs.spring.io/spring- ... /html 
DEMO,https://github.com/icnws/spring-data-jpa-demo
个人博客,http://www.icnws.com


版权归作者所有,如需转载,请与作者联系!


spring-data-es 视频公开课(二)《spring-data-elasticsearch 简单案例搭建》

开源项目泥瓦匠BYSocket 发表了文章 • 5 个评论 • 1469 次浏览 • 2017-06-27 20:46 • 来自相关话题

spring-data-es 视频公开课(二)《spring-data-elasticsearch 简单案例搭建》
视频如下:







一、前言
a. 版权声明 
本视频由 泥瓦匠BYSocket 支持 Spring For All 社区 www.spring4all.com ,版权为个人及社区所有,欢迎转发。
b. 视频主旨
学会 ElasticSearch 及 spring-data-elasticsearch项目实战教学

视频系列目录
spring-data-es 视频公开课(一)《elasticsearch 及 head 插件安装使用》spring-data-es 视频公开课(二)《spring-data-elasticsearch 简单案例搭建》spring-data-es 视频公开课(三)《spring-data-elasticsearch 实战案例搭建》

二、正文

本节提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、搭建 spring-data-elasticsearch-crud 工程

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
1.后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

2.添加 maven 依赖
pom.xml:<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>


这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

3.新建启动 Application 类
Application.java:// Spring Boot 应用的标识
@SpringBootApplication
public class Application {
public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
SpringApplication.run(Application.class,args);
}
}

4.编写 ES 代码(重点)
application.properties
 配置 ES 地址# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)


ES domain 实体类
City.java/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}


注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase
 

ES 数据操作层
CityRepository.java/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}
接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot


城市 ES 业务接口类
CityService.java/**
* 城市 ES 业务接口类
*
*/
public interface CityService {
/**
* 新增 ES 城市信息
*
* @param city
* @return
*/
Long saveCity(City city);
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* @param description
* @return
*/
List<City> findByDescription(String description);
/**
* NOT 语句查询
*
* @param description
* @return
*/
List<City> findByDescriptionNot(String description);
/**
* LIKE 语句查询
*
* @param description
* @return
*/
List<City> findByDescriptionLike(String description);
}




城市 ES 业务逻辑实现类
CityESServiceImpl.java/**
* 城市 ES 业务逻辑实现类
* <p>
* Created by bysocket on 07/02/2017.
*/
@Service
public class CityESServiceImpl implements CityService {
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
// 分页参数 -> TODO 代码可迁移到具体项目的公共 common 模块
private static final Integer pageNumber = 0;
private static final Integer pageSize = 10;
Pageable pageable = new PageRequest(pageNumber, pageSize);
// ES 操作类
@Autowired
CityRepository cityRepository;
public Long saveCity(City city) {
City cityResult = cityRepository.save(city);
return cityResult.getId();
}
public List<City> findByDescriptionAndScore(String description, Integer score) {
return cityRepository.findByDescriptionAndScore(description, score);
}
public List<City> findByDescriptionOrScore(String description, Integer score) {
return cityRepository.findByDescriptionOrScore(description, score);
}
public List<City> findByDescription(String description) {
return cityRepository.findByDescription(description, pageable).getContent();
}
public List<City> findByDescriptionNot(String description) {
return cityRepository.findByDescriptionNot(description, pageable).getContent();
}
public List<City> findByDescriptionLike(String description) {
return cityRepository.findByDescriptionLike(description, pageable).getContent();
}
}




城市 Controller 实现 Restful HTTP 服务
CityRestController.java/**
* 城市 Controller 实现 Restful HTTP 服务
* <p>
* Created by bysocket on 03/05/2017.
*/
@RestController
public class CityRestController {
@Autowired
private CityService cityService;
/**
* 插入 ES 新城市
*
* @param city
* @return
*/
@RequestMapping(value = "/api/city", method = RequestMethod.POST)
public Long createCity(@RequestBody City city) {
return cityService.saveCity(city);
}
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
@RequestMapping(value = "/api/city/and/find", method = RequestMethod.GET)
public List<City> findByDescriptionAndScore(@RequestParam(value = "description") String description,
@RequestParam(value = "score") Integer score) {
return cityService.findByDescriptionAndScore(description, score);
}
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
@RequestMapping(value = "/api/city/or/find", method = RequestMethod.GET)
public List<City> findByDescriptionOrScore(@RequestParam(value = "description") String description,
@RequestParam(value = "score") Integer score) {
return cityService.findByDescriptionOrScore(description, score);
}
/**
* 查询城市描述
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/description/find", method = RequestMethod.GET)
public List<City> findByDescription(@RequestParam(value = "description") String description) {
return cityService.findByDescription(description);
}
/**
* NOT 语句查询
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/description/not/find", method = RequestMethod.GET)
public List<City> findByDescriptionNot(@RequestParam(value = "description") String description) {
return cityService.findByDescriptionNot(description);
}
/**
* LIKE 语句查询
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/like/find", method = RequestMethod.GET)
public List<City> findByDescriptionLike(@RequestParam(value = "description") String description) {
return cityService.findByDescriptionLike(description);
}
}


5.编译工程
mvn clean install

6.运行工程
右键运行 Application 应用启动类的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。

用 Postman 工具新增两个城市
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
    "id":"1",
    "score":"5",
    "name":"上海",
    "description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
    "id":"2",
    "score":"4",
    "name":"温岭",
    "description":"温岭是个沿海城市"
}

可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/。
 
下面是基本查询语句的接口:
a. 普通查询,查询城市描述
GET http://localhost:8080/api/city ... on%3D温岭
b. AND 语句查询
GET http://localhost:8080/api/city ... on%3D温岭&score=4
如果换成 score=5 ,就没有结果了。
c. OR 语句查询
GET http://localhost:8080/api/city ... on%3D上海&score=4
d. NOT 语句查询
GET http://localhost:8080/api/city ... on%3D温州
e. LIKE 语句查询
GET http://localhost:8080/api/city ... on%3D城市

三、视频小结
下一视频《spring-data-elasticsearch 实战案例搭建》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
  查看全部
spring-data-es 视频公开课(二)《spring-data-elasticsearch 简单案例搭建》
视频如下:








一、前言
a. 版权声明 
本视频由 泥瓦匠BYSocket 支持 Spring For All 社区 www.spring4all.com ,版权为个人及社区所有,欢迎转发。
b. 视频主旨
  • 学会 ElasticSearch 及 spring-data-elasticsearch
  • 项目实战教学


视频系列目录


二、正文

本节提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、搭建 spring-data-elasticsearch-crud 工程

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
1.后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

2.添加 maven 依赖
pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>



这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

3.新建启动 Application 类
Application.java:
// Spring Boot 应用的标识
@SpringBootApplication
public class Application {
public static void main(String args) {
// 程序启动入口
// 启动嵌入式的 Tomcat 并初始化 Spring 环境及其各 Spring 组件
SpringApplication.run(Application.class,args);
}
}


4.编写 ES 代码(重点)
application.properties
 配置 ES 地址
# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300

默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)


ES domain 实体类
City.java
/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}



注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase
 

ES 数据操作层
CityRepository.java
/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}

接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot


城市 ES 业务接口类
CityService.java
/**
* 城市 ES 业务接口类
*
*/
public interface CityService {
/**
* 新增 ES 城市信息
*
* @param city
* @return
*/
Long saveCity(City city);
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* @param description
* @return
*/
List<City> findByDescription(String description);
/**
* NOT 语句查询
*
* @param description
* @return
*/
List<City> findByDescriptionNot(String description);
/**
* LIKE 语句查询
*
* @param description
* @return
*/
List<City> findByDescriptionLike(String description);
}





城市 ES 业务逻辑实现类
CityESServiceImpl.java
/**
* 城市 ES 业务逻辑实现类
* <p>
* Created by bysocket on 07/02/2017.
*/
@Service
public class CityESServiceImpl implements CityService {
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
// 分页参数 -> TODO 代码可迁移到具体项目的公共 common 模块
private static final Integer pageNumber = 0;
private static final Integer pageSize = 10;
Pageable pageable = new PageRequest(pageNumber, pageSize);
// ES 操作类
@Autowired
CityRepository cityRepository;
public Long saveCity(City city) {
City cityResult = cityRepository.save(city);
return cityResult.getId();
}
public List<City> findByDescriptionAndScore(String description, Integer score) {
return cityRepository.findByDescriptionAndScore(description, score);
}
public List<City> findByDescriptionOrScore(String description, Integer score) {
return cityRepository.findByDescriptionOrScore(description, score);
}
public List<City> findByDescription(String description) {
return cityRepository.findByDescription(description, pageable).getContent();
}
public List<City> findByDescriptionNot(String description) {
return cityRepository.findByDescriptionNot(description, pageable).getContent();
}
public List<City> findByDescriptionLike(String description) {
return cityRepository.findByDescriptionLike(description, pageable).getContent();
}
}





城市 Controller 实现 Restful HTTP 服务
CityRestController.java
/**
* 城市 Controller 实现 Restful HTTP 服务
* <p>
* Created by bysocket on 03/05/2017.
*/
@RestController
public class CityRestController {
@Autowired
private CityService cityService;
/**
* 插入 ES 新城市
*
* @param city
* @return
*/
@RequestMapping(value = "/api/city", method = RequestMethod.POST)
public Long createCity(@RequestBody City city) {
return cityService.saveCity(city);
}
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
@RequestMapping(value = "/api/city/and/find", method = RequestMethod.GET)
public List<City> findByDescriptionAndScore(@RequestParam(value = "description") String description,
@RequestParam(value = "score") Integer score) {
return cityService.findByDescriptionAndScore(description, score);
}
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
@RequestMapping(value = "/api/city/or/find", method = RequestMethod.GET)
public List<City> findByDescriptionOrScore(@RequestParam(value = "description") String description,
@RequestParam(value = "score") Integer score) {
return cityService.findByDescriptionOrScore(description, score);
}
/**
* 查询城市描述
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/description/find", method = RequestMethod.GET)
public List<City> findByDescription(@RequestParam(value = "description") String description) {
return cityService.findByDescription(description);
}
/**
* NOT 语句查询
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/description/not/find", method = RequestMethod.GET)
public List<City> findByDescriptionNot(@RequestParam(value = "description") String description) {
return cityService.findByDescriptionNot(description);
}
/**
* LIKE 语句查询
*
* @param description
* @return
*/
@RequestMapping(value = "/api/city/like/find", method = RequestMethod.GET)
public List<City> findByDescriptionLike(@RequestParam(value = "description") String description) {
return cityService.findByDescriptionLike(description);
}
}



5.编译工程
mvn clean install

6.运行工程
右键运行 Application 应用启动类的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。

用 Postman 工具新增两个城市
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
    "id":"1",
    "score":"5",
    "name":"上海",
    "description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
    "id":"2",
    "score":"4",
    "name":"温岭",
    "description":"温岭是个沿海城市"
}

可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/
 
下面是基本查询语句的接口:
a. 普通查询,查询城市描述
GET http://localhost:8080/api/city ... on%3D温岭
b. AND 语句查询
GET http://localhost:8080/api/city ... on%3D温岭&score=4
如果换成 score=5 ,就没有结果了。
c. OR 语句查询
GET http://localhost:8080/api/city ... on%3D上海&score=4
d. NOT 语句查询
GET http://localhost:8080/api/city ... on%3D温州
e. LIKE 语句查询
GET http://localhost:8080/api/city ... on%3D城市

三、视频小结
下一视频《spring-data-elasticsearch 实战案例搭建》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 

spring-data-es 视频公开课(一)《elasticsearch 及 head 插件安装使用》

开源项目泥瓦匠BYSocket 发表了文章 • 1 个评论 • 1600 次浏览 • 2017-06-26 23:05 • 来自相关话题

spring-data-es 视频公开课(一)《elasticsearch 及 head 插件安装使用》 
视频如下:



 

一、前言
a. 版权声明 
本视频由 泥瓦匠BYSocket 支持 Spring For All 社区 www.spring4all.com ,版权为个人及社区所有,欢迎转发。

b. 视频主旨
学会 ElasticSearch 及 spring-data-elasticsearch项目实战教学

视频系列目录
spring-data-es 视频公开课(一)《elasticsearch 及 head 插件安装使用》spring-data-es 视频公开课(二)《spring-data-elasticsearch 简单案例搭建》spring-data-es 视频公开课(三)《spring-data-elasticsearch 实战案例搭建》

二、正文
本节提纲
一、Elasticsearch 安装
二、可视化插件 elasticsearch-head 安装
三、小结

运行环境
Mac OS 10.12.x
Elasticsearch 2.3.2

一、Elasticsearch 安装
什么是 Elasticsearch ?
Elasticsearch 是一个基于 Apache Lucene(TM) 的开源搜索引擎。无论在开源还是专有领域,Lucene 可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。并通过简单的 HTTP RESTful API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单。
Elasticsearch 不仅仅是 Lucene 和全文搜索,我们还能这样去描述它:
分布式的实时文件存储,每个字段都被索引并可被搜索分布式的实时分析搜索引擎可以扩展到上百台服务器,处理PB级结构化或非结构化数据


下面开始具体安装步骤:
1. .tar.gz 安装包安装 Elasticsearch 2.3.2
首先打开官网下载页 https://www.elastic.co/downloads/elasticsearch ,下载对应的 elasticsearch-2.3.2.tar.gz 文件,本文下载到 /java/es 目录。然后在文件的当前目录,通过 tar 命令解压安装包完成安装。tar -xzf elasticsearch-2.3.2.tar.gz
cd elasticsearch-2.3.2
2. 运行
一般在后台起守护线程启动 Elasticsearch,在命令行加入 -d 指定。自然,也可以加入 -p ,可将进程 ID 记录到文件中。./bin/elasticsearch -d








访问 http://localhost:9200/ ,可以看到成功运行的案例,返回的 JSON 页面。如图:
要关闭 Elasticsearch 进程,需要通过 ps 找到对应的 pid,在 kill pid 即可。ps aux |grep elasticsearch
kill -7 pid

二、可视化插件 elasticsearch-head 安装
官方 GitHub 地址:https://github.com/mobz/elasticsearch-head。
安装也很简单,安装 README 步骤走就好了:sudo ./bin/plugin install mobz/elasticsearch-head

访问 http://localhost:9200/_plugin/head/ ,右上角表示连接上了上小节启动的 Elasticsearch。
 

三、小结
本文写的比较基础,记录下主要两个点:
A、为啥不用高版本 ES?
- 版本问题,目前spring-data-elasticsearch 最高版本还只是 snapshot 版。所以 spring boot 的 starter 不敢用。详细点击 spring-data-elasticsearch Github 地址:
https://github.com/spring-proj ... earch
- 业界还是比较流行 2.3.2 和 最高版本 两种

B、为啥项目中不用 HTTP 去调用?
- 其实是可以的,HTTP 比较轻量级
- 本系列主要实践中总结 spring-data-elasticsearch ,spring-data-elasticsearch 提供对 ES 数据的访问



  查看全部
spring-data-es 视频公开课(一)《elasticsearch 及 head 插件安装使用》 
视频如下:




 

一、前言
a. 版权声明 
本视频由 泥瓦匠BYSocket 支持 Spring For All 社区 www.spring4all.com ,版权为个人及社区所有,欢迎转发。

b. 视频主旨
  • 学会 ElasticSearch 及 spring-data-elasticsearch
  • 项目实战教学


视频系列目录


二、正文
本节提纲
一、Elasticsearch 安装
二、可视化插件 elasticsearch-head 安装
三、小结

运行环境
Mac OS 10.12.x
Elasticsearch 2.3.2

一、Elasticsearch 安装
什么是 Elasticsearch ?
Elasticsearch 是一个基于 Apache Lucene(TM) 的开源搜索引擎。无论在开源还是专有领域,Lucene 可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。并通过简单的 HTTP RESTful API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单。
Elasticsearch 不仅仅是 Lucene 和全文搜索,我们还能这样去描述它:
  • 分布式的实时文件存储,每个字段都被索引并可被搜索
  • 分布式的实时分析搜索引擎
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据



下面开始具体安装步骤:
1. .tar.gz 安装包安装 Elasticsearch 2.3.2
首先打开官网下载页 https://www.elastic.co/downloads/elasticsearch ,下载对应的 elasticsearch-2.3.2.tar.gz 文件,本文下载到 /java/es 目录。然后在文件的当前目录,通过 tar 命令解压安装包完成安装。
tar -xzf elasticsearch-2.3.2.tar.gz
cd elasticsearch-2.3.2

2. 运行
一般在后台起守护线程启动 Elasticsearch,在命令行加入 -d 指定。自然,也可以加入 -p ,可将进程 ID 记录到文件中。
./bin/elasticsearch -d








访问 http://localhost:9200/ ,可以看到成功运行的案例,返回的 JSON 页面。如图:
要关闭 Elasticsearch 进程,需要通过 ps 找到对应的 pid,在 kill pid 即可。
ps aux |grep elasticsearch
kill -7 pid


二、可视化插件 elasticsearch-head 安装
官方 GitHub 地址:https://github.com/mobz/elasticsearch-head
安装也很简单,安装 README 步骤走就好了:
sudo ./bin/plugin install mobz/elasticsearch-head


访问 http://localhost:9200/_plugin/head/ ,右上角表示连接上了上小节启动的 Elasticsearch。
 

三、小结
本文写的比较基础,记录下主要两个点:
A、为啥不用高版本 ES?
- 版本问题,目前spring-data-elasticsearch 最高版本还只是 snapshot 版。所以 spring boot 的 starter 不敢用。详细点击 spring-data-elasticsearch Github 地址:
https://github.com/spring-proj ... earch
- 业界还是比较流行 2.3.2 和 最高版本 两种

B、为啥项目中不用 HTTP 去调用?
- 其实是可以的,HTTP 比较轻量级
- 本系列主要实践中总结 spring-data-elasticsearch ,spring-data-elasticsearch 提供对 ES 数据的访问



 

深入浅出 spring-data-elasticsearch - 实战案例详解(四)

Spring Data泥瓦匠BYSocket 发表了文章 • 5 个评论 • 1054 次浏览 • 2017-06-22 19:14 • 来自相关话题

『  热烈的爱情到订婚早已是定点,婚一结一切了结。现在订了婚,彼此间还留着情感发展的余地,这是桩好事。- 《我们仨》 』

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2

本文提纲
一、搜索实战场景需求
二、运行 spring-data-elasticsearch-query 工程
三、spring-data-elasticsearch-query 工程代码详解
 
一、搜索实战场景需求
搜索的场景会很多,常用的搜索场景,需要搜索的字段很多,但每个字段匹配到后所占的权重又不同。比如电商网站的搜索,搜到商品名称和商品描述,自然商品名称的权重远远大于商品描述。而且单词匹配肯定不如短语匹配。这样就出现了新的需求,如何确定这些短语,即自然分词。那就利用分词器,即可得到所需要的短语,然后进行搜索。
下面介绍短语如何进行按权重分匹配搜索。
 
二、运行 spring-data-elasticsearch-query 工程
1. 后台起守护线程启动 Elasticsearchcd elasticsearch-2.3.2/
./bin/elasticsearch -d
 
git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample。
下面开始运行工程步骤(Quick Start):
2. 项目结构介绍org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置
本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-query,运行 maven 指令:mvn clean install

4.运行工程
右键运行 Application 应用启动类(位置:org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 spring-data-elasticsearch-query 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
 
下面是实战搜索语句的接口:
GET http://localhost:8080/api/city ... nt%3D城市
获取返回结果:
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
 
应用的控制台中,日志打印出查询语句的 DSL : DSL =
{
"function_score" : {
"functions" : [ {
"filter" : {
"match" : {
"name" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 1000.0
}, {
"filter" : {
"match" : {
"description" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 500.0
} ],
"score_mode" : "sum",
"min_score" : 10.0
}
}
 
三、spring-data-elasticsearch-query 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)
 

3. ES 数据操作层/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
}
接口只要继承 ElasticsearchRepository 接口类即可,具体使用的是该接口的方法: Iterable<T> search(QueryBuilder query);
Page<T> search(QueryBuilder query, Pageable pageable);
Page<T> search(SearchQuery searchQuery);
Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);

4. 实体类/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase
 
 
5. 城市 ES 业务逻辑实现类
代码如下:/**
* 城市 ES 业务逻辑实现类
* <p>
* Created by bysocket on 20/06/2017.
*/
@Service
public class CityESServiceImpl implements CityService {
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
/* 分页参数 */
Integer PAGE_SIZE = 12; // 每页数量
Integer DEFAULT_PAGE_NUMBER = 0; // 默认当前页码
/* 搜索模式 */
String SCORE_MODE_SUM = "sum"; // 权重分求和模式
Float MIN_SCORE = 10.0F; // 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
@Autowired
CityRepository cityRepository; // ES 操作类
public Long saveCity(City city) {
City cityResult = cityRepository.save(city);
return cityResult.getId();
}
@Override
public List<City> searchCity(Integer pageNumber, Integer pageSize, String searchContent) {
// 校验分页参数
if (pageSize == null || pageSize <= 0) {
pageSize = PAGE_SIZE;
}
if (pageNumber == null || pageNumber < DEFAULT_PAGE_NUMBER) {
pageNumber = DEFAULT_PAGE_NUMBER;
}
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n ");
// 构建搜索查询
SearchQuery searchQuery = getCitySearchQuery(pageNumber,pageSize,searchContent);
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n " + searchQuery.getQuery().toString());
Page<City> cityPage = cityRepository.search(searchQuery);
return cityPage.getContent();
}
/**
* 根据搜索词构造搜索查询语句
*
* 代码流程:
* - 权重分查询
* - 短语匹配
* - 设置权重分最小值
* - 设置分页参数
*
* @param pageNumber 当前页码
* @param pageSize 每页大小
* @param searchContent 搜索内容
* @return
*/
private SearchQuery getCitySearchQuery(Integer pageNumber, Integer pageSize,String searchContent) {
// 短语匹配到的搜索词,求和模式累加权重分
// 权重分查询 https://www.elastic.co/guide/c ... .html
// - 短语匹配 https://www.elastic.co/guide/c ... .html
// - 字段对应权重分设置,可以优化成 enum
// - 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
.add(QueryBuilders.matchPhraseQuery("name", searchContent),
ScoreFunctionBuilders.weightFactorFunction(1000))
.add(QueryBuilders.matchPhraseQuery("description", searchContent),
ScoreFunctionBuilders.weightFactorFunction(500))
.scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);
// 分页参数
Pageable pageable = new PageRequest(pageNumber, pageSize);
return new NativeSearchQueryBuilder()
.withPageable(pageable)
.withQuery(functionScoreQueryBuilder).build();
}
}
可以看到该过程实现了,短语精准匹配以及匹配到根据字段权重分求和,从而实现按权重搜索查询。代码流程如下:
- 权重分查询
- 短语匹配
- 设置权重分最小值
- 设置分页参数

注意:
- 字段对应权重分设置,可以优化成 enum
- 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
 
权重分查询文档:https://www.elastic.co/guide/c ... .html。
短语匹配文档: https://www.elastic.co/guide/c ... .html。
 
四、小结
Elasticsearch 还提供很多高级的搜索功能。这里提供下需要经常逛的相关网站:
Elasticsearch 中文社区 https://elasticsearch.cn/topic/elasticsearch
Elasticsearch: 权威指南-在线版 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html 
 

摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 查看全部

『  热烈的爱情到订婚早已是定点,婚一结一切了结。现在订了婚,彼此间还留着情感发展的余地,这是桩好事。- 《我们仨》 』

运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2

本文提纲
一、搜索实战场景需求
二、运行 spring-data-elasticsearch-query 工程
三、spring-data-elasticsearch-query 工程代码详解
 
一、搜索实战场景需求
搜索的场景会很多,常用的搜索场景,需要搜索的字段很多,但每个字段匹配到后所占的权重又不同。比如电商网站的搜索,搜到商品名称和商品描述,自然商品名称的权重远远大于商品描述。而且单词匹配肯定不如短语匹配。这样就出现了新的需求,如何确定这些短语,即自然分词。那就利用分词器,即可得到所需要的短语,然后进行搜索。
下面介绍短语如何进行按权重分匹配搜索。
 
二、运行 spring-data-elasticsearch-query 工程
1. 后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

 
git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample
下面开始运行工程步骤(Quick Start):
2. 项目结构介绍
org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置

本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-query,运行 maven 指令:
mvn clean install

4.运行工程
右键运行 Application 应用启动类(位置:org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 spring-data-elasticsearch-query 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}

 
下面是实战搜索语句的接口:
GET http://localhost:8080/api/city ... nt%3D城市
获取返回结果:
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

 
应用的控制台中,日志打印出查询语句的 DSL :
 DSL  = 
{
"function_score" : {
"functions" : [ {
"filter" : {
"match" : {
"name" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 1000.0
}, {
"filter" : {
"match" : {
"description" : {
"query" : "城市",
"type" : "phrase"
}
}
},
"weight" : 500.0
} ],
"score_mode" : "sum",
"min_score" : 10.0
}
}

 
三、spring-data-elasticsearch-query 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址
# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300

默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)
 

3. ES 数据操作层
/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
}

接口只要继承 ElasticsearchRepository 接口类即可,具体使用的是该接口的方法:
    Iterable<T> search(QueryBuilder query);
Page<T> search(QueryBuilder query, Pageable pageable);
Page<T> search(SearchQuery searchQuery);
Page<T> searchSimilar(T entity, String[] fields, Pageable pageable);


4. 实体类
/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}

注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase
 
 
5. 城市 ES 业务逻辑实现类
代码如下:
/**
* 城市 ES 业务逻辑实现类
* <p>
* Created by bysocket on 20/06/2017.
*/
@Service
public class CityESServiceImpl implements CityService {
private static final Logger LOGGER = LoggerFactory.getLogger(CityESServiceImpl.class);
/* 分页参数 */
Integer PAGE_SIZE = 12; // 每页数量
Integer DEFAULT_PAGE_NUMBER = 0; // 默认当前页码
/* 搜索模式 */
String SCORE_MODE_SUM = "sum"; // 权重分求和模式
Float MIN_SCORE = 10.0F; // 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
@Autowired
CityRepository cityRepository; // ES 操作类
public Long saveCity(City city) {
City cityResult = cityRepository.save(city);
return cityResult.getId();
}
@Override
public List<City> searchCity(Integer pageNumber, Integer pageSize, String searchContent) {
// 校验分页参数
if (pageSize == null || pageSize <= 0) {
pageSize = PAGE_SIZE;
}
if (pageNumber == null || pageNumber < DEFAULT_PAGE_NUMBER) {
pageNumber = DEFAULT_PAGE_NUMBER;
}
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n ");
// 构建搜索查询
SearchQuery searchQuery = getCitySearchQuery(pageNumber,pageSize,searchContent);
LOGGER.info("\n searchCity: searchContent [" + searchContent + "] \n DSL = \n " + searchQuery.getQuery().toString());
Page<City> cityPage = cityRepository.search(searchQuery);
return cityPage.getContent();
}
/**
* 根据搜索词构造搜索查询语句
*
* 代码流程:
* - 权重分查询
* - 短语匹配
* - 设置权重分最小值
* - 设置分页参数
*
* @param pageNumber 当前页码
* @param pageSize 每页大小
* @param searchContent 搜索内容
* @return
*/
private SearchQuery getCitySearchQuery(Integer pageNumber, Integer pageSize,String searchContent) {
// 短语匹配到的搜索词,求和模式累加权重分
// 权重分查询 https://www.elastic.co/guide/c ... .html
// - 短语匹配 https://www.elastic.co/guide/c ... .html
// - 字段对应权重分设置,可以优化成 enum
// - 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery()
.add(QueryBuilders.matchPhraseQuery("name", searchContent),
ScoreFunctionBuilders.weightFactorFunction(1000))
.add(QueryBuilders.matchPhraseQuery("description", searchContent),
ScoreFunctionBuilders.weightFactorFunction(500))
.scoreMode(SCORE_MODE_SUM).setMinScore(MIN_SCORE);
// 分页参数
Pageable pageable = new PageRequest(pageNumber, pageSize);
return new NativeSearchQueryBuilder()
.withPageable(pageable)
.withQuery(functionScoreQueryBuilder).build();
}
}

可以看到该过程实现了,短语精准匹配以及匹配到根据字段权重分求和,从而实现按权重搜索查询。代码流程如下:
- 权重分查询
- 短语匹配
- 设置权重分最小值
- 设置分页参数

注意:
- 字段对应权重分设置,可以优化成 enum
- 由于无相关性的分值默认为 1 ,设置权重分最小值为 10
 
权重分查询文档:https://www.elastic.co/guide/c ... .html
短语匹配文档: https://www.elastic.co/guide/c ... .html
 
四、小结
Elasticsearch 还提供很多高级的搜索功能。这里提供下需要经常逛的相关网站:
Elasticsearch 中文社区 https://elasticsearch.cn/topic/elasticsearch
Elasticsearch: 权威指南-在线版 https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html 
 


摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢!


spring boot 学习系列

开源项目hong 发表了文章 • 8 个评论 • 1848 次浏览 • 2017-06-21 09:35 • 来自相关话题

学习记录 https://github.com/t-hong/springboot-examples 

spring boot 学习系列 (使用版本 1.5.3) 
1.chapter1> springboot hello world. 2.chapter2> 配置文件详解:自定义属性、随机数、多环境配置等(目前只使用properties,后续添加yml的配置方式用法) 3.chapter3> spring boot 构建restful api以及单元测试,替换默认json 解析框架,使用fastjson 4.chapter3-jersey> springboot 整合jersey 实现restful. 5.chapter4-thymeleaf> spring boot 国际化相关处理和thymeleaf 模板语法的基本使用 6.chapter4-freemarker> 使用freemarker模板引擎 7.chapter4-jsp> spring boot 对jsp 的支持 8.chapter4-velocity> spring boot 使用velocity 版本情况说明 9.chapter5-swagger> spring boot 使用swagger构建restful api10.chapter6-handle-exception> spring boot 统一异常处理(返回异常对象json 或者modelandview) 11.chapter6-servlet> spring boot 下使用servlet、filter、listener以及springmvc interceptor 的相关处理. 12.chapter6-aop-log> spring boot log4j 使用aop方式添加日志. 13.chapter7-jdbctemplate> spring boot 整合jdbctemplate 操作数据库. 14.chapter7-spring-data-jpa> spring boot 整合spring data jpa ,简化数据库操作 15.chapter7-mybatis> spring boot 整合mybatis 使用,提供两种方式:xml 和annotation的方式。16.chapter7-mybatis-mapper-plugin> spring boot 整合mybatis 第三方mapper 插件、mybatis自动生成工具 generator 简化开发. 17.chapter8-jdbctemplate-mutil-datasource> spring boot 整合jdbctemplate 多数据源 18.chapter8-jpa-mutil-datasource> spring boot 整合spring data jpa 多数据源 19.chapter8-mybatis-mutil-datasource> spring boot 使用druid 数据源 整合mybatis 多数据源 20.chapter9-redis> spring boot 整合redis 21.chapter9-redis-cache> spring boot 使用redis 作为缓存实例 22.chapter9-redis-cluster> 添加了注释说明了JedisCluster 与RedisTemplate 操作RedisCluster 的一些使用原理 . 23.chapter9-redis-session> spring boot 整合Redis . 24.chapter10-shiro> spring boot 整合shiro 基本实例 25.chapter10-jwt> spring boot 整合jwt 实现token 认证. 26.chapter10-security> spring boot 整合security 实现简单权限控制. 27.chapter10-security-jwt> spring boot security token 整合实现认证登陆. 28.chapter11-caching-ehcache> spring boot框架缓存系列 ehcache . 29.chapter12-rocketmq> spring boot 整合rocketmq 测试. 查看全部
学习记录 https://github.com/t-hong/springboot-examples 

spring boot 学习系列 (使用版本 1.5.3) 
  • 1.chapter1> springboot hello world. 
  • 2.chapter2> 配置文件详解:自定义属性、随机数、多环境配置等(目前只使用properties,后续添加yml的配置方式用法) 
  • 3.chapter3> spring boot 构建restful api以及单元测试,替换默认json 解析框架,使用fastjson 
  • 4.chapter3-jersey> springboot 整合jersey 实现restful. 
  • 5.chapter4-thymeleaf> spring boot 国际化相关处理和thymeleaf 模板语法的基本使用 
  • 6.chapter4-freemarker> 使用freemarker模板引擎 
  • 7.chapter4-jsp> spring boot 对jsp 的支持 
  • 8.chapter4-velocity> spring boot 使用velocity 版本情况说明 
  • 9.chapter5-swagger> spring boot 使用swagger构建restful api
  • 10.chapter6-handle-exception> spring boot 统一异常处理(返回异常对象json 或者modelandview) 
  • 11.chapter6-servlet> spring boot 下使用servlet、filter、listener以及springmvc interceptor 的相关处理. 
  • 12.chapter6-aop-log> spring boot log4j 使用aop方式添加日志. 
  • 13.chapter7-jdbctemplate> spring boot 整合jdbctemplate 操作数据库. 
  • 14.chapter7-spring-data-jpa> spring boot 整合spring data jpa ,简化数据库操作 
  • 15.chapter7-mybatis> spring boot 整合mybatis 使用,提供两种方式:xml 和annotation的方式。
  • 16.chapter7-mybatis-mapper-plugin> spring boot 整合mybatis 第三方mapper 插件、mybatis自动生成工具 generator 简化开发. 
  • 17.chapter8-jdbctemplate-mutil-datasource> spring boot 整合jdbctemplate 多数据源 
  • 18.chapter8-jpa-mutil-datasource> spring boot 整合spring data jpa 多数据源 
  • 19.chapter8-mybatis-mutil-datasource> spring boot 使用druid 数据源 整合mybatis 多数据源 
  • 20.chapter9-redis> spring boot 整合redis 
  • 21.chapter9-redis-cache> spring boot 使用redis 作为缓存实例 
  • 22.chapter9-redis-cluster> 添加了注释说明了JedisCluster 与RedisTemplate 操作RedisCluster 的一些使用原理 . 
  • 23.chapter9-redis-session> spring boot 整合Redis . 
  • 24.chapter10-shiro> spring boot 整合shiro 基本实例 
  • 25.chapter10-jwt> spring boot 整合jwt 实现token 认证. 
  • 26.chapter10-security> spring boot 整合security 实现简单权限控制. 
  • 27.chapter10-security-jwt> spring boot security token 整合实现认证登陆. 
  • 28.chapter11-caching-ehcache> spring boot框架缓存系列 ehcache . 
  • 29.chapter12-rocketmq> spring boot 整合rocketmq 测试.

深入浅出 spring-data-elasticsearch - 基本案例详解(三)

Spring Data泥瓦匠BYSocket 发表了文章 • 30 个评论 • 2201 次浏览 • 2017-06-20 11:37 • 来自相关话题

『  风云说:能分享自己职位的知识的领导是个好领导。 』
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2
本文提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、运行 spring-data-elasticsearch-crud 工程
三、spring-data-elasticsearch-crud 工程代码详解

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix; 

Spring Boot Version (x)    Spring Data Elasticsearch Version (y)    Elasticsearch Version (z)
x <= 1.3.5    y <= 1.3.4    z <= 1.7.2*
x >= 1.4.x    2.0.0 <=y < 5.0.0**    2.0.0 <= z < 5.0.0**
*  - 只需要你修改下对应的 pom 文件版本号 
** - 下一个 ES 的版本会有重大的更新
 
1. 后台起守护线程启动 Elasticsearchcd elasticsearch-2.3.2/
./bin/elasticsearch -d
git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample。
下面开始运行工程步骤(Quick Start):
 
2. 项目结构介绍org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-crud,运行 maven 指令:mvn clean install
 4.运行工程
右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}
POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/:
(如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 http://www.bysocket.com/?p=1744 。)
在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下:{
"_index": "cityindex",
"_type": "city",
"_id": "1",
"_version": 1,
"_score": 1,
"_source": {
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
}
下面是基本查询语句的接口:
a. 普通查询,查询城市描述GET http://localhost:8080/api/city ... on%3D温岭
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]

b. AND 语句查询GET http://localhost:8080/api/city ... on%3D温岭&score=4
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]如果换成 score=5 ,就没有结果了。

c. OR 语句查询GET http://localhost:8080/api/city ... on%3D上海&score=4
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
d. NOT 语句查询GET http://localhost:8080/api/city ... on%3D温州
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
e. LIKE 语句查询GET http://localhost:8080/api/city ... on%3D城市
返回 JSON 如下:[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]
三、spring-data-elasticsearch-crud 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>
这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。
更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)

3. ES 数据操作层/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}
接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot

4. 实体类/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}
注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase

四、小结
预告下
下一篇《深入浅出 spring-data-elasticsearch - 实战案例详解》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 

摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢! 查看全部
『  风云说:能分享自己职位的知识的领导是个好领导。 』
运行环境:JDK 7 或 8,Maven 3.0+
技术栈:SpringBoot 1.5+, Spring Data Elasticsearch 1.5+ ,ElasticSearch 2.3.2
本文提纲
一、spring-data-elasticsearch-crud 的工程介绍
二、运行 spring-data-elasticsearch-crud 工程
三、spring-data-elasticsearch-crud 工程代码详解

一、spring-data-elasticsearch-crud 的工程介绍
spring-data-elasticsearch-crud 的工程,介绍 Spring Data Elasticsearch 简单的 ES 操作。Spring Data Elasticsearch 可以跟 JPA 进行类比。其使用方法也很简单。

二、运行 spring-data-elasticsearch-crud 工程
注意的是这里使用的是 ElasticSearch 2.3.2。是因为版本对应关系 https://github.com/spring-projects/spring-data-elasticsearch/wiki/Spring-Data-Elasticsearch---Spring-Boot---version-matrix; 

Spring Boot Version (x)    Spring Data Elasticsearch Version (y)    Elasticsearch Version (z)
x <= 1.3.5    y <= 1.3.4    z <= 1.7.2*
x >= 1.4.x    2.0.0 <=y < 5.0.0**    2.0.0 <= z < 5.0.0**
*  - 只需要你修改下对应的 pom 文件版本号 
** - 下一个 ES 的版本会有重大的更新
 
1. 后台起守护线程启动 Elasticsearch
cd elasticsearch-2.3.2/
./bin/elasticsearch -d

git clone 下载工程 springboot-elasticsearch ,项目地址见 GitHub - https://github.com/JeffLi1993/ ... ample
下面开始运行工程步骤(Quick Start):
 
2. 项目结构介绍
org.spring.springboot.controller - Controller 层
org.spring.springboot.repository - ES 数据操作层
org.spring.springboot.domain - 实体类
org.spring.springboot.service - ES 业务逻辑层
Application - 应用启动类
application.properties - 应用配置文件,应用启动会自动读取配置
本地启动的 ES ,就不需要改配置文件了。如果连测试 ES 服务地址,需要修改相应配置
 
3.编译工程
在项目根目录 spring-data-elasticsearch-crud,运行 maven 指令:
mvn clean install

 4.运行工程
右键运行 Application 应用启动类(位置:/springboot-learning-example/springboot-elasticsearch/src/main/java/org/spring/springboot/Application.java)的 main 函数,这样就成功启动了 springboot-elasticsearch 案例。
用 Postman 工具新增两个城市
 
a. 新增城市信息
POST http://127.0.0.1:8080/api/city
{
"id”:"1",
"score":"5",
"name":"上海",
"description":"上海是个热城市"
}

POST http://127.0.0.1:8080/api/city
{
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}

可以打开 ES 可视化工具 head 插件:http://localhost:9200/_plugin/head/
(如果不知道怎么安装,请查阅 《Elasticsearch 和插件 elasticsearch-head 安装详解》 http://www.bysocket.com/?p=1744 。)
在「数据浏览」tab,可以查阅到 ES 中数据是否被插入,插入后的数据格式如下:
{
"_index": "cityindex",
"_type": "city",
"_id": "1",
"_version": 1,
"_score": 1,
"_source": {
"id":"2",
"score”:"4",
"name”:”温岭",
"description":”温岭是个沿海城市"
}
}

下面是基本查询语句的接口:
a. 普通查询,查询城市描述
GET http://localhost:8080/api/city ... on%3D温岭
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]


b. AND 语句查询
GET http://localhost:8080/api/city ... on%3D温岭&score=4
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
}
]
如果换成 score=5 ,就没有结果了。

c. OR 语句查询
GET http://localhost:8080/api/city ... on%3D上海&score=4
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

d. NOT 语句查询
GET http://localhost:8080/api/city ... on%3D温州
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

e. LIKE 语句查询
GET http://localhost:8080/api/city ... on%3D城市
返回 JSON 如下:
[
{
"id": 2,
"name": "温岭",
"description": "温岭是个沿海城市",
"score": 4
},
{
"id": 1,
"name": "上海",
"description": "上海是个好城市",
"score": 3
}
]

三、spring-data-elasticsearch-crud 工程代码详解
具体代码见 GitHub - https://github.com/JeffLi1993/springboot-learning-example 

1.pom.xml 依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/ma ... gt%3B
<modelVersion>4.0.0</modelVersion>
<groupId>springboot</groupId>
<artifactId>spring-data-elasticsearch-crud</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-data-elasticsearch-crud :: spring-data-elasticsearch - 基本案例 </name>
<!-- Spring Boot 启动父依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.1.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Boot Elasticsearch 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- Spring Boot Web 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
</project>

这里依赖的 spring-boot-starter-data-elasticsearch 版本是 1.5.1.RELEASE,对应的 spring-data-elasticsearch 版本是 2.1.0.RELEASE。对应官方文档:http://docs.spring.io/spring-d ... html/。后面数据操作层都是通过该 spring-data-elasticsearch 提供的接口实现。

2. application.properties 配置 ES 地址
# ES
spring.data.elasticsearch.repositories.enabled = true
spring.data.elasticsearch.cluster-nodes = 127.0.0.1:9300
默认 9300 是 Java 客户端的端口。9200 是支持 Restful HTTP 的接口。

更多配置:
 spring.data.elasticsearch.cluster-name Elasticsearch    集群名。(默认值: elasticsearch)
 spring.data.elasticsearch.cluster-nodes    集群节点地址列表,用逗号分隔。如果没有指定,就启动一个客户端节点。
 spring.data.elasticsearch.propertie     用来配置客户端的额外属性。
 spring.data.elasticsearch.repositories.enabled     开启 Elasticsearch 仓库。(默认值:true。)

3. ES 数据操作层
/**
* ES 操作类
* <p>
* Created by bysocket on 17/05/2017.
*/
public interface CityRepository extends ElasticsearchRepository<City, Long> {
/**
* AND 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionAndScore(String description, Integer score);
/**
* OR 语句查询
*
* @param description
* @param score
* @return
*/
List<City> findByDescriptionOrScore(String description, Integer score);
/**
* 查询城市描述
*
* 等同于下面代码
* @Query("{\"bool\" : {\"must\" : {\"term\" : {\"description\" : \"?0\"}}}}")
* Page<City> findByDescription(String description, Pageable pageable);
*
* @param description
* @param page
* @return
*/
Page<City> findByDescription(String description, Pageable page);
/**
* NOT 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionNot(String description, Pageable page);
/**
* LIKE 语句查询
*
* @param description
* @param page
* @return
*/
Page<City> findByDescriptionLike(String description, Pageable page);
}

接口只要继承 ElasticsearchRepository 类即可。默认会提供很多实现,比如 CRUD 和搜索相关的实现。类似于 JPA 读取数据,是使用 CrudRepository 进行操作 ES 数据。支持的默认方法有: count(), findAll(), findOne(ID), delete(ID), deleteAll(), exists(ID), save(DomainObject), save(Iterable<DomainObject>)。

另外可以看出,接口的命名是遵循规范的。常用命名规则如下:
关键字     方法命名
And          findByNameAndPwd
Or             findByNameOrSex
Is              findById
Between   findByIdBetween
Like           findByNameLike
NotLike     findByNameNotLike
OrderBy    findByIdOrderByXDesc
Not           findByNameNot

4. 实体类
/**
* 城市实体类
* <p>
* Created by bysocket on 03/05/2017.
*/
@Document(indexName = "province", type = "city")
public class City implements Serializable {
private static final long serialVersionUID = -1L;
/**
* 城市编号
*/
private Long id;
/**
* 城市名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 城市评分
*/
private Integer score;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getScore() {
return score;
}
public void setScore(Integer score) {
this.score = score;
}
}

注意
a. City 属性名不支持驼峰式。
b. indexName 配置必须是全部小写,不然会出异常。
org.elasticsearch.indices.InvalidIndexNameException: Invalid index name [provinceIndex], must be lowercase

四、小结
预告下
下一篇《深入浅出 spring-data-elasticsearch - 实战案例详解》,会带来实战项目中涉及到的权重分 & 短语精准匹配的讲解。
 


摘要: 原创出处 www.bysocket.com 「泥瓦匠BYSocket 」欢迎转载,保留摘要,谢谢!


深入浅出 spring-data-elasticsearch 之 ElasticSearch 架构初探(一)

Spring Data泥瓦匠BYSocket 发表了文章 • 2 个评论 • 2532 次浏览 • 2017-06-04 17:20 • 来自相关话题

本文目录
一、Elasticsearch 基本术语
1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
二、Elasticsearch 工作原理
2.1 文档存储的路由
2.2 如何健康检查
2.3 如何水平扩容
三、小结

一、Elasticsearch 基本术语

1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
文档(Document)
文档,在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index、_type 和 _id 唯一标识。

索引(Index)
索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。

类型(Type)
类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

和关系型数据库 MySQL 做个类比:
Document 类似于 Record
Type 类似于 Table
Index 类似于 Database

1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
集群(Cluster)
服务器集群大家都知道,这里 ES 也是类似的。多个 ElasticSearch 运行实例(节点)组合的组合体是 ElasticSearch 集群。
ElasticSearch 是天然的分布式,通过水平扩容为集群添加更多节点。
集群是去中心化的,有一个主节点(Master)。主节点是动态选举,因此不会出现单点故障。

那分片和节点的配置呢?
节点(Node)
一个 ElasticSearch 运行实例就是节点。顺着集群来,任何节点都可以被选举成为主节点。主节点负责集群内所以变更,比如索引的增加、删除等。所以集群不会因为主节点流量的增大成为瓶颈。因为任何节点都会成为主节点。
下面有 3 个节点,第 1 个节点有:2 个主分片和 1 个副分片。如图:





那么,只有一个节点的 ElasticSearch 服务会存在瓶颈。如图:





分片(Shard)
分片,是 ES 节点中最小的工作单元。分片仅仅保存全部数据的一部分,分片的集合是 ES 的索引。分片包括主分片和副分片,主分片是副分片的拷贝。主分片和副分片地工作基本没有大的区别。
在索引中全文搜索,然后会查询到每个分片,将每个分配的结果进行全局地收集处理,并返回。

二、Elasticsearch 工作原理

2.1 文档存储的路由
当索引到一个文档(如:报价系统),具体的文档数据(如:报价数据)会存储到一个分片。具体文档数据会被切分,并分别存储在分片 1 或者 分片 2 … 
那么如何确定存在哪个分片呢?
存储路由过程由下面地公式决定:shard = hash(routing) % number_of_primary_shards
routing 是可变值,支持自定义,默认文档 _id。
hash 函数生成数字,经过取余算法得到余数,那么这个余数就是分片的位置。
这是不是有点负载均衡的类似。
 
2.2 如何健康检查
集群名,集群的健康状态GET http://127.0.0.1:9200/_cluster/stats
{
"cluster_name": "elasticsearch",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}
status 字段是需要我们关心的。状态可能是下列三个值之一:green
所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
yellow
所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。
red
至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。


active_primary_shards 集群中的主分片数量
active_shards 所有分片的汇总值
relocating_shards 显示当前正在从一个节点迁往其他节点的分片的数量。通常来说应该是 0,不过在 Elasticsearch 发现集群不太均衡时,该值会上涨。比如说:添加了一个新节点,或者下线了一个节点。
initializing_shards 刚刚创建的分片的个数。
unassigned_shards 已经在集群状态中存在的分片。
 
2.3 如何水平扩容
主分片在索引创建已经确定。读操作可以同时被主分片和副分片处理。因此,更多的分片,会拥有更高的吞吐量。自然,需要增加更多的硬件资源支持吞吐量。
说明,这里无法提高性能,因为每个分片获得的资源会变少。
动态调整副本分片数,按需伸缩集群,比如把副本数默认值为 1 增加到 2:PUT /blogs/_settings
{
"number_of_replicas" : 2
}

三、小结
简单初探了下 ElasticSearch 的相关内容。后面会主要落地到实战,关于  spring-data-elasticsearch 这块的实战。

最后,《 深入浅出 spring-data-elasticsearch 》小连载目录如下:
深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一)
深入浅出 spring-data-elasticsearch - 概述(二)
深入浅出 spring-data-elasticsearch - 基本案例详解(三)
深入浅出 spring-data-elasticsearch - 复杂案例详解(四)
深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五)
 

资料:
官方《Elasticsearch: 权威指南》
https://www.elastic.co/guide/c ... .html
 

本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处 查看全部
本文目录
一、Elasticsearch 基本术语
1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
二、Elasticsearch 工作原理
2.1 文档存储的路由
2.2 如何健康检查
2.3 如何水平扩容
三、小结

一、Elasticsearch 基本术语

1.1 文档(Document)、索引(Index)、类型(Type)文档三要素
文档(Document)
文档,在面向对象观念就是一个对象。在 ES 里面,是一个大 JSON 对象,是指定了唯一 ID 的最底层或者根对象。文档的位置由 _index、_type 和 _id 唯一标识。

索引(Index)
索引,用于区分文档成组,即分到一组的文档集合。索引,用于存储文档和使文档可被搜索。比如项目存索引 project 里面,交易存索引 sales 等。

类型(Type)
类型,用于区分索引中的文档,即在索引中对数据逻辑分区。比如索引 project 的项目数据,根据项目类型 ui 项目、插画项目等进行区分。

和关系型数据库 MySQL 做个类比
Document 类似于 Record
Type 类似于 Table
Index 类似于 Database

1.2 集群(Cluster)、节点(Node)、分片(Shard)分布式三要素
集群(Cluster)
服务器集群大家都知道,这里 ES 也是类似的。多个 ElasticSearch 运行实例(节点)组合的组合体是 ElasticSearch 集群。
ElasticSearch 是天然的分布式,通过水平扩容为集群添加更多节点。
集群是去中心化的,有一个主节点(Master)。主节点是动态选举,因此不会出现单点故障。

那分片和节点的配置呢?
节点(Node
一个 ElasticSearch 运行实例就是节点。顺着集群来,任何节点都可以被选举成为主节点。主节点负责集群内所以变更,比如索引的增加、删除等。所以集群不会因为主节点流量的增大成为瓶颈。因为任何节点都会成为主节点。
下面有 3 个节点,第 1 个节点有:2 个主分片和 1 个副分片。如图:
elas_node.png


那么,只有一个节点的 ElasticSearch 服务会存在瓶颈。如图:
elas_0201.png


分片(Shard)
分片,是 ES 节点中最小的工作单元。分片仅仅保存全部数据的一部分,分片的集合是 ES 的索引。分片包括主分片和副分片,主分片是副分片的拷贝。主分片和副分片地工作基本没有大的区别。
在索引中全文搜索,然后会查询到每个分片,将每个分配的结果进行全局地收集处理,并返回。

二、Elasticsearch 工作原理

2.1 文档存储的路由
当索引到一个文档(如:报价系统),具体的文档数据(如:报价数据)会存储到一个分片。具体文档数据会被切分,并分别存储在分片 1 或者 分片 2 … 
那么如何确定存在哪个分片呢?
存储路由过程由下面地公式决定:
shard = hash(routing) % number_of_primary_shards

routing 是可变值,支持自定义,默认文档 _id。
hash 函数生成数字,经过取余算法得到余数,那么这个余数就是分片的位置。
这是不是有点负载均衡的类似。
 
2.2 如何健康检查
集群名,集群的健康状态
GET http://127.0.0.1:9200/_cluster/stats 
{
"cluster_name": "elasticsearch",
"status": "green",
"timed_out": false,
"number_of_nodes": 1,
"number_of_data_nodes": 1,
"active_primary_shards": 0,
"active_shards": 0,
"relocating_shards": 0,
"initializing_shards": 0,
"unassigned_shards": 0
}

status 字段是需要我们关心的。状态可能是下列三个值之一:
green
所有的主分片和副本分片都已分配。你的集群是 100% 可用的。
yellow
所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。高可用会弱化把 yellow 想象成一个需要及时调查的警告。
red
至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。


active_primary_shards 集群中的主分片数量
active_shards 所有分片的汇总值
relocating_shards 显示当前正在从一个节点迁往其他节点的分片的数量。通常来说应该是 0,不过在 Elasticsearch 发现集群不太均衡时,该值会上涨。比如说:添加了一个新节点,或者下线了一个节点。
initializing_shards 刚刚创建的分片的个数。
unassigned_shards 已经在集群状态中存在的分片。
 
2.3 如何水平扩容
主分片在索引创建已经确定。读操作可以同时被主分片和副分片处理。因此,更多的分片,会拥有更高的吞吐量。自然,需要增加更多的硬件资源支持吞吐量。
说明,这里无法提高性能,因为每个分片获得的资源会变少。
动态调整副本分片数,按需伸缩集群,比如把副本数默认值为 1 增加到 2:
PUT /blogs/_settings
{
"number_of_replicas" : 2
}


三、小结
简单初探了下 ElasticSearch 的相关内容。后面会主要落地到实战,关于  spring-data-elasticsearch 这块的实战。

最后,《 深入浅出 spring-data-elasticsearch 》小连载目录如下:
深入浅出 spring-data-elasticsearch - ElasticSearch 架构初探(一)
深入浅出 spring-data-elasticsearch - 概述(二)
深入浅出 spring-data-elasticsearch - 基本案例详解(三)
深入浅出 spring-data-elasticsearch - 复杂案例详解(四)
深入浅出 spring-data-elasticsearch - 架构原理以及源码浅析(五)
 

资料:
官方《Elasticsearch: 权威指南》
https://www.elastic.co/guide/c ... .html
 


本文作者: 泥瓦匠
原文链接: http://www.bysocket.com
版权归作者所有,转载请注明出处


SpringData ES中一些底层原理的分析

Spring Dataformat_coder 发表了文章 • 7 个评论 • 958 次浏览 • 2017-06-02 18:31 • 来自相关话题

之前写过一篇SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题,顺便深入学习下Spring Data Elasticsearch。
 
Spring Data Elasticsearch是Spring Data针对Elasticsearch的实现。

它跟Spring Data一样,提供了Repository接口,我们只需要定义一个新的接口并继承这个Repository接口,然后就可以注入这个新的接口使用了。
 
定义接口:
 
@Repository
public interface TaskRepository extends ElasticsearchRepository<Task, String> { }
注入接口进行使用:
 
@Autowired
private TaskRepository taskRepository;

....
taskRepository.save(task);
Repository接口的代理生成
 
上面的例子中TaskRepository是个接口,而我们却直接注入了这个接口并调用方法;很明显,这是错误的。

其实SpringData ES内部基于这个TaskRepository接口构造一个SimpleElasticsearchRepository,真正被注入的是这个SimpleElasticsearchRepository。

这个过程是如何实现的呢?  来分析一下。

ElasticsearchRepositoriesAutoConfiguration自动化配置类会导入ElasticsearchRepositoriesRegistrar这个ImportBeanDefinitionRegistrar。

ElasticsearchRepositoriesRegistrar继承自AbstractRepositoryConfigurationSourceSupport,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器调用registerBeanDefinitions进行自定义bean的注册。

ElasticsearchRepositoriesRegistrar委托给RepositoryConfigurationDelegate完成bean的解析。

整个解析过程可以分3个步骤:
 
找出模块中的org.springframework.data.repository.Repository接口的实现类或者org.springframework.data.repository.RepositoryDefinition注解的修饰类,并会过滤掉org.springframework.data.repository.NoRepositoryBean注解的修饰类。找出后封装到RepositoryConfiguration中遍历这些RepositoryConfiguration,然后构造成BeanDefinition并注册到Spring容器中。需要注意的是这些RepositoryConfiguration会以beanClass为ElasticsearchRepositoryFactoryBean这个类的方式被注册,并把对应的Repository接口当做构造参数传递给ElasticsearchRepositoryFactoryBean,还会设置相应的属性比如elasticsearchOperations、evaluationContextProvider、namedQueries、repositoryBaseClass、lazyInitqueryLookupStrategyKeyElasticsearchRepositoryFactoryBean被实例化的时候设置对应的构造参数和属性。设置完毕以后调用afterPropertiesSet方法(实现了InitializingBean接口)。在afterPropertiesSet方法内部会去创建RepositoryFactorySupport类,并进行一些初始化,比如namedQueries、repositoryBaseClass等。然后通过这个RepositoryFactorySupport的getRepository方法基于Repository接口创建出代理类,并使用AOP添加了几个MethodInterceptor
 
// 遍历基于第1步条件得到的RepositoryConfiguration集合
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {
// 构造出BeanDefinitionBuilder
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);

extension.postProcess(definitionBuilder, configurationSource);

if (isXml) {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource);
} else {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
}

// 使用命名策略生成bean的名字
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName,
configuration.getRepositoryInterface(), extension.getRepositoryFactoryClassName());
}

beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
// 注册到Spring容器中
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}

// build方法
public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {

Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
// 得到factoryBeanName,这里会使用extension.getRepositoryFactoryClassName()去获得
// extension.getRepositoryFactoryClassName()返回的正是ElasticsearchRepositoryFactoryBean
String factoryBeanName = configuration.getRepositoryFactoryBeanName();
factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName
: extension.getRepositoryFactoryClassName();
// 基于factoryBeanName构造BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(factoryBeanName);

builder.getRawBeanDefinition().setSource(configuration.getSource());
// 设置ElasticsearchRepositoryFactoryBean的构造参数,这里是对应的Repository接口
// 设置一些的属性值
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.addPropertyValue("repositoryBaseClass", configuration.getRepositoryBaseClassName());

NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder(
extension.getDefaultNamedQueryLocation());

if (StringUtils.hasText(configuration.getNamedQueriesLocation())) {
definitionBuilder.setLocations(configuration.getNamedQueriesLocation());
}

builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource()));
// 查找是否有对应Repository接口的自定义实现类
String customImplementationBeanName = registerCustomImplementation(configuration);
// 存在自定义实现类的话,设置到属性中
if (customImplementationBeanName != null) {
builder.addPropertyReference("customImplementation", customImplementationBeanName);
builder.addDependsOn(customImplementationBeanName);
}

RootBeanDefinition evaluationContextProviderDefinition = new RootBeanDefinition(
ExtensionAwareEvaluationContextProvider.class);
evaluationContextProviderDefinition.setSource(configuration.getSource());
// 设置一些的属性值
builder.addPropertyValue("evaluationContextProvider", evaluationContextProviderDefinition);

return builder;
}

// RepositoryFactorySupport的getRepository方法,获得Repository接口的代理类
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {

// 获取Repository的元数据
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
// 获取Repository的自定义实现类
Class<?> customImplementationClass = null == customImplementation ? null : customImplementation.getClass();
// 根据元数据和自定义实现类得到Repository的RepositoryInformation信息类
// 获取信息类的时候如果发现repositoryBaseClass是空的话会根据meta中的信息去自动匹配
// 具体匹配过程在下面的getRepositoryBaseClass方法中说明
RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass);
// 验证
validate(information, customImplementation);
// 得到最终的目标类实例,会通过repositoryBaseClass去查找
Object target = getTargetRepository(information);

// 创建代理工厂
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(new Class[] { repositoryInterface, Repository.class });
// 进行aop相关的设置
result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);

if (TRANSACTION_PROXY_TYPE != null) {
result.addInterface(TRANSACTION_PROXY_TYPE);
}
// 使用RepositoryProxyPostProcessor处理
for (RepositoryProxyPostProcessor processor : postProcessors) {
processor.postProcess(result, information);
}

if (IS_JAVA_8) {
// 如果是JDK8的话,添加DefaultMethodInvokingMethodInterceptor
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}

// 添加QueryExecutorMethodInterceptor
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target));
// 使用代理工厂创建出代理类,这里是使用jdk内置的代理模式
return (T) result.getProxy(classLoader);
}

// 目标类的获取
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// 如果Repository接口属于QueryDsl,抛出异常。目前还不支持
if (isQueryDslRepository(metadata.getRepositoryInterface())) {
throw new IllegalArgumentException("QueryDsl Support has not been implemented yet.");
}
// 如果主键是数值类型的话,repositoryBaseClass为NumberKeyedRepository
if (Integer.class.isAssignableFrom(metadata.getIdType())
|| Long.class.isAssignableFrom(metadata.getIdType())
|| Double.class.isAssignableFrom(metadata.getIdType())) {
return NumberKeyedRepository.class;
} else if (metadata.getIdType() == String.class) {
// 如果主键是String类型的话,repositoryBaseClass为SimpleElasticsearchRepository
return SimpleElasticsearchRepository.class;
} else if (metadata.getIdType() == UUID.class) {
// 如果主键是UUID类型的话,repositoryBaseClass为UUIDElasticsearchRepository
return UUIDElasticsearchRepository.class;
} else {
// 否则报错
throw new IllegalArgumentException("Unsupported ID type " + metadata.getIdType());
}
}
ElasticsearchRepositoryFactoryBean是一个FactoryBean接口的实现类,getObject方法返回的上面提到的getRepository方法返回的代理对象;getObjectType方法返回的是对应Repository接口类型。

我们文章一开始提到的注入TaskRepository的时候,实际上这个对象是ElasticsearchRepositoryFactoryBean类型的实例,只不过ElasticsearchRepositoryFactoryBean实现了FactoryBean接口,所以注入的时候会得到一个代理对象,这个代理对象是由jdk内置的代理生成的,并且它的target对象是SimpleElasticsearchRepository(主键是String类型)。
 
 
SpringData ES中ElasticsearchOperations的介绍
 
ElasticsearchTemplate实现了ElasticsearchOperations接口。

ElasticsearchOperations接口是SpringData对Elasticsearch操作的一层封装,比如有创建索引createIndex方法、获取索引的设置信息getSetting方法、查询对象queryForObject方法、分页查询方法queryForPage、删除文档delete方法、更新文档update方法等等。

ElasticsearchTemplate是具体的实现类,它有这些属性:
 
// elasticsearch提供的基于java的客户端连接接口。java对es集群的操作使用这个接口完成
private Client client;
// 一个转换器接口,定义了2个方法,分别可以获得MappingContext和ConversionService
// MappingContext接口用于获取所有的持久化实体和这些实体的属性
// ConversionService目前在SpringData ES中没有被使用
private ElasticsearchConverter elasticsearchConverter;
// 内部使用EntityMapper完成对象到json字符串和json字符串到对象的映射。默认使用jackson完成映射,可自定义
private ResultsMapper resultsMapper;
// 查询超时时间
private String searchTimeout;
Client接口在ElasticsearchAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public Client elasticsearchClient() {
try {
return createClient();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
ElasticsearchTemplate、ElasticsearchConverter以及SimpleElasticsearchMappingContext在ElasticsearchDataAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public ElasticsearchTemplate elasticsearchTemplate(Client client,
ElasticsearchConverter converter) {
try {
return new ElasticsearchTemplate(client, converter);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

@Bean
@ConditionalOnMissingBean
public ElasticsearchConverter elasticsearchConverter(
SimpleElasticsearchMappingContext mappingContext) {
return new MappingElasticsearchConverter(mappingContext);
}

@Bean
@ConditionalOnMissingBean
public SimpleElasticsearchMappingContext mappingContext() {
return new SimpleElasticsearchMappingContext();
}
 需要注意的是这个bean被自动化配置类构造的前提是它们在Spring容器中并不存在。
 
Repository的调用过程
 
以自定义的TaskRepository的save方法为例,大致的执行流程如下所示:



SimpleElasticsearchRepository的save方法具体的分析在SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题中分析过。

像自定义的Repository查询方法,或者Repository接口的自定义实现类的操作这些底层,可以去QueryExecutorMethodInterceptor中查看,大家有兴趣的可以自行查看源码。
  查看全部
之前写过一篇SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题,顺便深入学习下Spring Data Elasticsearch。
 
Spring Data ElasticsearchSpring Data针对Elasticsearch的实现。

它跟Spring Data一样,提供了Repository接口,我们只需要定义一个新的接口并继承这个Repository接口,然后就可以注入这个新的接口使用了。
 
定义接口:
 
@Repository
public interface TaskRepository extends ElasticsearchRepository<Task, String> { }

注入接口进行使用:
 
@Autowired
private TaskRepository taskRepository;

....
taskRepository.save(task);

Repository接口的代理生成
 
上面的例子中TaskRepository是个接口,而我们却直接注入了这个接口并调用方法;很明显,这是错误的。

其实SpringData ES内部基于这个TaskRepository接口构造一个SimpleElasticsearchRepository,真正被注入的是这个SimpleElasticsearchRepository。

这个过程是如何实现的呢?  来分析一下。

ElasticsearchRepositoriesAutoConfiguration自动化配置类会导入ElasticsearchRepositoriesRegistrar这个ImportBeanDefinitionRegistrar。

ElasticsearchRepositoriesRegistrar继承自AbstractRepositoryConfigurationSourceSupport,是个ImportBeanDefinitionRegistrar接口的实现类,会被Spring容器调用registerBeanDefinitions进行自定义bean的注册。

ElasticsearchRepositoriesRegistrar委托给RepositoryConfigurationDelegate完成bean的解析。

整个解析过程可以分3个步骤:
 
  1. 找出模块中的org.springframework.data.repository.Repository接口的实现类或者org.springframework.data.repository.RepositoryDefinition注解的修饰类,并会过滤掉org.springframework.data.repository.NoRepositoryBean注解的修饰类。找出后封装到RepositoryConfiguration中
  2. 遍历这些RepositoryConfiguration,然后构造成BeanDefinition并注册到Spring容器中。需要注意的是这些RepositoryConfiguration会以beanClass为ElasticsearchRepositoryFactoryBean这个类的方式被注册,并把对应的Repository接口当做构造参数传递给ElasticsearchRepositoryFactoryBean,还会设置相应的属性比如elasticsearchOperations、evaluationContextProvider、namedQueries、repositoryBaseClass、lazyInitqueryLookupStrategyKey
  3. ElasticsearchRepositoryFactoryBean被实例化的时候设置对应的构造参数和属性。设置完毕以后调用afterPropertiesSet方法(实现了InitializingBean接口)。在afterPropertiesSet方法内部会去创建RepositoryFactorySupport类,并进行一些初始化,比如namedQueries、repositoryBaseClass等。然后通过这个RepositoryFactorySupport的getRepository方法基于Repository接口创建出代理类,并使用AOP添加了几个MethodInterceptor

 
// 遍历基于第1步条件得到的RepositoryConfiguration集合
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {
// 构造出BeanDefinitionBuilder
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);

extension.postProcess(definitionBuilder, configurationSource);

if (isXml) {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource) configurationSource);
} else {
// 设置elasticsearchOperations属性
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource) configurationSource);
}

// 使用命名策略生成bean的名字
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
String beanName = beanNameGenerator.generateBeanName(beanDefinition, registry);

if (LOGGER.isDebugEnabled()) {
LOGGER.debug(REPOSITORY_REGISTRATION, extension.getModuleName(), beanName,
configuration.getRepositoryInterface(), extension.getRepositoryFactoryClassName());
}

beanDefinition.setAttribute(FACTORY_BEAN_OBJECT_TYPE, configuration.getRepositoryInterface());
// 注册到Spring容器中
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}

// build方法
public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {

Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
// 得到factoryBeanName,这里会使用extension.getRepositoryFactoryClassName()去获得
// extension.getRepositoryFactoryClassName()返回的正是ElasticsearchRepositoryFactoryBean
String factoryBeanName = configuration.getRepositoryFactoryBeanName();
factoryBeanName = StringUtils.hasText(factoryBeanName) ? factoryBeanName
: extension.getRepositoryFactoryClassName();
// 基于factoryBeanName构造BeanDefinitionBuilder
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(factoryBeanName);

builder.getRawBeanDefinition().setSource(configuration.getSource());
// 设置ElasticsearchRepositoryFactoryBean的构造参数,这里是对应的Repository接口
// 设置一些的属性值
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.addPropertyValue("repositoryBaseClass", configuration.getRepositoryBaseClassName());

NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder(
extension.getDefaultNamedQueryLocation());

if (StringUtils.hasText(configuration.getNamedQueriesLocation())) {
definitionBuilder.setLocations(configuration.getNamedQueriesLocation());
}

builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource()));
// 查找是否有对应Repository接口的自定义实现类
String customImplementationBeanName = registerCustomImplementation(configuration);
// 存在自定义实现类的话,设置到属性中
if (customImplementationBeanName != null) {
builder.addPropertyReference("customImplementation", customImplementationBeanName);
builder.addDependsOn(customImplementationBeanName);
}

RootBeanDefinition evaluationContextProviderDefinition = new RootBeanDefinition(
ExtensionAwareEvaluationContextProvider.class);
evaluationContextProviderDefinition.setSource(configuration.getSource());
// 设置一些的属性值
builder.addPropertyValue("evaluationContextProvider", evaluationContextProviderDefinition);

return builder;
}

// RepositoryFactorySupport的getRepository方法,获得Repository接口的代理类
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {

// 获取Repository的元数据
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
// 获取Repository的自定义实现类
Class<?> customImplementationClass = null == customImplementation ? null : customImplementation.getClass();
// 根据元数据和自定义实现类得到Repository的RepositoryInformation信息类
// 获取信息类的时候如果发现repositoryBaseClass是空的话会根据meta中的信息去自动匹配
// 具体匹配过程在下面的getRepositoryBaseClass方法中说明
RepositoryInformation information = getRepositoryInformation(metadata, customImplementationClass);
// 验证
validate(information, customImplementation);
// 得到最终的目标类实例,会通过repositoryBaseClass去查找
Object target = getTargetRepository(information);

// 创建代理工厂
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(new Class[] { repositoryInterface, Repository.class });
// 进行aop相关的设置
result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);

if (TRANSACTION_PROXY_TYPE != null) {
result.addInterface(TRANSACTION_PROXY_TYPE);
}
// 使用RepositoryProxyPostProcessor处理
for (RepositoryProxyPostProcessor processor : postProcessors) {
processor.postProcess(result, information);
}

if (IS_JAVA_8) {
// 如果是JDK8的话,添加DefaultMethodInvokingMethodInterceptor
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}

// 添加QueryExecutorMethodInterceptor
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target));
// 使用代理工厂创建出代理类,这里是使用jdk内置的代理模式
return (T) result.getProxy(classLoader);
}

// 目标类的获取
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
// 如果Repository接口属于QueryDsl,抛出异常。目前还不支持
if (isQueryDslRepository(metadata.getRepositoryInterface())) {
throw new IllegalArgumentException("QueryDsl Support has not been implemented yet.");
}
// 如果主键是数值类型的话,repositoryBaseClass为NumberKeyedRepository
if (Integer.class.isAssignableFrom(metadata.getIdType())
|| Long.class.isAssignableFrom(metadata.getIdType())
|| Double.class.isAssignableFrom(metadata.getIdType())) {
return NumberKeyedRepository.class;
} else if (metadata.getIdType() == String.class) {
// 如果主键是String类型的话,repositoryBaseClass为SimpleElasticsearchRepository
return SimpleElasticsearchRepository.class;
} else if (metadata.getIdType() == UUID.class) {
// 如果主键是UUID类型的话,repositoryBaseClass为UUIDElasticsearchRepository
return UUIDElasticsearchRepository.class;
} else {
// 否则报错
throw new IllegalArgumentException("Unsupported ID type " + metadata.getIdType());
}
}

ElasticsearchRepositoryFactoryBean是一个FactoryBean接口的实现类,getObject方法返回的上面提到的getRepository方法返回的代理对象;getObjectType方法返回的是对应Repository接口类型。

我们文章一开始提到的注入TaskRepository的时候,实际上这个对象是ElasticsearchRepositoryFactoryBean类型的实例,只不过ElasticsearchRepositoryFactoryBean实现了FactoryBean接口,所以注入的时候会得到一个代理对象,这个代理对象是由jdk内置的代理生成的,并且它的target对象是SimpleElasticsearchRepository(主键是String类型)。
 
 
SpringData ES中ElasticsearchOperations的介绍
 
ElasticsearchTemplate实现了ElasticsearchOperations接口。

ElasticsearchOperations接口是SpringData对Elasticsearch操作的一层封装,比如有创建索引createIndex方法、获取索引的设置信息getSetting方法、查询对象queryForObject方法、分页查询方法queryForPage、删除文档delete方法、更新文档update方法等等。

ElasticsearchTemplate是具体的实现类,它有这些属性:
 
// elasticsearch提供的基于java的客户端连接接口。java对es集群的操作使用这个接口完成
private Client client;
// 一个转换器接口,定义了2个方法,分别可以获得MappingContext和ConversionService
// MappingContext接口用于获取所有的持久化实体和这些实体的属性
// ConversionService目前在SpringData ES中没有被使用
private ElasticsearchConverter elasticsearchConverter;
// 内部使用EntityMapper完成对象到json字符串和json字符串到对象的映射。默认使用jackson完成映射,可自定义
private ResultsMapper resultsMapper;
// 查询超时时间
private String searchTimeout;

Client接口在ElasticsearchAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public Client elasticsearchClient() {
try {
return createClient();
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

ElasticsearchTemplate、ElasticsearchConverter以及SimpleElasticsearchMappingContext在ElasticsearchDataAutoConfiguration自动化配置类里被构造:
 
@Bean
@ConditionalOnMissingBean
public ElasticsearchTemplate elasticsearchTemplate(Client client,
ElasticsearchConverter converter) {
try {
return new ElasticsearchTemplate(client, converter);
}
catch (Exception ex) {
throw new IllegalStateException(ex);
}
}

@Bean
@ConditionalOnMissingBean
public ElasticsearchConverter elasticsearchConverter(
SimpleElasticsearchMappingContext mappingContext) {
return new MappingElasticsearchConverter(mappingContext);
}

@Bean
@ConditionalOnMissingBean
public SimpleElasticsearchMappingContext mappingContext() {
return new SimpleElasticsearchMappingContext();
}

 需要注意的是这个bean被自动化配置类构造的前提是它们在Spring容器中并不存在。
 
Repository的调用过程
 
以自定义的TaskRepository的save方法为例,大致的执行流程如下所示:



SimpleElasticsearchRepository的save方法具体的分析在SpringData ES 关于字段名和索引中的列名字不一致导致的查询问题中分析过。

像自定义的Repository查询方法,或者Repository接口的自定义实现类的操作这些底层,可以去QueryExecutorMethodInterceptor中查看,大家有兴趣的可以自行查看源码。