乐趣区

SpringBoot整合ElasticSearch

Java 客户端

在 Elasticsearch 中,为 java 提供了 2 种客户端,一种是 REST 风格的客户端,另一种是 Java API 的客户端。https://www.elastic.co/guide/…

1.REST 客户端
Elasticsearch 提供了 2 种 REST 客户端,一种是低级客户端,一种是高级客户端。

  • Java Low Level REST Client:官方提供的低级客户端。该客户端通过 http 来连接 Elasticsearch 集群。用户在使用该客户端时需要将请求数据手动拼接成 Elasticsearch 所需 JSON 格式进行发送,收到响应时同样也需要将返回的 JSON 数据手动封装成对象。虽然麻烦,不过该客户端兼容所有的 Elasticsearch 版本。
  • Java High Level REST Client:官方提供的高级客户端。该客户端基于低级客户端实现,它提供了很多便捷的 API 来解决低级客户端需要手动转换数据格式的问题。

2. 构造数据

curl -X POST "http://47.101.129.45:9200/test/house/_bulk?pretty" -H 'Content-Type: application/json' --data-binary '{"index":{"_index":"test","_type":"house"}}
{"id":"1001","title":"整租 · 南丹大楼 1 居室 7500","price":"7500"}
{"index":{"_index":"test","_type":"house"}}
{"id":"1002","title":"陆家嘴板块,精装设计一室一厅,可拎包入住诚意租。","price":"8500"}
{"index":{"_index":"test","_type":"house"}}
{"id":"1003","title":"整租 · 健安坊 1 居室 4050","price":"7500"}
{"index":{"_index":"test","_type":"house"}}
{"id":"1004","title":"整租 · 中凯城市之光 + 视野开阔 + 景色秀丽 + 拎包入住","price":"6500"}
{"index":{"_index":"test","_type":"house"}}
{"id":"1005","title":"整租 · 南京西路品质小区 21213 三轨交汇配套齐 * 拎包入住","price":"6000"}
{"index":{"_index":"test","_type":"house"}}
{"id":"1006","title":"祥康里简约风格 * 南户型拎包入住看房随时","price":"7000"}
'

3.REST 低级客户端
1)用 IDEA 创建 SpringBoot 工程 spring-elasticsearch

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elasticsearch</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>6.5.4</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.4</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2)编写测试

/**
 * REST 低级客户端
 */
public class TestElasticSearch {private static final ObjectMapper MAPPER = new ObjectMapper();

    private RestClient restClient;

    @Before
    public void init() {
        RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost("47.101.129.45", 9200, "http"));
        restClientBuilder.setFailureListener(new RestClient.FailureListener() {
            @Override
            public void onFailure(Node node) {System.out.println("出错了 ->" + node);
            }
        });
        this.restClient = restClientBuilder.build();}

    @After
    public void after() throws IOException {restClient.close();
    }

    /**
     * 查询集群状态
     *
     * @throws IOException
     */
    @Test
    public void testGetInfo() throws IOException {Request request = new Request("GET", "/_cluster/state");
        request.addParameter("pretty", "true");
        Response response = this.restClient.performRequest(request);
        System.out.println(response.getStatusLine());
        System.out.println(EntityUtils.toString(response.getEntity()));
    }

    /**
     * 新增数据
     *
     * @throws IOException
     */
    @Test
    public void testCreateData() throws IOException {Request request = new Request("POST", "/test/house");
        request.addParameter("pretty", "true");

        Map<String, Object> data = new HashMap<>();
        data.put("id", "2001");
        data.put("title", "张江高科");
        data.put("price", "3500");
        request.setJsonEntity(MAPPER.writeValueAsString(data));
        Response response = this.restClient.performRequest(request);

        System.out.println(response.getStatusLine());
        System.out.println(EntityUtils.toString(response.getEntity()));
    }

    /**
     * 根据 id 查询数据
     */
    @Test
    public void testQueryData() throws IOException {Request request = new Request("GET", "/test/house/3xNNOW4BpJzEX51okOM5");
        request.addParameter("pretty", "true");

        Response response = this.restClient.performRequest(request);
        System.out.println(response.getStatusLine());
        System.out.println(EntityUtils.toString(response.getEntity()));
    }

    /**
     * 搜索数据
     */
    @Test
    public void testSearchData() throws IOException {Request request = new Request("POST", "/test/house/_search");
        String searchJson = "{\"query\": {\"match\": {\"title\": \" 拎包入住 \"}}}";
        request.setJsonEntity(searchJson);
        request.addParameter("pretty", "true");
        Response response = this.restClient.performRequest(request);

        System.out.println(response.getStatusLine());
        System.out.println(EntityUtils.toString(response.getEntity()));
    }
}

