SpringBoot整合ElasticSearch

环境

ElasticSearch 。

如果使用 docker-compose 部署 ElasticSearch 集群,可以参考我的这篇文章 docker-compose部署ElasticSearch集群

依赖

pom.xml 依赖文件如下:springboot 版本是 2.4.0.RELEASE 。

xml 复制代码
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- elasticsearch starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        <version>2.4.3</version>
    </dependency>

    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.47</version>
    </dependency>

</dependencies>

配置

我这里采用 Java 配置和配置文件相结合的方式进行配置,创建配置类如下:

java 复制代码
/**
 * es配置类
 *
 * @author Leo
 * @create 2021/3/2 10:51
 **/
@Configuration
public class ElasticsearchConfig {

    @Value("${elasticsearch.schema}")
    private String schema;
    @Value("${elasticsearch.address}")
    private String address;
    @Value("${elasticsearch.connectTimeout}")
    private int connectTimeout;
    @Value("${elasticsearch.socketTimeout}")
    private int socketTimeout;
    @Value("${elasticsearch.connectionRequestTimeout}")
    private int tryConnTimeout;
    @Value("${elasticsearch.maxConnectNum}")
    private int maxConnNum;
    @Value("${elasticsearch.maxConnectPerRoute}")
    private int maxConnectPerRoute;
    @Value("${elasticsearch.userName}")
    private String userName;
    @Value("${elasticsearch.password}")
    private String password;

    /**
     * Es Rest客户端配置
     * @return
     */
    @Bean
    public RestHighLevelClient restHighLevelClient() {
        //认证信息
        final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));

        //解析地址
        List<HttpHost> hostLists = new ArrayList<>();
        String[] hostList = address.split(",");
        for (String addr : hostList) {
            String host = addr.split(":")[0];
            String port = addr.split(":")[1];
            hostLists.add(new HttpHost(host, Integer.parseInt(port), schema));
        }

        HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
        RestClientBuilder builder = RestClient.builder(httpHost);
        //连接超时配置
        builder.setRequestConfigCallback(requestConfigBuilder -> {
            requestConfigBuilder.setConnectTimeout(connectTimeout);
            requestConfigBuilder.setSocketTimeout(socketTimeout);
            requestConfigBuilder.setConnectionRequestTimeout(tryConnTimeout);
            return requestConfigBuilder;
        });
        //连接数、认证信息配置
        builder.setHttpClientConfigCallback(httpClientBuilder -> {
            httpClientBuilder.setMaxConnTotal(maxConnNum);
            httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);
            httpClientBuilder.setDefaultCredentialsProvider(credentialsProvider);
            return httpClientBuilder;
        });
        return new RestHighLevelClient(builder);
    }
}

配置文件application.yml内容如下:

yaml 复制代码
spring:
  application:
    name: hello-elasticsearch

elasticsearch:
  schema: http
  address: localhost:9200
  connectTimeout: 5000
  socketTimeout: 5000
  connectionRequestTimeout: 1000
  maxConnectNum: 50
  maxConnectPerRoute: 10
  userName:
  password:

测试

创建一个实体类

该类对应我们存储进 ES 的数据结构。这里用 Book 做示例

java 复制代码
/**
 * @author Leo
 * @create 2021/6/9 14:58
 **/
@Data
@Accessors(chain = true)
@Document(indexName = "books")
public class Book {
    @Id
    private String id;

    @Field(type = FieldType.Text)
    private String name;

    @Field(type = FieldType.Text)
    private String summary;

    @Field(type = FieldType.Integer)
    private Integer price;

    @Field(type = FieldType.Date, format = DateFormat.date)
    private LocalDate publishDate;
}

我们可以通过注解绑定对象和 ES 中的 index ,以及定义字段的类型等。

创建DAO类

该类和我们经常定义的访问数据库的类一样,用于定义访问 ES 的接口。

java 复制代码
/**
 * @author Leo
 * @create 2021/6/9 15:02
 **/
@Repository
public interface BookRepository extends ElasticsearchRepository<Book, String> {
    /**
     * 计算特定name数量
     * @param name
     * @return
     */
    long countByName(String name);

    /**
     * 通过name查询 也可以用 List 接收
     * @param name
     * @return
     */
    Iterable<Book> findByName(String name);

    /**
     * 通过name查询,带分页
     * @param name
     * @param pageable
     * @return
     */
    Page<Book> findByName(String name, Pageable pageable);

    /**
     * 模糊查询(忽略大小写)
     * @param name
     * @return
     */
    List<Book> findByNameLike(String name);

    /**
     * 查询价格低于特定值的记录
     * @param price
     * @return
     */
    Stream<Book> findByPriceLessThan(Integer price);

    /**
     * 异步查询
     * 返回类型可以是:Future/CompletableFuture/ListenableFuture
     * @param name
     * @return
     */
    @Async
    CompletableFuture<Book> findOneByName(String name);
}

