ElasticSearch学习笔记六:Springboot整合

发布于:2024-11-27 ⋅ 阅读:(15) ⋅ 点赞:(0)

一、前言

在前一篇文章中,我们学习了ES中的一部分的搜索功能,作为一名Java工程师,更多时候我们是用代码去操作ES,同时对于Java而言时下最流行的就是Springboot了,所以这里我们将ES和Springboot整合将上一篇文章中的所有DSL操作,使用Java代码来操作一遍从而加深记忆。

二、创建一个Springboot项目

1、POM文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>springboot-es</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>springboot-es</name>
  <description>springboot-es</description>
  <properties>
    <java.version>17</java.version>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <spring-boot.version>2.7.6</spring-boot.version>
  </properties>
  <dependencies>

    <!--Spring Data ES-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>

    <!--Spring Web-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!--Spring Test-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

    <!--Lombok:简化开发-->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </dependency>


  </dependencies>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.8.1</version>
        <configuration>
          <source>17</source>
          <target>17</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
        <version>${spring-boot.version}</version>
        <configuration>
          <mainClass>com.example.SpringbootEsApplication</mainClass>
          <skip>true</skip>
        </configuration>
        <executions>
          <execution>
            <id>repackage</id>
            <goals>
              <goal>repackage</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

依赖解释:

(1)首先我们是用的是Springboot2.7.6的版本,JDK则使用的是17,当然用JDK8也是一样的。

(2)我们是用SpringData作为操作数据的框架,当然你也可以用原生的客户端。

(3)添加了Web依赖,我们直接使用接口的形式可以更方便的看出效果

(4)lombok:则是为了简化开发,可以简单的认为是用于生成get/set方法

2、配置文件
server:
  port: 8088
spring:
  elasticsearch:
    uris: http://localhost:9200

这里没啥好说的,就是定义了程序运行在8088端口,同时es的地址为localhost:9200。至此Springboot和ES的整合就完成了,是不是很简单。当然这背后可一点也不简单,之所以我们可以这么简单的配置就能做好整合,是因为Springboot为我们提供的强大的自动装配,这个不在本文的范畴,暂时放下不表。

3、编写代码

在上一篇文章中,我们已经定义了索引的结构