从使用中,可以看出,基本和我们使用 RESTful api 使用几乎是一致的

4.REST 高级客户端
pom.xml 引入依赖

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>6.5.4</version>
</dependency>

编写测试

/**
 * REST 高级客户端
 */
public class TestRestHighLevel {

    private RestHighLevelClient client;

    @Before
    public void init() {
        RestClientBuilder restClientBuilder = RestClient.builder(new HttpHost("47.101.129.45", 9200, "http")
        );

        this.client = new RestHighLevelClient(restClientBuilder);
    }

    @After
    public void after() throws Exception {this.client.close();
    }

    /**
     * 新增文档,同步操作
     *
     * @throws Exception
     */
    @Test
    public void testCreate() throws Exception {Map<String, Object> data = new HashMap<>();
        data.put("id", "2002");
        data.put("title", "南京西路 拎包入住 一室一厅");
        data.put("price", "4500");

        IndexRequest indexRequest = new IndexRequest("test", "house").source(data);

        IndexResponse indexResponse = this.client.index(indexRequest, RequestOptions.DEFAULT);

        System.out.println(indexResponse);
        System.out.println("id->" + indexResponse.getId());
        System.out.println("index->" + indexResponse.getIndex());
        System.out.println("type->" + indexResponse.getType());
        System.out.println("version->" + indexResponse.getVersion());
        System.out.println("result->" + indexResponse.getResult());
        System.out.println("shardInfo->" + indexResponse.getShardInfo());
    }

    /**
     * 新增文档,异步操作
     */

    @Test
    public void testCreateAsync() throws Exception {Map<String, Object> data = new HashMap<>();
        data.put("id", "2003");
        data.put("title", "南京东路最新房源二室一厅");
        data.put("price", "5500");

        IndexRequest indexRequest = new IndexRequest("test", "house").source(data);

        this.client.indexAsync(indexRequest, RequestOptions.DEFAULT, new ActionListener<IndexResponse>() {
            @Override
            public void onResponse(IndexResponse indexResponse) {System.out.println(indexResponse);
            }

            @Override
            public void onFailure(Exception e) {System.out.println(e);
            }
        });
        Thread.sleep(2000);
    }

    /**
     * 指定返回字段查询
     */

    @Test
    public void testQuery() throws Exception {GetRequest request = new GetRequest("test", "house", "4hN-OW4BpJzEX51oe-Of");

        // 指定返回字段
        String[] includes = new String[]{"title", "id"};
        String[] excludes = Strings.EMPTY_ARRAY;

        FetchSourceContext fetchSourceContext = new FetchSourceContext(true, includes, excludes);
        request.fetchSourceContext(fetchSourceContext);

        GetResponse response = this.client.get(request, RequestOptions.DEFAULT);
        System.out.println("数据 ->" + response);
    }

    /**
     * 判断是否存在
     */

    @Test
    public void testExists() throws Exception {GetRequest getRequest = new GetRequest("test", "house", "4hN-OW4BpJzEX51oe-Of");
        // 不返回字段
        getRequest.fetchSourceContext(new FetchSourceContext(false));
        boolean exists = this.client.exists(getRequest, RequestOptions.DEFAULT);
        System.out.println("exists ->" + exists);
    }

