Java中Elasticsearch的基本使用

最近工作中用到了es,看了些文档教程,简单总结一些。

总体上Java连接Elasticsearch有两种方法:

    1:RestClient,基于http协议,9200端口
    2:TransportClient,基于Tcp协议,9300端口

    目前Elasticsearch最新版本为7.3.1版本,第二种连接方法TransportClient已经被弃用,在7以后的版本都不在支持。官方推荐使用 RestClient。

Java中使用es主要有三种方式

     1.使用原生API

     2.使用RestClient

     3.使用Spring Data Elasticsearch

     spring data使用起来是最方便的,提供了ElasticsearchTemplate进行操作,并且有orm思想的实现,配置映射后操作实体即可,但可惜spring data 的封装是使用了TransportClient这种被弃用的方法,所以spring data 不支持7以后的版本。

为了使用框架,我选择了Elasticsearch 6.2.4版本。

ES下载地址:https://www.elastic.co/cn/downloads/past-releases#elasticsearch

版本兼容搭配

开始使用

首先搭建一个简单的springboot应用。

1.pom.xml引入依赖

<!-- elasticsearch 必须的-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>

<!-- lombok 不是必须的-->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>

lombok是为了少写代码,不是必须的,加入lombok的注解,可以自动帮我们加入get、set、构造方法、toString等。

2.application.propersites添加配置

spring.data.elasticsearch.cluster-name=my-application
spring.data.elasticsearch.cluster-nodes=127.0.0.1:9300

这个跟es的配置是有关系的,在es的config目录下,elasticsearch.yml文件中:

将以下四行(不是连续的)解除注释
    cluster.name: my-application
    node.name: node-1
    network.host: 0.0.0.0
    http.port: 9200

并添加:
    http.cors.enabled: true   //跨域 这是为了使用可视化工具 查看数据方便
    http.cors.allow-origin: "*"
    node.master: true
    node.data: true

如果启动闪退,日志提示:cluster.initial_master_nodes
还需要加:
    cluster.initial_master_nodes: ["node-1"]

3.编写配置实体类

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString   //上面四个注解是lombok的注解 会生成 get set 无参/全参构造 toString 

@Document(indexName = "article",type = "docs", shards = 1, replicas = 0)
public class Article {

    @Id
    private int id; //文章id

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

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String abstractText;  //文章摘要

    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String content; //文章内容

    @Field(index = false, type = FieldType.Keyword)
    private String createTime;  //创建时间
    

}
Spring Data通过注解来声明字段的映射属性,有下面的三个注解:

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

这段摘自:https://blog.csdn.net/chen_2890/article/details/83895646?tdsourcetag=s_pctim_aiomsg

4.编写Repository接口

跟jpa框架一样,让自己的接口继承一个定义了基本操作的接口,便可以直接进行操作。

                                                             //实体,主键类型
public interface ArticleEsDao extends ElasticsearchRepository<Article,Integer> {
}

大概的结构(上为父,下为子) 

Repository接口   无方法
       |
       |
CrudRepository接口  增加了 save count findAll findById delete deleteById deleteAll 等方法
       |
       |
PagingAndSortingRepository接口  增加了 分页的findAll 排序的findAll 方法
       |
       |
ElasticsearchCrudRepository接口  无增加方法
       |
       |
ElasticsearchRepository接口  增加了search getEntityClass 等方法 
    /           \
   /             \
我们的接口     AbstractElasticsearchRepository抽象类  为方法做实现

     AbstractElasticsearchRepository抽象类中,使用了ElasticsearchOperations接口做操作,这个接口的实现就是ElasticsearchTemplate,它里面使用的Client应该就是TransportClient,未来spring更新成RestClient才可以支持7版本以后的es,我们的接口底层使用ElasticsearchTemplate进行具体操作,我们也可以直接使用ElasticsearchTemplate进行操作。

4.开始使用

先把ES服务启动。