PUT /hotel
{
  "mappings": {
    "properties": {
      "title":{
        "type": "text" 
      },
      "city":{
        "type": "keyword"
      },
      "price":{
        "type": "double"
      },
      "create_time":{
        "type": "date",
        "format": "yyyy-MM-dd HH:mm:ss"
      },
      "amenities":{
        "type": "text"
      },
      "full_room":{
        "type": "boolean"
      },
      "location":{
        "type": "geo_point"
      },
      "praise":{
        "type": "integer"
      }
    }
  }
3.1、编写实体类

按照这个索引的结构,我们创建对应的实体类,这个和我们是用Mybatis时很像,也要定义对应的实体类。

package com.example.entity;

import com.fasterxml.jackson.annotation.JsonFormat;
import java.math.BigDecimal;
import java.util.Date;
import lombok.Data;
import org.springframework.data.annotation.Id;
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 org.springframework.data.elasticsearch.annotations.GeoPointField;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;

/**
 * @Author hardy(叶阳华)
 * @Description
 * @Date 2024/11/26 14:06
 */
@Data
@Document(indexName = "hotel", createIndex = false)
public class Hotel {

    /**
     * ID
     */
    @Id
    private String id;

    /**
     * 标题:text类型,使用ik_max_word作为分词器
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
    private String title;

    /**
     * 所在城市:所在城市没必要分词
     */
    @Field(type = FieldType.Keyword)
    private String city;

    /**
     * 价格
     */
    @Field(type = FieldType.Double)
    private BigDecimal price;

    /**
     * 便利措施
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word", searchAnalyzer = "ik_max_word")
    private String amenities;
    
    /**
     * 创建时间
     */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Field(value = "create_time", type = FieldType.Date, format = {}, pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;

    /**
     * 是否满员
     */
    @Field(value = "full_room", type = FieldType.Boolean)
    private Boolean fullRoom;

    /**
     * 位置
     */
    @GeoPointField
    private GeoPoint location;

    @Field(type = FieldType.Integer)
    private Integer praise;
}

说一下笔者编写过程中碰到的小坑

(1)Date类型,如果要自定义日期的格式,需要将format设置为custom(不建议,即将被废弃),或者设置为{},否则就会提示日期转换异常

(2)地理位置,一开始笔者也以为可以直接使用@Field(type = FieldType.Point) 然后,并没有这个类型,官网上则说使用 @GeoPoint 然而还是没有,最终发现有一个@GeoPointField尝试了一下发现可以。

(3)@GeoPointField 并没有任何属性,如果我们的字段名和索引中的字段名不一致,则可以使用 @Field去标记,如

3.2、查询索引是否存在
    @Resource
    private ElasticsearchOperations elasticsearchOperations;


    /**
     * 校验索引是否存在
     */
    @GetMapping("checkIndexExists")
    public Boolean checkIndexExists() {
        return elasticsearchOperations.indexOps(Hotel.class).exists();
    }

tips:ElasticsearchOperations是SpringData为我们提供的一个工具类,用于简化开发。

执行结果

3.3、批量写入数据
  /**
     * 批量写入
     */
    @PostMapping("/batchCreate")
    public Boolean batchCreate(@RequestBody List<Hotel> hotelList) {
        if (CollectionUtils.isEmpty(hotelList)) {
            throw new IllegalArgumentException("参数不能为空");
        }
        final Iterable<Hotel> hotels = elasticsearchOperations.save(hotelList);
        hotels.iterator().forEachRemaining(System.out::println);
        return true;
    }

请求参数:

[
	{
		"id": "001",
		"title": "文雅酒店",
		"city": "青岛",
		"price": 556,
		"createTime": "2020-04-18 12:00:00",
		"amenities": "浴池,普通停车场/充电停车场",
		"fullRoom": false,
		"location": {
			"lat": 36.083078,
			"lon": 120.37566
		},
		"praise": 10
	},
	{
		"id": "002",
		"title": "金都嘉怡假日酒店",
		"city": "北京",
		"price": 337,
		"createTime": "2021-03-15 20:00:00",
		"amenities": "wifi,充电停车场/可升降停车场",
		"fullRoom": false,
		"location": {
			"lat": 39.915153,
			"lon": 116.403
		},
		"praise": 60
	},
	{
		"id": "003",
		"itle": "金都欣欣酒店",
		"city": "天津",
		"price": 200,
		"createTime": "2021-05-09 16:00:00",
		"amenities": "提供假日party,免费早餐,可充电停车场",
		"fullRoom": true,
		"location": {
			"lat": 39.186555,
			"lon": 117.162007
		},
		"praise": 30
	},
	{
		"id": "004",
		"title": "金都酒店",
		"city": "北京",
		"price": 500,
		"createTime": "2021-02-18 08:00:00",
		"amenities": "浴池(假日需预定),室内游泳池,普通停车场",
		"fullRoom": true,
		"location": {
			"lat": 39.915343,
			"lon": 116.4239
		},
		"praise": 20
	},
	{
		"id": "005",
		"title": "文雅精选酒店",
		"city": "北京",
		"price": 800,
		"createTime": "2021-01-01 08:00:00",
		"amenities": "浴池(假日需预定),wifi,室内游泳池,普通停车场",
		"fullRoom": true,
		"location": {
			"lat": 39.918229,
			"lon": 116.422011
		},
		"praise": 20
	}
]

结果:

3.4、查询所有数据
@GetMapping("/queryAll")
public List<Hotel> queryAll() {
    Criteria criteria = new Criteria();
    Query query = new CriteriaQuery(criteria);
    final SearchHits<Hotel> searchHits = elasticsearchOperations.search(query, Hotel.class);
    //没有命中任何文档
    if (!searchHits.hasSearchHits()) {
        return new ArrayList<>();
    }
    return searchHits.getSearchHits().stream().map(SearchHit::getContent).collect(Collectors.toList());
}

结果:

3.5、分页查询
   @GetMapping("/pageQuery")
    public Page<Hotel> pageQuery(String city, int pageNo, int pageSize) {
        Criteria criteria = Criteria.where("city").is(city);
        PageRequest pageRequest = PageRequest.of(pageNo, pageSize);
        Query query = new CriteriaQuery(criteria);
        query.setPageable(pageRequest);
        final SearchHits<Hotel> searchHits = elasticsearchOperations.search(query, Hotel.class);
        if (!searchHits.hasSearchHits()) {
            return new PageImpl<>(new ArrayList<>());
        }
        List<Hotel> hotels = searchHits.getSearchHits().stream()
            .map(SearchHit::getContent)
            .collect(Collectors.toList());
        //总记录数
        final long totalHits = searchHits.getTotalHits();
        // 返回分页结果
        return new PageImpl<>(hotels, pageRequest, totalHits);
    }

结果:

{
  "content": [
    {
      "id": "002",
      "title": "金都嘉怡假日酒店",
      "city": "北京",
      "price": 337,
      "amenities": "wifi,充电停车场/可升降停车场",
      "createTime": "2021-03-15 20:00:00",
      "fullRoom": false,
      "location": {
        "lat": 39.915153,
        "lon": 116.403
      },
      "praise": 60
    },
    {
      "id": "004",
      "title": "金都酒店",
      "city": "北京",
      "price": 500,
      "amenities": "浴池(假日需预定),室内游泳池,普通停车场",
      "createTime": "2021-02-18 08:00:00",
      "fullRoom": true,
      "location": {
        "lat": 39.915343,
        "lon": 116.4239
      },
      "praise": 20
    },
    {
      "id": "005",
      "title": "文雅精选酒店",
      "city": "北京",
      "price": 800,
      "amenities": "浴池(假日需预定),wifi,室内游泳池,普通停车场",
      "createTime": "2021-01-01 08:00:00",
      "fullRoom": true,
      "location": {
        "lat": 39.918229,
        "lon": 116.422011
      },
      "praise": 20
    }
  ],
  "pageable": {
    "sort": {
      "empty": true,
      "unsorted": true,
      "sorted": false
    },
    "offset": 0,
    "pageNumber": 0,
    "pageSize": 3,
    "paged": true,
    "unpaged": false
  },
  "last": true,
  "totalPages": 1,
  "totalElements": 3,
  "first": true,
  "size": 3,
  "number": 0,
  "sort": {
    "empty": true,
    "unsorted": true,
    "sorted": false
  },
  "numberOfElements": 3,
  "empty": false
}
和上面直接返回所有数据不同,分页查询还会返回分页相关信息

三、结束语

至此Springboot和ES已经整合,并且做了一些基础的查询操作,后续我们会继续审核ES的查询,更多的复杂的查询在后面的文章中,希望对你有所帮助。