    /**
     * 删除数据
     */
    @Test
    public void testDelete() throws Exception {DeleteRequest deleteRequest = new DeleteRequest("test", "house", "4hN-OW4BpJzEX51oe-Of");
        DeleteResponse response = this.client.delete(deleteRequest, RequestOptions.DEFAULT);
        System.out.println(response.status());// OK or NOT_FOUND
    }

    /**
     * 更新数据
     */
    @Test
    public void testUpdate() throws Exception {UpdateRequest updateRequest = new UpdateRequest("test", "house", "4BN4OW4BpJzEX51o3-PZ");

        Map<String, Object> data = new HashMap<>();
        data.put("title", "南京西路 2 一室一厅 2");
        data.put("price", "4000");
        updateRequest.doc(data);
        UpdateResponse response = this.client.update(updateRequest, RequestOptions.DEFAULT);
        System.out.println("version ->" + response.getVersion());
    }

    /**
     * 查询数据
     */
    @Test
    public void testSearch() throws Exception {SearchRequest searchRequest = new SearchRequest("test");
        searchRequest.types("house");

        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.matchQuery("title", "拎包入住"));
        sourceBuilder.from(0);
        sourceBuilder.size(5);
        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        searchRequest.source(sourceBuilder);

        SearchResponse search = this.client.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println("搜索到 ->" + search.getHits().totalHits + "条数据");

        SearchHits hits = search.getHits();
        for (SearchHit hit : hits) {System.out.println(hit.getSourceAsString());
        }
    }

}

SpringBoot 整合 Elasticsearch

Spring Data 项目对 Elasticsearch 做了支持,其目的就是简化对 Elasticsearch 的操作,https://spring.io/projects/sp…。

1. 导入依赖

<?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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.example</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>elasticsearch</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>6.5.4</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.4</version>
        </dependency>

        <!--REST 高级客户端 -->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>6.5.4</version>
        </dependency>

        <!--SpringBoot 整合 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2. 编写 application.yml

spring:
  application:
    name: spring-elasticsearch
  data:
    elasticsearch:
      cluster-name: docker-cluster
      cluster-nodes: 47.101.129.45:9300

这里要注意,使用的端口是 9300,而并非 9200,原因是 9200 是 RESTful 端口,9300 是 API 端口。

ElasticSearch 之 ElasticsearchTemplate

3. 编写测试

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "demo", type = "person", createIndex = false)
public class Person {
    /**
     * 1. 索引库(indices)         indices 是 index 的复数,代表许多的索引,* 2. 类型(type)类型是模拟 mysql 中的 table 概念,一个索引库下可以有不同类型的索引,比如商品索引,订单索引,其数据格式不同。不过这会导致索引库混乱,因此未来版本中会移除这个概念
     * 3. 文档(document)存入索引库原始的数据。比如每一条商品信息,就是一个文档
     * 4. 字段(field)文档中的属性
     * 5. 映射配置(mappings)字段的数据类型、属性、是否索引、是否存储等特性
     */

    /**
     * @Document 作用在类,标记实体类为文档对象,一般有两个属性
     *      1.indexName:对应索引库名称
     *      2.type:对应在索引库中的类型
     *      3.shards:分片数量,默认 5
     *      4.replicas:副本数量,默认 1
     * @Id 作用在成员变量,标记一个字段作为 id 主键
     * @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:*      1.type:字段类型,是枚举:FieldType,可以是 text、long、short、date、integer、object 等
     *      2.text:存储数据时候,会自动分词,并生成索引
     *      3.keyword:存储数据时候,不会分词建立索引
     *      4.Numerical:数值类型,分两类
     *          基本数据类型:long、interger、short、byte、double、float、half_float
     *          浮点数的高精度类型:scaled_float
     *          需要指定一个精度因子,比如 10 或 100。elasticsearch 会把真实值乘以这个因子后存储,取出时再还原。*      5.Date:日期类型
     *          elasticsearch 可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为 long,节省空间。*      6.index:是否索引,布尔类型,默认是 true
     *      7.store:是否存储,布尔类型,默认是 false
     *      8.analyzer:分词器名称,这里的 ik_max_word 即使用 ik 分词器
     */
    @Id
    private Long id;

