戳我查看源码 springboot2.3.3中操作elasticsearch非常简单,一般的查询只需定义一个接口即可。 注意:我是用的es版本是7.6.2,是springboot2.3.3中默认的es版本,如果您的版本不一致,更换elasticsearch的依赖版本即可。 如果您还没有安装elasticsearch,可参见我的博客: docker-compose + elasticsearch7.6(配置密码及证书) + kibana7.6 + elasticsearch-head快速搭建集群
首先要清楚注解的使用。
@Document:类注解,以指示该类是映射到数据库的候选对象。最重要的属性是:
indexName:用于存储此实体的索引的名称
type:映射类型。如果未设置,则使用小写的类的简单名称。(当前版本开始不推荐使用)
shards:索引的分片数。
replicas:索引的副本数。
refreshIntervall:索引的刷新间隔。用于索引创建。默认值为“ 1s”。
indexStoreType:索引的索引存储类型。用于索引创建。默认值为“ fs”。
createIndex:配置是否在存储库引导中创建索引。默认值为true。
versionType:版本管理的配置。默认值为EXTERNAL。
@Id:字段注解,以标记用于标识目的的字段。
@Field:在字段级别应用并定义字段的属性,大多数属性映射到各自的Elasticsearch映射定义(以下列表不完整,请查看注释Javadoc以获取完整的参考):
name:字段名称,它将在Elasticsearch文档中表示,如果未设置,则使用Java字段名称。
type:字段类型,可以是文本,关键字,长整数,短整数,字节,双精度,浮点型,半浮点数,标度浮点数,日期,布尔值,二进制,整数等。
format和日期类型的pattern定义。必须为日期类型定义。
store:标记是否将原始字段值存储在Elasticsearch中,默认值为false。
analyzer,searchAnalyzer,normalizer用于指定自定义分析和正规化。
@GeoPoint:将字段标记为geo_point数据类型。如果字段是GeoPoint类的实例,则可以省略。
这里我定义了两个对象的demo,Person和Company。对象Person中有一个属性company,用来表示该人员属于哪个公司。
package com.framework.example.entity; import java.time.LocalDate; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.TypeAlias; import org.springframework.data.elasticsearch.annotations.DateFormat; import org.springframework.data.elasticsearch.annotations.Document; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** * <p> * 描述:人员实体 * </p> * * @author aLiang * @date 2020年9月2日 下午3:34:51 */ @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) @Document(indexName = "person", shards = 1, replicas = 1) @TypeAlias("person") public class Person { @Id private Long id; @Field(type = FieldType.Keyword, analyzer = "ik_max_word") private String name; @Field(type = FieldType.Text, analyzer = "ik_max_word") private String address; @Field(type = FieldType.Keyword, analyzer = "ik_max_word") private String mobile; @Field(type = FieldType.Keyword, analyzer = "ik_max_word") private String email; @Field(type = FieldType.Date, format = DateFormat.date) private LocalDate birthday; @Field(type = FieldType.Keyword) private String idCard; @Field private Company company; } package com.framework.example.entity; import org.springframework.data.elasticsearch.annotations.Field; import org.springframework.data.elasticsearch.annotations.FieldType; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; /** * <p> * 描述: * </p> * * @author aLiang * @date 2020年9月2日 下午3:34:51 */ @Data @NoArgsConstructor @AllArgsConstructor @Accessors(chain = true) public class Company { @Field(type = FieldType.Keyword, analyzer = "ik_max_word") private String name; @Field(type = FieldType.Text, analyzer = "ik_max_word") private String address; @Field(type = FieldType.Keyword, analyzer = "ik_max_word") private String mobile; @Field(type = FieldType.Keyword, analyzer = "ik_max_word") private String email; @Field(type = FieldType.Text, analyzer = "ik_max_word") private String nature; @Field(type = FieldType.Text) private String website; }CrudRepository接口是官方提供的接口,继承接口Repository<T, ID>,里面包含了很多常用的查询接口
package org.springframework.data.repository; @NoRepositoryBean public interface CrudRepository<T, ID> extends Repository<T, ID> { <S extends T> S save(S entity); <S extends T> Iterable<S> saveAll(Iterable<S> entities); Optional<T> findById(ID id); Iterable<T> findAll(); // 省略…… }PagingAndSortingRepository接口继承CrudRepository接口,添加了两个接口。
package org.springframework.data.repository; @NoRepositoryBean public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> { Iterable<T> findAll(Sort sort); Page<T> findAll(Pageable pageable); }这里包含了每种查询的写法,举个例子说明一下:
/** * 根据eamil和名字查询 * * @param email * @param name * @return */ List<Person> findByEmailAndName(String email, String name);只需要定义一个接口即可,该接口会自动解析为根据eamil和name来查询 以下是完整代码
package com.framework.example.repository; import java.util.List; import java.util.concurrent.CompletableFuture; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.util.Streamable; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Repository; import com.framework.example.entity.Person; /** * <p> * 描述:person数据查询 * </p> * * @author aLiang * @date 2020年9月2日 下午3:41:40 */ @Repository public interface PersonRepository extends PagingAndSortingRepository<Person, Long> { /** * 根据名字查询数量 * * @param name * @return */ long countByName(String name); /** * 根据地址查询 * * @param address * @return */ long countByAddress(String address); /** * 根据名字删除,并返回删除数量 * * @param name * @return */ long deleteByName(String name); /** * 根据名字删除并返回删除对象 * * @param name * @return */ List<Person> removeByName(String name); /** * 根据名字查询 * * @param name * @return */ List<Person> findByName(String name); /** * 根据eamil和名字查询 * * @param email * @param name * @return */ List<Person> findByEmailAndName(String email, String name); /** * 根据email或者手机号查询 * * @param email * @param mobile * @return */ List<Person> findDistinctByEmailOrMobile(String email, String mobile); /** * 根据名字分页查询 * * @param name * @param page * @return */ Page<Person> findByName(String name, Pageable page); /** * 根据名字按照id倒序排列查询 * * @param name * @param page * @return */ Page<Person> findByNameOrderByIdDesc(String name, Pageable page); /** * 根据company对象下的name属性查询 * * @param companyName * @param page * @return */ Page<Person> findByCompanyName(String companyName, Pageable page); /** * 根据company对象下的name或者根据company对象下的nature属性查询 * * @param companyName * @param ompanyNature * @return */ List<Person> findByCompanyNameOrCompanyNature(String companyName, String ompanyNature); /** * 根据id范围查询 * * @param start * @param end * @return */ List<Person> findByIdBetween(Long start, Long end); /** * id小于参数查询 * * @param id * @return */ List<Person> findByIdLessThan(Long id); /** * 名字模糊查询并且根据id范围查询 * * @param name * @param from * @param to * @return */ List<Person> findByNameLikeAndIdBetween(String name, Long from, Long to); /** * 忽略email大小写查询 * * @param email * @return */ List<Person> findByEmailIgnoreCaseLike(String email); /** * 查询前10 * * @param name * @param sort * @return */ List<Person> findFirst10ByName(String name, Sort sort); /** * 查询前3 * * @param name * @param pageable * @return */ Slice<Person> findTop3ByName(String name, Pageable pageable); /** * 根据名字查询前10分页 * * @param name * @param pageable * @return */ Page<Person> queryFirst10ByName(String name, Pageable pageable); /** * 小于参数id查询 * * @param id * @return */ Streamable<Person> queryByIdLessThan(Long id); /** * 大于id参数查询 * * @param id * @return */ Streamable<Person> queryByIdGreaterThan(Long id); /** * 根据名字异步查询 * * @param name * @return */ @Async CompletableFuture<Person> findOneByName(String name); @Query("{\"range\":{\"id\":{\"from\":\"?0\",\"to\":\"?1\"}}}") List<Person> queryByIdSql(Long start, Long end); @Query("{\"match\": {\"name\": {\"query\": \"?0\"}}}") List<Person> queryByNameSql(String name); }底层会根据方法名来解析具体的查询,从此告别写查询语句。
表达式通常是属性遍历,并带有可串联的运算符。您可以使用AND和组合属性表达式OR,您还可以使用Between,LessThan,GreaterThan,和Like表达式。 Between需要两个参数:findbyIdBetween(long from, long to)LessThan小于:findbyIdLessThan(long id) 关键字示例查询信息AndfindByNameAndPrice{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }}OrfindByNameOrPrice{ “query” : { “bool” : { “should” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } }, { “query_string” : { “query” : “?”, “fields” : [ “price” ] } } ] } }}IsfindByName{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }}NotfindByNameNot{ “query” : { “bool” : { “must_not” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] } } ] } }}BetweenfindByPriceBetween{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}LessThanfindByPriceLessThan{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : false } } } ] } }}LessThanEqualfindByPriceLessThanEqual{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}GreaterThanfindByPriceGreaterThan{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : false, “include_upper” : true } } } ] } }}GreaterThanEqualfindByPriceGreaterThan{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }}BeforefindByPriceBefore{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : null, “to” : ?, “include_lower” : true, “include_upper” : true } } } ] } }}AfterfindByPriceAfter{ “query” : { “bool” : { “must” : [ {“range” : {“price” : {“from” : ?, “to” : null, “include_lower” : true, “include_upper” : true } } } ] } }}LikefindByNameLike{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}StartingWithfindByNameStartingWith{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?*”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}EndingWithfindByNameEndingWith{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “*?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}Contains/ContainingfindByNameContaining{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “?”, “fields” : [ “name” ] }, “analyze_wildcard”: true } ] } }}InfindByNameIn(Collectionnames){ “query” : { “bool” : { “must” : [ {“bool” : {“must” : [ {“terms” : {“name” : ["?","?"]}} ] } } ] } }}NotInfindByNameNotIn(Collectionnames){ “query” : { “bool” : { “must” : [ {“bool” : {“must_not” : [ {“terms” : {“name” : ["?","?"]}} ] } } ] } }}NearfindByStoreNearNot Supported Yet !TRUEfindByAvailableTrue{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “true”, “fields” : [ “available” ] } } ] } }}FALSEfindByAvailableFalse{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “false”, “fields” : [ “available” ] } } ] } }}OrderByfindByAvailableTrueOrderByNameDesc{ “query” : { “bool” : { “must” : [ { “query_string” : { “query” : “true”, “fields” : [ “available” ] } } ] } }, “sort”:[{“name”:{“order”:“desc”}}] }如果您曾经使用过ElasticsearchTemplate ,那么对ElasticsearchRestTemplate也不会陌生,里面实现了多种api,具体的api不再解释。但是ElasticsearchTemplate 在该版本中已经废弃。
在test包下PersonTest 中包含各个接口的测试,在PersonController中包含部分测试接口
package com.framework.example; import java.time.LocalDate; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Sort.TypedSort; import org.springframework.data.util.Streamable; import org.springframework.test.context.junit4.SpringRunner; import com.alibaba.fastjson.JSONObject; import com.framework.example.entity.Company; import com.framework.example.entity.Person; import com.framework.example.repository.PersonRepository; import lombok.extern.log4j.Log4j2; /** * <p> * 描述: * </p> * * @author aLiang * @date 2020年9月2日 下午4:53:58 */ @RunWith(SpringRunner.class) @SpringBootTest @Log4j2 public class PersonTest { @Autowired private PersonRepository personRepository; @Test public void saveTest() { Company c = new Company("百度在线网络技术(北京)有限公司", "北京市海淀区中关村街道", "18510367878", "123@baidu.com", "互联网", "https://www.baidu.com"); Person p = new Person(1L, "李四", "北京市海淀区中关村街道", "165187413544", "123@baidu.com", LocalDate.of(1999, 2, 12), "321302199902121478", c); Person save = personRepository.save(p); log.info("保存后的结果 {} ", JSONObject.toJSONString(save, true)); } @Test public void deleteByIdTest() { long id = 0L; personRepository.deleteById(id); log.info("删除数据id {}", id); } @Test public void findAllSortTest() { // 排序的列 Sort sort = Sort.by("birthday"); // 升序 log.info("升序 {}", JSONObject.toJSONString(personRepository.findAll(sort), true)); // sort.descending() 倒序 log.info("倒序 {}", JSONObject.toJSONString(personRepository.findAll(sort.descending()), true)); } @Test public void countByNameTest() { String name = "良"; long count = personRepository.countByName(name); log.info("姓名:{},数量: {}", name, count); } @Test public void countByAddressTest() { String address = "深圳"; long count = personRepository.countByAddress(address); log.info("地址:{},数量: {}", address, count); } @Test public void deleteByNameTest() { String name = "张四"; long count = personRepository.deleteByName(name); log.info("删除姓名:{},删除数量: {}", name, count); } @Test public void findByNameTest() { String name = "李四"; List<Person> list = personRepository.findByName(name); log.info("姓名:{},结果: {}", name, JSONObject.toJSONString(list, true)); } @Test public void findByEmailAndNameTest() { String name = "阿良"; String email = "aliang@huawei.com"; List<Person> list = personRepository.findByEmailAndName(email, name); log.info("email:{},name: {},结果: {}", email, name, JSONObject.toJSONString(list, true)); } @Test public void findDistinctByEmailOrMobile() { String email = "liyiyi@huawei.com"; String mobile = "16518741234"; List<Person> list = personRepository.findDistinctByEmailOrMobile(email, mobile); log.info("email:{},mobile: {},结果: {}", email, mobile, JSONObject.toJSONString(list, true)); } @Test public void findByNamePageTest() { // 分页查询 String name = "李四"; // 从0开始 int page = 0; // 必须大于0 int size = 2; // 排序可以这样 // 方式1 // Sort sort = Sort.by("id"); // PageRequest.of(page, size, sort); // 方式2 TypedSort<Person> typedSort = Sort.sort(Person.class); Sort sort = typedSort.by(Person::getId).ascending().and(typedSort.by(Person::getBirthday).descending()); Pageable pageable = PageRequest.of(page, size, sort); Page<Person> result = personRepository.findByName(name, pageable); log.info("name: {}, 分页结果: {}", name, JSONObject.toJSONString(result, true)); } @Test public void findByNameNoPageTest() { // 不分页 String name = "李四"; Pageable unpaged = Pageable.unpaged(); Page<Person> result = personRepository.findByName(name, unpaged); log.info("name: {}, 分页结果: {}", name, JSONObject.toJSONString(result, true)); } @Test public void findByNameOrderByIdDescTest() { String name = "李四"; // 从0开始 int page = 0; // 必须大于0 int size = 2; Pageable pageable = PageRequest.of(page, size); Page<Person> result = personRepository.findByNameOrderByIdDesc(name, pageable); log.info("name: {}, 分页结果: {}", name, JSONObject.toJSONString(result, true)); } @Test public void findByCompanyTest() { // 从0开始 int page = 0; // 必须大于0 int size = 2; Pageable pageable = PageRequest.of(page, size); String companyName = "华为"; Page<Person> result = personRepository.findByCompanyName(companyName, pageable); log.info("分页结果: {}", JSONObject.toJSONString(result, true)); } @Test public void findByCompanyNameOrCompanyNatureTest() { String companyName = "度"; String ompanyNature = "硬件"; List<Person> list = personRepository.findByCompanyNameOrCompanyNature(companyName, ompanyNature); log.info("结果: {}", JSONObject.toJSONString(list, true)); } @Test public void findByIdBetweenTest() { long start = 1L; long end = 3L; List<Person> list = personRepository.findByIdBetween(start, end); log.info("结果: {}", JSONObject.toJSONString(list, true)); } @Test public void findByIdLessThanTest() { long id = 3L; List<Person> list = personRepository.findByIdLessThan(id); log.info("结果: {}", JSONObject.toJSONString(list, true)); } @Test public void findByIdBetweenAndNameTest() { long start = 1L; long end = 5L; String name = "李"; List<Person> list = personRepository.findByNameLikeAndIdBetween(name, start, end); log.info("结果: {}", JSONObject.toJSONString(list, true)); } @Test public void findByEmailIgnoreCaseTest() { String email = "Huawei"; List<Person> list = personRepository.findByEmailIgnoreCaseLike(email); log.info("结果: {}", JSONObject.toJSONString(list, true)); } @Test public void streamableTest() { // 将结果合并 Streamable<Person> streamable = personRepository.queryByIdGreaterThan(5L) .and(personRepository.queryByIdLessThan(2L)); List<Person> list = streamable.toList(); log.info("结果: {}", JSONObject.toJSONString(list, true)); } @Test public void findOneByNameTest() throws InterruptedException, ExecutionException { String name = "李四"; CompletableFuture<Person> future = personRepository.findOneByName(name); Person person = future.get(); log.info("结果: {}", JSONObject.toJSONString(person, true)); } @Test public void queryBySqlTest() { List<Person> list = personRepository.queryByIdSql(1L, 3L); log.info("结果: {}", JSONObject.toJSONString(list, true)); } @Test public void queryByNameSqlTest() { List<Person> list = personRepository.queryByNameSql("李四"); log.info("结果: {}", JSONObject.toJSONString(list, true)); } }实现com.framework.example.repository.PagingAndSortingRepository<T, ID>接口即可,具体的查询按照方法命名规约即可实现。
戳我查看源码