1.使用ElasticsearchTemplate操作

    @Autowired
    private ElasticsearchTemplate esTemplate; //内部有 queryForList() queryForObject() 等方法

    /**
     * 建立索引
     */
    @Test
    public void createIndex() {
        esTemplate.createIndex(Article.class);
    }

    /**
     * 删除索引
     */
    @Test
    public void deleteIndex() {
        esTemplate.deleteIndex(Article.class);
    }

    /**
     * 自定义条件查询
     */
    @Test
   public void search(){
        CriteriaQuery criteriaQuery = new CriteriaQuery(
                           new Criteria("title").is("北京")
                      .and(new Criteria("content").contains("北京")))
                      .setPageable(new PageRequest(0, 10))
                      .addSort(new Sort(new Sort.Order(Sort.Direction.DESC, "createTime")));
        List<Article> articles = esTemplate.queryForList(criteriaQuery, Article.class);

        show(articles);//自己定义的一个遍历打印小方法
   }

2.使用接口操作

    @Autowired
    private ArticleEsDao articleEsDao; //继承了ElasticsearchRepository的接口


    /**
     * 新插入或修改一条数据  id存在则是修改
     */
    @Test
    public void add() {
        Article article = new Article();
        article.setId(1);
        article.setTitle("文章标题1");
        article.setCreateTime("2019-9-5");
        article.setAbstractText("文章摘要1");
        article.setContent("文章内容1");

        articleEsDao.save(article);
    }

    /**
     * 查询全部
     */
    @Test
    public void queryAll(){

        // descending() 降序
        // ascending() 升序
        Iterable<Article> list = articleEsDao.findAll(Sort.by("createTime").ascending());

        show(list);
    }



    /**
     * 基本查询方法
     */
    @Test
    public void query(){

        Optional<Article> byId = articleEsDao.findById(1);
        Article article = byId.get();
        System.out.println(article);

        List<Article> list = articleEsDao.findByTitleLike("北京");
        show(list);

    }


    /**
     * 简单查询思路:
     * 创建一个 NativeSearchQueryBuilder queryBuilder 条件构建实体
     *
     * 使用其 withXXX 方法 添加 条件   withQuery() 添加查询条件  withSort() 添加排序条件  withPageable() 添加分页条件 等
     *
     * 其中查询条件由 QueryBuilders 中的静态方法生成  matchQuery(字段,查询关键字) 基本分词条件
     *                                             boolQuery().must()/mustNot() 布尔查询  must中传入matchQuery()
     *                                             fuzzyQuery() 模糊查询
     *
     *    排序条件由 SortBuilders中的静态方法生成 fieldSort(字段).order(升序/降序)
     *
     *    分页条件由 PageRequest 中的静态方法生成 of(page,size)
     *
     *    最后使用 queryBuilder.build() 得到最终条件
     *
     *    spring data es 的orm框架中的 dao 接口 调用 search方法 传入最终条件 得到结果
     */

    /**
     * 自定义查询
     */
    @Test
    public void matchQuery(){
        // 查询条件构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 向构建器中添加查询条件                   基本分词查询
       queryBuilder.withQuery(QueryBuilders.matchQuery("title", "北京"));
        // 搜索,获取结果                           传入构建的条件
        Page<Article> items = articleEsDao.search(queryBuilder.build());
        // 总条数
        long total = items.getTotalElements();
        System.out.println("total = " + total);
        show(items.getContent());
    }

    /**
     * 自定义查询并分页
     */
    @Test
    public void matchQueryPage(){
        // 查询条件构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();

        // 向构建器中添加查询条件                   基本分词查询
        queryBuilder.withQuery(QueryBuilders.matchQuery("title", "北京"));

        //排序
        queryBuilder.withSort(SortBuilders.fieldSort("createTime").order(SortOrder.ASC));

        queryBuilder.withPageable(PageRequest.of(0,3));  //page从0开始
        // 搜索,获取结果                           传入构建的条件
        Page<Article> items = articleEsDao.search(queryBuilder.build());
        // 总条数
        long total = items.getTotalElements();
        System.out.println("total = " + total);
        show(items.getContent());
    }


    /**
     * 布尔查询
     */
    @Test
    public void booleanQuery(){
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();

        builder.withQuery(             
                QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("title","北京"))
                .mustNot(QueryBuilders.matchQuery("content","北京"))
        );

        // 查找
        Page<Article> page = articleEsDao.search(builder.build());
        show(page);
    }

    /**
     *模糊查询
     */
    @Test
    public void fuzzyQuery(){
        NativeSearchQueryBuilder builder = new NativeSearchQueryBuilder();
        builder.withQuery(QueryBuilders.fuzzyQuery("title","北京"));
        Page<Article> page = articleEsDao.search(builder.build());
       show(page);
    }