    @Field(store = true)
    private String name;

    @Field
    private Integer age;

    @Field
    private String mail;

    @Field(store = true)
    private String hobby;
}

1)新增数据

/**
 * Spring Data ElasticSearch
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class TestSpringBootES {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    /**
     * 添加数据
     */
    @Test
    public void save() {User user = new User();
        user.setId(1001L);
        user.setName("赵柳");
        user.setAge(20);
        user.setHobby("足球、篮球、听音乐");

        IndexQuery indexQuery = new IndexQueryBuilder()
                .withObject(user).build();

        String index = this.elasticsearchTemplate.index(indexQuery);

        System.out.println(index);
    }
}

2)批量插入

@Test
public void testBulk() {List list = new ArrayList<>();
    for (int i = 0; i < 5000; i++) {User person = new User();
        person.setId(1001L + i);
        person.setAge(i % 50 + 10);
        person.setName("张三" + i);
        person.setHobby("足球、篮球、听音乐");

        IndexQuery indexQuery = new IndexQueryBuilder().withObject(person).build();
        list.add(indexQuery);
    }
    Long start = System.currentTimeMillis();
    this.elasticsearchTemplate.bulkIndex(list);

    System.out.println("用时:" + (System.currentTimeMillis() - start));
}

3)局部更新,全部更新使用 index 覆盖即可

@Test
public void testUpdate() {IndexRequest indexRequest = new IndexRequest();
    indexRequest.source("age", "30");

    UpdateQuery updateQuery = new UpdateQueryBuilder()
            .withId("1002")
            .withClass(User.class)
            .withIndexRequest(indexRequest).build();

    UpdateResponse response = this.elasticsearchTemplate.update(updateQuery);

    System.out.println(response);

}

4)删除

@Test
public void testDelete() {String result = this.elasticsearchTemplate.delete(User.class, "1002");
    System.out.println(result);
}

5)查询

@Test
public void testSearch() {PageRequest pageRequest = PageRequest.of(0, 10);// 设置分页参数

    SearchQuery searchQuery = new NativeSearchQueryBuilder()
            .withQuery(QueryBuilders.matchQuery("name", "赵柳"))//match 查询
            .withPageable(pageRequest)
            .build();

    AggregatedPage<User> persons = this.elasticsearchTemplate.queryForPage(searchQuery, User.class);
    System.out.println("persons ->" + persons);
    System.out.println("总页数:" + persons.getTotalPages()); // 获取总页数

    List<User> content = persons.getContent();// 获取搜索到的数据
    for (User p : content) {System.out.println(p);
    }
}

ElasticSearch 之 ElasticsearchRepository

3. 编写测试
1)创建实体 Pojo

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "commodity", type = "docs", shards = 1, replicas = 0)
public class Commodity {
    @Id
    private Long id;

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title; // 标题

    @Field(type = FieldType.Keyword)
    private String category;// 分类

    @Field(type = FieldType.Keyword)
    private String brand; // 品牌

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

    @Field(index = false, type = FieldType.Keyword)
    private String images; // 图片地址
}

2)继承 ElasticsearchRepository

import com.example.elasticsearch.pojo.Commodity;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;

public interface CommodityRepository extends ElasticsearchRepository<Commodity, Long> {

}

3)测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class TestSpringBootES2 {

    @Resource
    private CommodityRepository commodityRepository;

    /**
     * 创建索引
     */
    @Test
    public void createIndex() {boolean index = elasticsearchTemplate.createIndex(Commodity.class);
        System.out.println(index);
    }
    
    /**
     * 添加数据
     */
    @Test
    public void testInsert() {
        Commodity commodity = new Commodity(1L, "小米手机 7", "手机",
                "小米", 3499.00, "http://image.baidu.com/13123.jpg");
        Commodity save = commodityRepository.save(commodity);
        System.out.println(save);
    }

}

退出移动版