美文网首页Spring cloud技术方案ElasticSearch入门
Elasticsearch实战篇——Spring Boot整合E

Elasticsearch实战篇——Spring Boot整合E

作者: 冯文议 | 来源:发表于2019-03-11 21:47 被阅读94次

    当前Spring Boot很是流行,包括我自己,也是在用Spring Boot集成其他框架进行项目开发,所以这一节,我们一起来探讨Spring Boot整合ElasticSearch的问题。

    步骤如下:

    第一步,通读文档。

    第二步,用三种方式实现基本CRUD操作。

    第三步,分析实战中可能用到的查询。

    第四步,搜索专题。

    还没有学过Elasticsearch的朋友,可以先学这个系列的第一节(这个系列共三节),如果你有不明白或者不正确的地方,可以给我评论、留言或者私信。

    第一步,通读文档

    Spring Data Elasticsearch 官方文档,这是当前最新的文档。

    关于repository

    文档一开始就介绍 CrudRepository ,比如,继承 Repository,其他比如 JpaRepositoryMongoRepository是继承CrudRepository。也对其中的方法做了简单说明,我们一起来看一下:

    public interface CrudRepository<T, ID extends Serializable>
      extends Repository<T, ID> {
    
    // Saves the given entity.
      <S extends T> S save(S entity);      
    
    // Returns the entity identified by the given ID.
      Optional<T> findById(ID primaryKey); 
    
    // Returns all entities.
      Iterable<T> findAll();               
    
    // Returns the number of entities.
      long count();                        
    
    // Deletes the given entity.
      void delete(T entity);               
    
    // Indicates whether an entity with the given ID exists.
      boolean existsById(ID primaryKey);   
    
      // … more functionality omitted.
    }
    

    好了,下面我们看一下今天的主角 ElasticsearchRepository 他是怎样的吧。

    ElasticsearchRepository继承图

    这说明什么?

    • 用法和JPA一样;

    • 再这他除了有CRUD的基本功能之外,还有分页和排序。

    清楚了这之后,是不是应该考虑该如何使用了呢?

    如何用?

    没错,接下来,开始说如何用,也写了很多示例代码。相对来说,还是比较简单,这里就贴一下代码就行了吧。

    interface PersonRepository extends Repository<User, Long> {
    
      List<Person> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname);
    
      // Enables the distinct flag for the query
      List<Person> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname);
      List<Person> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname);
    
      // Enabling ignoring case for an individual property
      List<Person> findByLastnameIgnoreCase(String lastname);
      // Enabling ignoring case for all suitable properties
      List<Person> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname);
    
      // Enabling static ORDER BY for a query
      List<Person> findByLastnameOrderByFirstnameAsc(String lastname);
      List<Person> findByLastnameOrderByFirstnameDesc(String lastname);
    }
    

    是不是这样,就可以正常使用了呢?

    问题

    当然可以,但是如果错了问题怎么办呢,官网写了一个常见的问题,比如包扫描问题,没有你要的方法。

    interface HumanRepository {
      void someHumanMethod(User user);
    }
    
    class HumanRepositoryImpl implements HumanRepository {
    
      public void someHumanMethod(User user) {
        // Your custom implementation
      }
    }
    
    interface ContactRepository {
    
      void someContactMethod(User user);
    
      User anotherContactMethod(User user);
    }
    
    class ContactRepositoryImpl implements ContactRepository {
    
      public void someContactMethod(User user) {
        // Your custom implementation
      }
    
      public User anotherContactMethod(User user) {
        // Your custom implementation
      }
    }
    

    你也可以自己写接口,并且去实现它。

    说完理论,作为我,应该在实际的代码中如何运用呢?

    示例

    官方也提供了很多示例代码,我们一起来看看。

    @Controller
    class PersonController {
    
      @Autowired PersonRepository repository;
    
      @RequestMapping(value = "/persons", method = RequestMethod.GET)
      HttpEntity<PagedResources<Person>> persons(Pageable pageable,
        PagedResourcesAssembler assembler) {
    
        Page<Person> persons = repository.findAll(pageable);
        return new ResponseEntity<>(assembler.toResources(persons), HttpStatus.OK);
      }
    }
    

    这段代码相对来说还是十分经典的,我相信很多人都看到别人的代码,可能都会问,它为什么会这么用呢,答案或许就在这里吧。

    当然,这是以前的代码,或许现在用不一定合适。

    高级搜索

    终于到高潮了!

    学完我的第一节,你应该已经发现了,Elasticsearch搜索是一件十分复杂的事,为了用好它,我们不得不学好它。一起加油。

    到这里,官方文档我们算是过了一遍了,大致明白了,他要告诉我们什么。其实,文档还有很多内容,可能你遇到的问题都能在里面找到答案。

    最后,我们继续看一下官网写的一段处理得十分优秀的一段代码吧:

    SearchQuery searchQuery = new NativeSearchQueryBuilder()
        .withQuery(matchAllQuery())
        .withIndices(INDEX_NAME)
        .withTypes(TYPE_NAME)
        .withFields("message")
        .withPageable(PageRequest.of(0, 10))
        .build();
    
    CloseableIterator<SampleEntity> stream = elasticsearchTemplate.stream(searchQuery, SampleEntity.class);
    
    List<SampleEntity> sampleEntities = new ArrayList<>();
    while (stream.hasNext()) {
        sampleEntities.add(stream.next());
    }
    

    Spring Boot整合ElasticSearch

    添加依赖

    implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
    

    添加配置

    spring:
      data:
        elasticsearch:
          cluster-nodes: localhost:9300
          cluster-name: es-wyf
    

    这样就完成了整合,接下来我们用两种方式操作。

    Model

    我们先写一个的实体类,借助这个实体类呢来完成基础的CRUD功能。

    @Data
    @Accessors(chain = true)
    @Document(indexName = "blog", type = "java")
    public class BlogModel implements Serializable {
    
        private static final long serialVersionUID = 6320548148250372657L;
    
        @Id
        private String id;
    
        private String title;
    
        //@Field(type = FieldType.Date, format = DateFormat.basic_date)
        @DateTimeFormat(pattern = "yyyy-MM-dd")
        @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
        private Date time;
    }
    

    注意id字段是必须的,可以不写注解@Id。

    Repository

    BlogRepository

    public interface BlogRepository extends ElasticsearchRepository<BlogModel, String> {
    }
    

    基础操作

    基础操作的代码,都是在 BlogController 里面写。

    @RestController
    @RequestMapping("/blog")
    public class BlogController {
        @Autowired
        private BlogRepository blogRepository;
    }
    

    添加

    @PostMapping("/add")
    public Result add(@RequestBody BlogModel blogModel) {
        blogRepository.save(blogModel);
        return Result.success();
    }
    

    我们添加一条数据,标题是:Elasticsearch实战篇:Spring Boot整合ElasticSearch,时间是:2019-03-06。我们来测试,看一下成不成功。

    POST http://localhost:8080/blog/add

    {
        "title":"Elasticsearch实战篇:Spring Boot整合ElasticSearch",
        "time":"2019-05-06"
    }
    

    得到响应:

    {
        "code": 0,
        "msg": "Success"
    }
    

    嘿,成功了。那接下来,我们一下查询方法测试一下。

    查询

    • 根据ID查询
    @GetMapping("/get/{id}")
    public Result getById(@PathVariable String id) {
        if (StringUtils.isEmpty(id))
            return Result.error();
        Optional<BlogModel> blogModelOptional = blogRepository.findById(id);
        if (blogModelOptional.isPresent()) {
            BlogModel blogModel = blogModelOptional.get();
            return Result.success(blogModel);
        }
        return Result.error();
    }
    

    测试一下:

    测试根据ID查询

    ok,没问题。

    • 查询所有
    @GetMapping("/get")
    public Result getAll() {
        Iterable<BlogModel> iterable = blogRepository.findAll();
        List<BlogModel> list = new ArrayList<>();
        iterable.forEach(list::add);
        return Result.success(list);
    }
    

    测试一下:

    GET http://localhost:8080/blog/get

    结果:

    {
        "code": 0,
        "msg": "Success",
        "data": [
            {
                "id": "fFXTTmkBTzBv3AXCweFS",
                "title": "Elasticsearch实战篇:Spring Boot整合ElasticSearch",
                "time": "2019-05-06"
            }
        ]
    }
    

    根据ID修改

    @PostMapping("/update")
    public Result updateById(@RequestBody BlogModel blogModel) {
        String id = blogModel.getId();
        if (StringUtils.isEmpty(id))
            return Result.error();
        blogRepository.save(blogModel);
        return Result.success();
    }
    

    测试:

    POST http://localhost:8080/blog/update

    {
        "id":"fFXTTmkBTzBv3AXCweFS",
        "title":"Elasticsearch入门篇",
        "time":"2019-05-01"
    }
    

    响应:

    {
        "code": 0,
        "msg": "Success"
    }
    

    查询一下:

    修改数据成功

    ok,成功!

    删除

    • 根据ID删除
    @DeleteMapping("/delete/{id}")
    public Result deleteById(@PathVariable String id) {
        if (StringUtils.isEmpty(id))
            return Result.error();
        blogRepository.deleteById(id);
        return Result.success();
    }
    

    测试:

    DELETE http://localhost:8080/blog/delete/fFXTTmkBTzBv3AXCweFS

    响应:

    {
        "code": 0,
        "msg": "Success"
    }
    

    我们再查一下:

    删除数据成功
    • 删除所有数据
    @DeleteMapping("/delete")
    public Result deleteById() {
        blogRepository.deleteAll();
        return Result.success();
    }
    

    构造数据

    为了方便测试,我们先构造数据

    构造查询数据

    Repository查询操作

    搜索标题中的关键字

    BlogRepository

    List<BlogModel> findByTitleLike(String keyword);
    

    BlogController

    @GetMapping("/rep/search/title")
    public Result repSearchTitle(String keyword) {
        if (StringUtils.isEmpty(keyword))
            return Result.error();
        return Result.success(blogRepository.findByTitleLike(keyword));
    }
    

    我们来测试一下。

    POST http://localhost:8080/blog/rep/search/title?keyword=java

    结果:

    {
        "code": 0,
        "msg": "Success",
        "data": [
            {
                "id": "f1XrTmkBTzBv3AXCeeFA",
                "title": "java实战",
                "time": "2018-03-01"
            },
            {
                "id": "fVXrTmkBTzBv3AXCHuGH",
                "title": "java入门",
                "time": "2018-01-01"
            },
            {
                "id": "flXrTmkBTzBv3AXCUOHj",
                "title": "java基础",
                "time": "2018-02-01"
            },
            {
                "id": "gFXrTmkBTzBv3AXCn-Eb",
                "title": "java web",
                "time": "2018-04-01"
            },
            {
                "id": "gVXrTmkBTzBv3AXCzuGh",
                "title": "java ee",
                "time": "2018-04-10"
            }
        ]
    }
    

    继续搜索:

    GET http://localhost:8080/blog/rep/search/title?keyword=入门

    结果:

    {
        "code": 0,
        "msg": "Success",
        "data": [
            {
                "id": "hFXsTmkBTzBv3AXCtOE6",
                "title": "Elasticsearch入门",
                "time": "2019-01-20"
            },
            {
                "id": "fVXrTmkBTzBv3AXCHuGH",
                "title": "java入门",
                "time": "2018-01-01"
            },
            {
                "id": "glXsTmkBTzBv3AXCBeH_",
                "title": "php入门",
                "time": "2018-05-10"
            }
        ]
    }
    

    为了验证,我们再换一个关键字搜索:

    GET http://localhost:8080/blog/rep/search/title?keyword=java入门

    {
        "code": 0,
        "msg": "Success",
        "data": [
            {
                "id": "fVXrTmkBTzBv3AXCHuGH",
                "title": "java入门",
                "time": "2018-01-01"
            },
            {
                "id": "hFXsTmkBTzBv3AXCtOE6",
                "title": "Elasticsearch入门",
                "time": "2019-01-20"
            },
            {
                "id": "glXsTmkBTzBv3AXCBeH_",
                "title": "php入门",
                "time": "2018-05-10"
            },
            {
                "id": "gFXrTmkBTzBv3AXCn-Eb",
                "title": "java web",
                "time": "2018-04-01"
            },
            {
                "id": "gVXrTmkBTzBv3AXCzuGh",
                "title": "java ee",
                "time": "2018-04-10"
            },
            {
                "id": "f1XrTmkBTzBv3AXCeeFA",
                "title": "java实战",
                "time": "2018-03-01"
            },
            {
                "id": "flXrTmkBTzBv3AXCUOHj",
                "title": "java基础",
                "time": "2018-02-01"
            }
        ]
    }
    

    哈哈,有没有觉得很眼熟。

    那根据上次的经验,我们正好换一种方式解决这个问题。

    @Query("{\"match_phrase\":{\"title\":\"?0\"}}")
    List<BlogModel> findByTitleCustom(String keyword);
    

    值得一提的是,官方文档示例代码可能是为了好看,出现问题。

    官网文档给的错误示例:

    官网文档错误

    官网示例代码:

    官方示例代码

    官方示例代码

    另外,?0 代指变量的意思。

    @GetMapping("/rep/search/title/custom")
    public Result repSearchTitleCustom(String keyword) {
        if (StringUtils.isEmpty(keyword))
            return Result.error();
        return Result.success(blogRepository.findByTitleCustom(keyword));
    }
    

    测试一下:

    测试成功示例

    ok,没有问题。

    ElasticsearchTemplate

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
    
    @GetMapping("/search/title")
    public Result searchTitle(String keyword) {
        if (StringUtils.isEmpty(keyword))
            return Result.error();
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(queryStringQuery(keyword))
                .build();
        List<BlogModel> list = elasticsearchTemplate.queryForList(searchQuery, BlogModel.class);
        return Result.success(list);
    }
    

    测试:

    POST http://localhost:8080/blog/search/title?keyword=java入门

    结果:

    {
        "code": 0,
        "msg": "Success",
        "data": [
            {
                "id": "fVXrTmkBTzBv3AXCHuGH",
                "title": "java入门",
                "time": "2018-01-01"
            },
            {
                "id": "hFXsTmkBTzBv3AXCtOE6",
                "title": "Elasticsearch入门",
                "time": "2019-01-20"
            },
            {
                "id": "glXsTmkBTzBv3AXCBeH_",
                "title": "php入门",
                "time": "2018-05-10"
            },
            {
                "id": "gFXrTmkBTzBv3AXCn-Eb",
                "title": "java web",
                "time": "2018-04-01"
            },
            {
                "id": "gVXrTmkBTzBv3AXCzuGh",
                "title": "java ee",
                "time": "2018-04-10"
            },
            {
                "id": "f1XrTmkBTzBv3AXCeeFA",
                "title": "java实战",
                "time": "2018-03-01"
            },
            {
                "id": "flXrTmkBTzBv3AXCUOHj",
                "title": "java基础",
                "time": "2018-02-01"
            }
        ]
    }
    

    OK,暂时先到这里,关于搜索,我们后面会专门开一个专题,学习搜索。

    Jest

    搜索中,发现Jest也可以操纵Elasticsearch,目前官方有star数1500+。网址:https://github.com/spring-projects/spring-data-elasticsearch

    另外,看到有人提供了Spring Boot整合的代码,如下:

    @Service
    public class JestClientService implements Serializable {
     private static final long serialVersionUID = 1L;
    JestClient client=null;
     
     @Value(“${jest.elasticsearch.host}”)
     String host;
     
     @Value(“${jest.elasticsearch.port}”)
     String port;
     
     @Value(“${jest.elasticsearch.index}”)
     String indexName;
    /**
     * 
     */
     public JestClient getClient() {
     if (this.client==null){
    GsonFireBuilder fireBuilder = new GsonFireBuilder();
     fireBuilder.enableExposeMethodResult();
     
     GsonBuilder builder = fireBuilder.createGsonBuilder();
     builder.excludeFieldsWithoutExposeAnnotation();
     
     final Gson gson = builder.setDateFormat(AbstractJestClient.ELASTIC_SEARCH_DATE_FORMAT).create();
    System.out.println(“Establishing JEST Connection to Elasticsearch over HTTP: “+”http://”+this.host+”:”+this.port);
     JestClientFactory factory = new JestClientFactory();
     factory.setHttpClientConfig(new HttpClientConfig
     .Builder(“http://”+this.host+”:”+this.port)
     .multiThreaded(true)
     .readTimeout(20000)
     .gson(gson)
     .build());
     this.client = factory.getObject();
     
     }
     
     return this.client;
     }
    }
    

    链接

    ElasticSearch 学习系列

    相关文章

      网友评论

        本文标题:Elasticsearch实战篇——Spring Boot整合E

        本文链接:https://www.haomeiwen.com/subject/dyuvuqtx.html