聚合查询 

    /**
     * 这里以分组统计为例  统计某个字段中 每个值的出现次数  简单思路
     *
     * 在原来的查询构建时 使用addAggregation 添加一个聚合条件  设置聚合的名称 及需要聚合的字段
     * 方法:queryBuilder.addAggregation(AggregationBuilders.terms(聚合名).field(字段名)); //聚合类型为terms
     *
     * 使用原search方法获得查询结果 结果强转成 AggregatedPage<Entity> 类型
     *
     * 解析结果 使用aggPage.getAggregation(聚合名)获得结果 结果根据聚合条件强转 String类型字段 terms类型聚合  强转成 StringTerms类型
     *
     * 强转后使用getBuckets获得桶集合  遍历桶集合 对每一个桶 使用getKeyAsString获得key名称 也就是聚合字段的值  getDocCount获得每个字段值对应的数量
     *
     *
     */
    【以下例子来自:https://blog.csdn.net/chen_2890/article/details/83895646?tdsourcetag=s_pctim_aiomsg】
    @Test
    public void agg(){
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 不查询任何结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand
        queryBuilder.addAggregation(
                AggregationBuilders.terms("brands").field("brand"));
        // 2、查询,需要把结果强转为AggregatedPage类型
        AggregatedPage<Phone> aggPage = (AggregatedPage<Phone>) phoneDao.search(queryBuilder.build());

        // 3、解析
        // 3.1、从结果中取出名为brands的那个聚合,
        // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
        StringTerms agg = (StringTerms) aggPage.getAggregation("brands");
        // 3.2、获取桶
        List<StringTerms.Bucket> buckets = agg.getBuckets();
        // 3.3、遍历
        for (StringTerms.Bucket bucket : buckets) {
            // 3.4、获取桶中的key,即品牌名称
            System.out.println(bucket.getKeyAsString());
            // 3.5、获取桶中的文档数量
            System.out.println(bucket.getDocCount());
        }

    }

    /**
     * queryBuilder.addAggregation() 方法可选值
     */
    public void build(){

        //(1)统计某个字段的数量
        AggregationBuilders.count("count_uid").field("uid");

        //(2)去重统计某个字段的数量(有少量误差)
        AggregationBuilders.cardinality("distinct_count_uid").field("uid");

        //(3)聚合过滤
        AggregationBuilders.filter("uid_filter",QueryBuilders.queryStringQuery("uid:001"));

        //(4)按某个字段分组
        AggregationBuilders.terms("group_name").field("name");

        //(5)求和
        AggregationBuilders.sum("sum_price").field("price");

        //(6)求平均
        AggregationBuilders.avg("avg_price").field("price");

        //(7)求最大值
        AggregationBuilders.max("max_price").field("price");

        //(8)求最小值
        AggregationBuilders.min("min_price").field("price");

        //(9)按日期间隔分组
        AggregationBuilders.dateHistogram("dh").field("date");

        //(10)获取聚合里面的结果
        AggregationBuilders.topHits("top_result");

        //(11)嵌套的聚合
        AggregationBuilders.nested("negsted_path","quests");

        //(12)反转嵌套
        AggregationBuilders.reverseNested("res_negsted").path("kps ");


    }

另外接口中,也有jpa的一个特性,就是可以识别方法名findByXXX,比如:

public interface ArticleEsDao extends ElasticsearchRepository<Article,Integer> {

    //查询title字段like关键字的实体集合
    List<Article> findByTitleLike(String name); //框架根据方法名 生成方法体                    

}

可以识别的关键字还有 And、Or、Is、Not、Between、StartingWith、EndingWith等,只要按照约定的关键字书写就可以。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

leon9512

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值