我们不需要实现该接口,就可以操作 ElasticSearch,因为如果我们按照一定的规则定义方法名,参数,返回值类型等,Spring Data 整合 ElasticSearch 模块会自动解析方法含义(可以看到我们的 BookRepository 继承了 ElasticsearchRepository),底层通过 RestHighLevelClient 访问 ES 。完整规则:Spring官方文档

创建测试类,测试对应方法

java 复制代码
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class HelloElasticsearchApplicationTests {

    @Resource
    private BookRepository bookRepository;

    /**
     * 新增
     */
    @Test
    public void testSave() {
        bookRepository.save(new Book()
//                .setId("1")
                .setName("jvm GC原理")
                .setPrice(78)
                .setPublishDate(LocalDate.of(2015, 7, 23))
                .setSummary("详细讲解Java 虚拟机垃圾回收机制及其原理"));
    }

    /**
     * 批量新增
     */
    @Test
    public void testSaveAll() {
        List<Book> books = new ArrayList<>();
        for(int i = 0; i < 20; i++) {
            books.add(new Book()
                    .setId(String.valueOf(i))
                    .setName("Book" + i)
                    .setPrice(new Random().nextInt(200))
                    .setPublishDate(LocalDate.now())
                    .setSummary(String.valueOf(i)));
        }
        bookRepository.saveAll(books);

    }

    /**
     * 查询单条,查询所有,查询记录数量
     */
    @Test
    public void testFind() {
//        bookRepository.findById("1").ifPresent(book -> log.info(book.toString()));
        log.info("count of books : " + bookRepository.count());
//        bookRepository.findById("2").ifPresent(book -> log.info(book.toString()));
        bookRepository.findAll().forEach(book -> log.info(book.toString()));
    }

    /**
     * 测试分页和排序
     */
    @Test
    public void testPage() {
        bookRepository.findAll(PageRequest.of(0, 10, Sort.by("price").descending()))
                .forEach(book -> log.info(book.toString()));
    }

    /**
     * 删除
     */
    @Test
    public void testDelete() {
        bookRepository.deleteById("0");
//        bookRepository.deleteAll();
    }

    /**
     * 指定条件计数
     */
    @Test
    public void testCount() {
        String name = "深入理解JVM";
        log.info("count of book 《" + name + "》 : " + bookRepository.countByName(name));
    }

    @Test
    public void testFindByName() {
        String name = "深入理解JVM";
        bookRepository.findByName(name).forEach(book -> log.info(book.toString()));
    }

    @Test
    public void testFindByNamePageable() {
        String name = "深入理解JVM";
        bookRepository.findByName(name, PageRequest.of(0, 1)).forEach(book -> log.info(book.toString()));
    }

    /**
     * 模糊查询
     */
    @Test
    public void testFindByNameLike() {
        String name = "JVM";
        bookRepository.findByNameLike(name).forEach(book -> log.info(book.toString()));
    }

    /**
     * 比较查询
     */
    @Test
    public void testFindByPriceLessThan() {
        int price = 100;
        bookRepository.findByPriceLessThan(price).forEach(book -> log.info(book.toString()));
    }

    /**
     * 异步执行
     */
    @Test
    public void testAsync() {
        String name = "深入理解JVM";
        CompletableFuture<Book> future = bookRepository.findOneByName(name);
        future.whenComplete((book, throwable) -> {
            if (throwable != null) {
                log.error("error! reason : ", throwable);
            } else {
                log.info("Async query get Result: " + book.toString());
            }
        });
    }
}

如果安装了 Kibana 的话,我们可以进入 Kibana 后台 http://localhost:5601/app/home#/,通过 Kibana Dev Tools 中的 Console 界面来方便查询数据。

或者通过 Kibana Discover 界面以列表的形式查看所有数据。

相关推荐
星空5 小时前
Springboot复习
java·spring boot·spring
wno7047 小时前
Spring Boot JdbcTemplate配置Druid多数据源
java·spring boot·后端
wear工程师7 小时前
Spring Boot 服务挂了却没重启?先分清 systemd 的三次判断
spring boot
旺仔学长 哈哈8 小时前
52486|高校专业评估不再靠表格:Spring Boot 本科专业质量评估管理系统实战
数据库·spring boot·毕业设计·教育管理
weixin_BYSJ19879 小时前
springboot小区物业管理小程序---附源码45555
java·javascript·spring boot·python·django·flask·php
RuoyiOffice9 小时前
抓包还能看到密码?Vue3 + Spring Boot 3 接口 AES/RSA 加解密(@ApiEncrypt)全链路实战
spring boot·vue3·aes·rsa·接口安全·spring boot 3·apiencrypt
凤山老林9 小时前
零停机数据库演进:Spring Boot 集成 Flyway 与平滑DDL变更策略
数据库·spring boot·后端
抓哇小菜鸡9 小时前
Spring Boot + 本地大模型(Ollama/DeepSeek) + MyBatis-Plus 企业级智能体数据分析系统从零到一源码全解析
spring boot·后端·mybatis
趴下吧9 小时前
spring boot启动流程总结
java·spring boot·spring
码农进化录9 小时前
Java 程序员的 AI 进化论 | AI 帮我做 MySQL 表结构升级,三天活变半天
java·spring boot·openai