- 从一个案例开始分析
- pom
- applicantion.yml
- pojo
- document 注解
- 映射注释概述(摘自官网)
- 索引设置(摘自官网)
- 实体类 - 官方案例
- Repository
- Query 注解(摘自官网)
- 继承 ElasticsearchRepository 得到的方法
- 测试 ProductDao ElasticsearchRestTemplate
- 核心类
- ElasticsearchRestTemplate
- ElasticsearchOperations
- ElasticsearchRepository
- 官网解释 CrudRepository
- ElasticsearchRestTemplate 的关系
- 文章说明
applicantion.yml4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.4 com.dongua es-springboot-demo 0.0.1-SNAPSHOT es-springboot-demo 11 7.14.1 org.springframework.boot spring-boot-starter-data-elasticsearch org.springframework.boot spring-boot-starter-web org.projectlombok lombok org.springframework.boot spring-boot-configuration-processor com.alibaba fastjson 1.2.75 org.springframework.boot spring-boot-starter-test test org.springframework.boot spring-boot-maven-plugin
spring:
elasticsearch:
rest:
# es 的 uri
uris: http://localhost:9200
# 连接超时时间
connection-timeout: 1
# 读取超时时间
read-timeout: 30
当然也可以根据自己的配置类定义
@EqualsAndHashCode(callSuper = true)
@ConfigurationProperties(prefix = "elasticsearch")
@Data
@Configuration
public class RestHighClientConfig extends AbstractElasticsearchConfiguration {
private String host;
private int port;
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
return new RestHighLevelClient
(RestClient.builder(new HttpHost(host, port, "http")));
}
// 这个也行
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
elasticsearch: host: 127.0.0.1 port: 9200pojo
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
// 别名,不设置的话,_class 的值就是 其全路径,例如 com.xxx.pojo.Product
@TypeAlias("product")
// index 的名字
@document(indexName = "product")
// 设置分片数量为 3个
@Setting(shards = 3)
public class Product {
// 唯一标识,重复会被覆盖
@Id
private Long id;
// text 类型,可被分词查询
@Field(type = FieldType.Text)
private String title;
// 关键字 类型,根据整个词进行查询
@Field(type = FieldType.Keyword)
private String category;
// Double 类型
@Field(type = FieldType.Double)
private Double price;
// 该字段不作为索引,默认为索引,此处设为 false
@Field(type = FieldType.Keyword, index = false)
private String images;
}
document 注解
映射注释概述(摘自官网)
在MappingElasticsearchConverter使用元数据驱动的对象的映射文件。元数据取自可以注释的实体属性。
以下注释可用:
@document: 应用于类级别,表示该类是映射到数据库的候选。最重要的属性是:
indexName:存储该实体的索引名称。这可以包含一个 SpEL 模板表达式,如 “log-#{T(java.time.LocalDate).now().toString()}”
type:映射类型。如果未设置,则使用类的小写简单名称。(自 4.0 版起已弃用)
createIndex: 标记是否在存储库引导时创建索引。默认值为true。请参阅使用相应映射自动创建索引
versionType: 版本管理的配置。默认值为EXTERNAL。
@Id:应用于字段级别以标记用于标识目的的字段。
@Transient: 默认情况下,所有字段在存储或检索时都映射到文档,此注释不包括该字段。
@PersistenceConstructor: 标记给定的构造函数 - 甚至是受包保护的构造函数 - 在从数据库实例化对象时使用。构造函数参数按名称映射到检索到的文档中的键值。
@Field:应用于字段级别,定义字段的属性,大部分属性映射到各自的Elasticsearch Mapping定义
name: 字段名称,因为它将在 Elasticsearch 文档中表示,如果未设置,则使用 Java 字段名称。
type:字段类型,可以是Text、Keyword、Long、Integer、Short、Byte、Double、Float、Half_Float、Scaled_Float、Date、Date_Nanos、Boolean、Binary、Integer_Range、Float_Range、Long_Range、Double_Range、Date_Range、Ip_Range、Object之一, 嵌套, Ip, TokenCount, Percolator, Flattened, Search_As_You_Type。查看Elasticsearch 映射类型
format:一种或多种内置日期格式
pattern:一种或多种自定义日期格式
store: 标志是否应将原始字段值存储在 Elasticsearch 中,默认值为false。
索引设置(摘自官网)使用 Spring Data Elasticsearch 创建 Elasticsearch 索引时,可以使用@Setting注释定义不同的索引设置。以下参数可用:
useServerConfiguration 不发送任何设置参数,因此 Elasticsearch 服务器配置确定它们。
settingPath 是指定义必须在类路径中解析的设置的 JSON 文件
shards要使用的分片数,默认为1
replicas副本数,默认为1
refreshIntervall, 默认为“1s”
indexStoreType, 默认为"fs"
也可以定义索引排序
@Setting 源码和解释
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE })
public @interface Setting {
String settingPath() default "";
boolean useServerConfiguration() default false;
short shards() default 1;
short replicas() default 1;
String refreshInterval() default "1s";
String indexStoreType() default "fs";
String[] sortFields() default {};
SortOrder[] sortOrders() default {};
SortMode[] sortModes() default {};
SortMissing[] sortMissingValues() default {};
// 提供内部枚举供使用
enum SortOrder {
asc, desc
}
enum SortMode {
min, max
}
enum SortMissing {
_last, _first
}
}
实体类 - 官方案例
@document(indexName = "entities")
@Setting(
sortFields = { "secondField", "firstField" }, // 1处
sortModes = { Setting.SortMode.max, Setting.SortMode.min }, // 2处
sortOrders = { Setting.SortOrder.desc, Setting.SortOrder.asc },
sortMissingValues = { Setting.SortMissing._last, Setting.SortMissing._first })
class Entity {
@Nullable
@Id private String id;
@Nullable
@Field(name = "first_field", type = FieldType.Keyword)
private String firstField;
@Nullable @Field(name = "second_field", type = FieldType.Keyword)
private String secondField;
// getter and setter...
}
1.处:定义排序字段时,使用 Java 属性的名称 ( firstField ),而不是可能为 Elasticsearch 定义的名称 ( first_field )
2.处 sortModes,sortOrders和sortMissingValues是可选的,但如果设置了它们,则条目数必须与sortFields元素数匹配
就是命名,驼峰和下划线的区分,java的属性用驼峰,数据库用下划线的意思
@Filed 注解源码
Repositoryimport com.dongua.pojo.Product; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.stereotype.Repository; // 这里面就是咱 CRUD 一些封装的核心,一会再探究一番 @Repository public interface ProductDao extends ElasticsearchRepositoryQuery 注解(摘自官网) 继承 ElasticsearchRepository 得到的方法 测试 ProductDao ElasticsearchRestTemplate{ }
按照官网以及代码的提示,先测几个 api
@SpringBootTest
public class SpringDataESProductDaoTest {
// ElasticsearchRestTemplate 实际上 实现的是 ElasticsearchOperations接口
@Resource
ElasticsearchRestTemplate restTemplate;
@Resource
private ProductDao productDao;
// 在初始已经创建了该索引,再用此方法不能再创建一次,这会报错
@Test
public void createIndex() {
boolean b = restTemplate.indexOps(Product.class).create();
System.out.println("b = " + b);
}
@Test
public void exists() {
// 判断 id 为 5 的文档数据有没有
boolean product = restTemplate.exists("5", Product.class);
System.out.println("判断 id 为 5 的文档数据有没有 " + product);
// 判断 product 索引存不存在 (我们已经初始化是定义了 Product 的索引名字)
boolean exists = restTemplate.indexOps(Product.class).exists();
System.out.println("判断 product 索引存不存在 " + exists);
boolean user = restTemplate.indexOps(IndexCoordinates.of("user")).exists();
// 判断名字叫 user 索引存不存在
System.out.println("判断 user 索引存不存在 " + user);
}
@Test
public void save() {
Product product = new Product();
// 全局唯一,重复会覆盖
product.setId(1L);
product.setTitle("华为手机4");
product.setCategory("手机");
product.setPrice(2999.0);
product.setImages("http://www.xxx/hw.jpg");
product.setScore(1);
productDao.save(product);
}
//POSTMAN, GET http://localhost:9200/product/_doc/1
//修改一条文档
@Test
public void update() {
Product product = new Product();
product.setId(1L);
product.setTitle("小米 2 手机");
product.setCategory("手机");
product.setPrice(9999.0);
product.setImages("http://www.xxx/xm.jpg");
product.setScore(8);
productDao.save(product);
}
//POSTMAN, GET http://localhost:9200/product/_doc/1
//根据 id 查询文档
@Test
public void findById() {
Product product = productDao.findById(2L).get();
System.out.println(product);
}
// 查询所有 文档
@Test
public void findAll() {
Iterable products = productDao.findAll();
for (Product product : products) {
System.out.println(product);
}
}
// 删除索引
@Test
public void deleteIndex() {
// 这里使用了 restTemplate 的 indexOps() 获取 IndexOperations 对象操作索引
// dao 所提供的方法不支持操作索引
boolean delete = restTemplate.indexOps(Product.class).delete();
System.out.println("delete = " + delete);
}
//删除一条文档
@Test
public void delete() {
Product product = new Product();
product.setId(2L);
productDao.delete(product);
}
//POSTMAN, GET http://localhost:9200/product/_doc/2
//批量新增文档
@Test
public void saveAll() {
List productList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
Product product = new Product();
product.setId(Long.valueOf(i));
product.setTitle("[" + i + "]小米手机");
product.setCategory("手机");
product.setPrice(1999.0 + i);
product.setImages("http://www.xxx/xm.jpg");
productList.add(product);
}
productDao.saveAll(productList);
}
//分页查询
@Test
public void findByPageable() {
// 设置排序(排序方式,正序还是倒序,排序的 id)
Sort sort = Sort.by(Sort.Direction.DESC, "id");
int currentPage = 0;//当前页,第一页从 0 开始, 1 表示第二页
int pageSize = 5;//每页显示多少条
// 设置查询分页
// 即使不提供 sort 参数,他也会自己做某种排序规则
PageRequest pageRequest = PageRequest.of(currentPage, pageSize, sort);
//分页查询
Page productPage = productDao.findAll(pageRequest);
System.out.println("总共的元素数目 = " + productPage.getTotalElements());
for (Product Product : productPage.getContent()) {
System.out.println(Product);
}
// 这个也行
Iterable products = productDao.findAll(Pageable.ofSize(5));
for (Product product : products) {
System.out.println(product);
}
}
}
不去全部测试了
核心类 ElasticsearchRestTemplate
- 在执行除查询方法时,索引就已经被初始化了
- 这里只列举了部分的 api 测试,其他的大家可以自己慢慢试
- restTemplate.indexOps(Product.class).方法 操作索引,本质是使用了 IndexOperations 对象
ElasticsearchRestTemplate 继承 AbstractElasticsearchTemplate
AbstractElasticsearchTemplate 实现 ElasticsearchOperations 接口
ElasticsearchOperations 继承 documentOperations, SearchOperations
摘自官网的对于 Elasticsearch 操作 的解释:
- IndexOperations 定义索引级别的操作,例如创建或删除索引。
- documentOperations 定义基于 id 存储、更新和检索实体的操作。
- SearchOperations 定义使用查询搜索多个实体的操作
- ElasticsearchOperations结合了documentOperations和SearchOperations接口。
写一个 TestController 测试一波 ElasticsearchOperations
// 测试 ElasticsearchOperations
@RestController
@RequestMapping("/")
public class TestController {
private final ElasticsearchOperations elasticsearchOperations;
public TestController(ElasticsearchOperations elasticsearchOperations) {
this.elasticsearchOperations = elasticsearchOperations;
}
@PostMapping("/user")
public String save(@RequestBody User user) {
// 索引查询对象:设置查询条件
IndexQuery indexQuery = new IndexQueryBuilder()
// .withVersion(2L) 设置版本号为 2
.withId(String.valueOf(user.getId()))// 设置索引 id 为 user 的id
.withObject(user) // 告诉他是哪个实体对象
.build(); // 返回
// IndexCoordinates.of("user") 索引 名字叫 user ,自定义
// 若没有这个索引他会自己创建索引,并增加/更新一条 User 文档数据
return elasticsearchOperations.index(indexQuery, IndexCoordinates.of("user"));
}
@PostMapping("/product")
public String save2(@RequestBody Product product) {
IndexQuery indexQuery = new IndexQueryBuilder()
.withId(String.valueOf(product.getId()))
.withObject(product)
.build();
return elasticsearchOperations.index(indexQuery, IndexCoordinates.of("product"));
}
// 根据 id 查找
@GetMapping("/user/{id}")
public User findById(@PathVariable("id") Long id) {
return elasticsearchOperations.get(String.valueOf(id), User.class, IndexCoordinates.of("user"));
}
// 查询所有
@GetMapping("/user/")
public List> search() {
SearchHits hits = elasticsearchOperations.search(Query.findAll(), User.class, IndexCoordinates.of("user"));
hits.forEach(System.out::println);
return hits.getSearchHits();
}
@GetMapping("/product/")
public List> search2() {
SearchHits hits = elasticsearchOperations.search(Query.findAll(), Product.class, IndexCoordinates.of("product"));
hits.forEach(System.out::println);
return hits.getSearchHits();
}
}
indexQuery 的一些查询条件
- 如果找到了对应的查询条件的文档数据,就更新这个文档(注意版本约束)
- 如果没找到就新增
IndexOperations 部分源码
package org.springframework.data.elasticsearch.core;
·······
public interface IndexOperations {
boolean create();
boolean create(Map settings);
boolean create(Map settings, document mapping);
boolean createWithMapping();
boolean delete();
boolean exists();
void refresh();
document createMapping();
document createMapping(Class> clazz);
default boolean putMapping() {
return putMapping(createMapping());
}
boolean putMapping(document mapping);
default boolean putMapping(Class> clazz) {
return putMapping(createMapping(clazz));
}
Map getMapping();
Settings createSettings();
Settings createSettings(Class> clazz);
Settings getSettings();
Settings getSettings(boolean includeDefaults);
}
ElasticsearchRepository
ElasticsearchRepository 继承 PagingAndSortingRepository
PagingAndSortingRepository 继承 CrudRepository
CrudRepository
- ElasticsearchRestTemplate是ElasticsearchOperations使用高级 REST 客户端的接口的实现。
- ElasticsearchRestTemplate使用的还是RestHighLevelClient
我们自定义配置类继承的 AbstractElasticsearchConfiguration 抽象类源码
public abstract class AbstractElasticsearchConfiguration extends ElasticsearchConfigurationSupport {
@Bean
public abstract RestHighLevelClient elasticsearchClient();
@Bean(name = { "elasticsearchOperations", "elasticsearchTemplate" })
public ElasticsearchOperations elasticsearchOperations(ElasticsearchConverter elasticsearchConverter,
RestHighLevelClient elasticsearchClient) {
ElasticsearchRestTemplate template = new ElasticsearchRestTemplate(elasticsearchClient, elasticsearchConverter);
template.setRefreshPolicy(refreshPolicy());
return template;
}
}
本质上只是为 RestHighLevelClient 做了一层封装
所以我们用这个ElasticsearchRestTemplate更舒服,使用 RestHighLevelClient 就显得很多冗余了。
第一次在官网上自学一个技术栈,希望记录所学。
有所纰漏,敬请谅解



