美文网首页
SpringBoot整合elasticsearch

SpringBoot整合elasticsearch

作者: 任未然 | 来源:发表于2022-01-07 00:23 被阅读0次

    一. 概述

    参考开源项目https://github.com/xkcoding/spring-boot-demo
    此Demo简单集成elasticsearch,实现创建索引、配置映射、删除索引、增删改查基本操作

    二. 安装elasticsearch

    作者编写本demo时,ElasticSearch版本为 6.8.22,使用 docker 运行,下面是所有步骤:

    1. 下载镜像:docker pull elasticsearch:6.8.22

    2. 运行容器:docker run -d -p 9200:9200 -p 9300:9300 --name elasticsearch-6.8.22 elasticsearch:6.8.22

    1. 进入容器:docker exec -it elasticsearch-6.8.22 /bin/bash

    2. 安装 ik 分词器:./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.8.22/elasticsearch-analysis-ik-6.8.22.zip

    3. 修改 es 配置文件:vi ./config/elasticsearch.yml

      # 集群名字
      cluster.name: "docker-cluster"
      network.host: 0.0.0.0
      
      # minimum_master_nodes need to be explicitly set when bound on a public IP
      # set to 1 to allow single node clusters
      # Details: https://github.com/elastic/elasticsearch/pull/17288
      discovery.zen.minimum_master_nodes: 1
      
      # just for elasticsearch-head plugin
      http.cors.enabled: true
      http.cors.allow-origin: "*"
      
    4. 退出容器:exit

    5. 停止容器:docker stop elasticsearch-6.8.22

    6. 启动容器:docker start elasticsearch-6.8.22

    7. 测试http://127.0.0.1:9200/

    三. 搭建SpringBoot工程

    3.1 引入依赖

            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            </dependency>
    
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
    
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
    

    3.2 application.yml

    spring:
      data:
        elasticsearch:
          # 集群的名字
          cluster-name: docker-cluster
          # 逗号分隔的集群节点地址列表
          cluster-nodes: localhost:9300
    

    3.3 启动类

    @SpringBootApplication
    public class SpringBootDemoElasticsearchApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringBootDemoElasticsearchApplication.class, args);
        }
    }
    

    3.4 实体类: Person.java

    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;
    
    import java.util.Date;
    
    @Document(indexName = "person")
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Person {
        /**
         * 主键
         */
        @Id
        private Long id;
    
        /**
         * 名字(type:指定数据类型)
         */
        @Field(type = FieldType.Keyword)
        private String name;
    
        /**
         * 国家
         */
        @Field(type = FieldType.Keyword)
        private String country;
    
        /**
         * 年龄
         */
        @Field(type = FieldType.Integer)
        private Integer age;
    
        /**
         * 生日
         */
        @Field(type = FieldType.Date)
        private Date birthday;
    
        /**
         * 介绍(指定分词器:ik_smart)
         */
        @Field(type = FieldType.Text, analyzer = "ik_smart")
        private String remark;
    }
    

    3.5 持久层: PersonRepository.java

    public interface PersonRepository extends ElasticsearchRepository<Person, Long> {
        /**
         * 根据年龄区间查询
         *
         * @param min 最小值
         * @param max 最大值
         * @return 满足条件的用户列表
         */
        List<Person> findByAgeBetween(Integer min, Integer max);
    }
    

    可以通过关键字直接声明方法, 不用写实现

    关键字 使用示例 等同于的ES查询
    And findByNameAndPrice {“bool” : {“must” : [ {“field” : {“name” : “?”}}, {“field” : {“price” : “?”}} ]}}
    Or findByNameOrPrice {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“price” : “?”}} ]}}
    Is findByName {“bool” : {“must” : {“field” : {“name” : “?”}}}}
    Not findByNameNot {“bool” : {“must_not” : {“field” : {“name” : “?”}}}}
    Between findByPriceBetween {“bool” : {“must” : {“range” : {“price” : {“from” : ?,”to” : ?,”include_lower” : true,”include_upper” : true}}}}}
    LessThanEqual findByPriceLessThan {“bool” : {“must” : {“range” : {“price” : {“from” : null,”to” : ?,”include_lower” : true,”include_upper” : true}}}}}
    GreaterThanEqual findByPriceGreaterThan {“bool” : {“must” : {“range” : {“price” : {“from” : ?,”to” : null,”include_lower” : true,”include_upper” : true}}}}}
    Before findByPriceBefore {“bool” : {“must” : {“range” : {“price” : {“from” : null,”to” : ?,”include_lower” : true,”include_upper” : true}}}}}
    After findByPriceAfter {“bool” : {“must” : {“range” : {“price” : {“from” : ?,”to” : null,”include_lower” : true,”include_upper” : true}}}}}
    Like findByNameLike {“bool” : {“must” : {“field” : {“name” : {“query” : “? *”,”analyze_wildcard” : true}}}}}
    StartingWith findByNameStartingWith {“bool” : {“must” : {“field” : {“name” : {“query” : “? *”,”analyze_wildcard” : true}}}}}
    EndingWith findByNameEndingWith {“bool” : {“must” : {“field” : {“name” : {“query” : “*?”,”analyze_wildcard” : true}}}}}
    Contains/Containing findByNameContaining {“bool” : {“must” : {“field” : {“name” : {“query” : “?”,”analyze_wildcard” : true}}}}}
    In findByNameIn(Collectionnames) {“bool” : {“must” : {“bool” : {“should” : [ {“field” : {“name” : “?”}}, {“field” : {“name” : “?”}} ]}}}}
    NotIn findByNameNotIn(Collectionnames) {“bool” : {“must_not” : {“bool” : {“should” : {“field” : {“name” : “?”}}}}}}
    True findByAvailableTrue {“bool” : {“must” : {“field” : {“available” : true}}}}
    False findByAvailableFalse {“bool” : {“must” : {“field” : {“available” : false}}}}
    OrderBy findByAvailableTrueOrderByNameDesc {“sort” : [{ “name” : {“order” : “desc”} }],”bool” : {“must” : {“field” : {“available” : true}}}}

    3.6 测试

    1. 索引创建与删除

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class TemplateTest{
        @Autowired
        private ElasticsearchTemplate esTemplate;
    
        /**
         * 测试 ElasticTemplate 创建 index
         */
        @Test
        public void testCreateIndex() {
            // 创建索引,会根据Item类的@Document注解信息来创建
            esTemplate.createIndex(Person.class);
    
            // 配置映射,会根据Item类中的id、Field等字段来自动完成映射
            esTemplate.putMapping(Person.class);
        }
    
        /**
         * 测试 ElasticTemplate 删除 index
         */
        @Test
        public void testDeleteIndex() {
            esTemplate.deleteIndex(Person.class);
        }
    }
    

    2. CURD

    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class PersonRepositoryTest {
        @Autowired
        private PersonRepository repo;
    
        /**
         * 测试新增
         */
        @Test
        public void save() {
            Person person = new Person(1L, "刘备", "蜀国", 18, DateUtil.parse("1990-01-02 03:04:05"), "刘备(161年-223年6月10日),即汉昭烈帝(221年-223年在位),又称先主,字玄德,东汉末年幽州涿郡涿县(今河北省涿州市)人,西汉中山靖王刘胜之后,三国时期蜀汉开国皇帝、政治家。\n刘备少年时拜卢植为师;早年颠沛流离,备尝艰辛,投靠过多个诸侯,曾参与镇压黄巾起义。先后率军救援北海相孔融、徐州牧陶谦等。陶谦病亡后,将徐州让与刘备。赤壁之战时,刘备与孙权联盟击败曹操,趁势夺取荆州。而后进取益州。于章武元年(221年)在成都称帝,国号汉,史称蜀或蜀汉。《三国志》评刘备的机权干略不及曹操,但其弘毅宽厚,知人待士,百折不挠,终成帝业。刘备也称自己做事“每与操反,事乃成尔”。\n章武三年(223年),刘备病逝于白帝城,终年六十三岁,谥号昭烈皇帝,庙号烈祖,葬惠陵。后世有众多文艺作品以其为主角,在成都武侯祠有昭烈庙为纪念。");
            Person save = repo.save(person);
            log.info("【save】= {}", save);
        }
    
        /**
         * 测试批量新增
         */
        @Test
        public void saveList() {
            List<Person> personList = Lists.newArrayList();
            personList.add(new Person(2L, "曹操", "魏国", 20, DateUtil.parse("1988-01-02 03:04:05"), "曹操(155年-220年3月15日),字孟德,一名吉利,小字阿瞒,沛国谯县(今安徽亳州)人。东汉末年杰出的政治家、军事家、文学家、书法家,三国中曹魏政权的奠基人。\n曹操曾担任东汉丞相,后加封魏王,奠定了曹魏立国的基础。去世后谥号为武王。其子曹丕称帝后,追尊为武皇帝,庙号太祖。\n东汉末年,天下大乱,曹操以汉天子的名义征讨四方,对内消灭二袁、吕布、刘表、马超、韩遂等割据势力,对外降服南匈奴、乌桓、鲜卑等,统一了中国北方,并实行一系列政策恢复经济生产和社会秩序,扩大屯田、兴修水利、奖励农桑、重视手工业、安置流亡人口、实行“租调制”,从而使中原社会渐趋稳定、经济出现转机。黄河流域在曹操统治下,政治渐见清明,经济逐步恢复,阶级压迫稍有减轻,社会风气有所好转。曹操在汉朝的名义下所采取的一些措施具有积极作用。\n曹操军事上精通兵法,重贤爱才,为此不惜一切代价将看中的潜能分子收于麾下;生活上善诗歌,抒发自己的政治抱负,并反映汉末人民的苦难生活,气魄雄伟,慷慨悲凉;散文亦清峻整洁,开启并繁荣了建安文学,给后人留下了宝贵的精神财富,鲁迅评价其为“改造文章的祖师”。同时曹操也擅长书法,唐朝张怀瓘在《书断》将曹操的章草评为“妙品”。"));
            personList.add(new Person(3L, "孙权", "吴国", 19, DateUtil.parse("1989-01-02 03:04:05"), "孙权(182年-252年5月21日),字仲谋,吴郡富春(今浙江杭州富阳区)人。三国时代孙吴的建立者(229年-252年在位)。\n孙权的父亲孙坚和兄长孙策,在东汉末年群雄割据中打下了江东基业。建安五年(200年),孙策遇刺身亡,孙权继之掌事,成为一方诸侯。建安十三年(208年),与刘备建立孙刘联盟,并于赤壁之战中击败曹操,奠定三国鼎立的基础。建安二十四年(219年),孙权派吕蒙成功袭取刘备的荆州,使领土面积大大增加。\n黄武元年(222年),孙权被魏文帝曹丕册封为吴王,建立吴国。同年,在夷陵之战中大败刘备。黄龙元年(229年),在武昌正式称帝,国号吴,不久后迁都建业。孙权称帝后,设置农官,实行屯田,设置郡县,并继续剿抚山越,促进了江南经济的发展。在此基础上,他又多次派人出海。黄龙二年(230年),孙权派卫温、诸葛直抵达夷州。\n孙权晚年在继承人问题上反复无常,引致群下党争,朝局不稳。太元元年(252年)病逝,享年七十一岁,在位二十四年,谥号大皇帝,庙号太祖,葬于蒋陵。\n孙权亦善书,唐代张怀瓘在《书估》中将其书法列为第三等。"));
            personList.add(new Person(4L, "诸葛亮", "蜀国", 16, DateUtil.parse("1992-01-02 03:04:05"), "诸葛亮(181年-234年10月8日),字孔明,号卧龙,徐州琅琊阳都(今山东临沂市沂南县)人,三国时期蜀国丞相,杰出的政治家、军事家、外交家、文学家、书法家、发明家。\n早年随叔父诸葛玄到荆州,诸葛玄死后,诸葛亮就在襄阳隆中隐居。后刘备三顾茅庐请出诸葛亮,联孙抗曹,于赤壁之战大败曹军。形成三国鼎足之势,又夺占荆州。建安十六年(211年),攻取益州。继又击败曹军,夺得汉中。蜀章武元年(221年),刘备在成都建立蜀汉政权,诸葛亮被任命为丞相,主持朝政。蜀后主刘禅继位,诸葛亮被封为武乡侯,领益州牧。勤勉谨慎,大小政事必亲自处理,赏罚严明;与东吴联盟,改善和西南各族的关系;实行屯田政策,加强战备。前后六次北伐中原,多以粮尽无功。终因积劳成疾,于蜀建兴十二年(234年)病逝于五丈原(今陕西宝鸡岐山境内),享年54岁。刘禅追封其为忠武侯,后世常以武侯尊称诸葛亮。东晋政权因其军事才能特追封他为武兴王。\n诸葛亮散文代表作有《出师表》《诫子书》等。曾发明木牛流马、孔明灯等,并改造连弩,叫做诸葛连弩,可一弩十矢俱发。诸葛亮一生“鞠躬尽瘁、死而后已”,是中国传统文化中忠臣与智者的代表人物。"));
            Iterable<Person> people = repo.saveAll(personList);
            log.info("【people】= {}", people);
        }
    
        /**
         * 测试更新
         */
        @Test
        public void update() {
            repo.findById(1L).ifPresent(person -> {
                person.setRemark(person.getRemark() + "\n更新更新更新更新更新");
                Person save = repo.save(person);
                log.info("【save】= {}", save);
            });
        }
    
        /**
         * 测试删除
         */
        @Test
        public void delete() {
            // 主键删除
            repo.deleteById(1L);
    
            // 对象删除
            repo.findById(2L).ifPresent(person -> repo.delete(person));
    
            // 批量删除
            repo.deleteAll(repo.findAll());
        }
    
        /**
         * 测试普通查询,按生日倒序
         */
        @Test
        public void select() {
            repo.findAll(Sort.by(Sort.Direction.DESC, "birthday")).forEach(person -> log.info("{} 生日: {}", person.getName(), DateUtil.formatDateTime(person.getBirthday())));
        }
    
        /**
         * 自定义查询,根据年龄范围查询
         */
        @Test
        public void customSelectRangeOfAge() {
            repo.findByAgeBetween(18, 19).forEach(person -> log.info("{} 年龄: {}", person.getName(), person.getAge()));
        }
    }
    

    3. 高级查询

    @Slf4j
    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class PersonRepositoryTest {
        @Autowired
        private PersonRepository repo;
        /**
         * 多条件组合查询
         * QueryBuilders.boolQuery()
         * QueryBuilders.boolQuery().must();//文档必须完全匹配条件,相当于and
         * QueryBuilders.boolQuery().mustNot();//文档必须不匹配条件,相当于not
         * QueryBuilders.boolQuery().should();//至少满足一个条件,这个文档就符合should,相当于or
         */
        @Test
        public void multiConditionSelect(){
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
            queryBuilder
                // 查询条件
                .withQuery(QueryBuilders.boolQuery()
                    .must(QueryBuilders.matchQuery("remark","曹操"))
                    .must(QueryBuilders.matchQuery("age","20")))
                // 排序
                .withSort(SortBuilders.fieldSort("age").order(SortOrder.DESC))
                // 分页
                .withPageable(PageRequest.of(0, 2));
            Page<Person> people = repo.search(queryBuilder.build());
            log.info("【people】总条数 = {}", people.getTotalElements());
            log.info("【people】总页数 = {}", people.getTotalPages());
            people.forEach(person -> log.info("【person】= {},年龄 = {}", person.getName(), person.getAge()));
        }
    
        /**
         * 测试聚合,测试平均年龄
         */
        @Test
        public void agg() {
            // 构造查询条件
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
            // 不查询任何结果
            queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
    
            // 平均年龄
            queryBuilder.addAggregation(AggregationBuilders.avg("avg").field("age"));
    
            log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build()));
    
            AggregatedPage<Person> people = (AggregatedPage<Person>) repo.search(queryBuilder.build());
            double avgAge = ((InternalAvg) people.getAggregation("avg")).getValue();
            log.info("【avgAge】= {}", avgAge);
        }
    
        /**
         * 测试高级聚合查询,每个国家的人有几个,每个国家的平均年龄是多少
         */
        @Test
        public void advanceAgg() {
            // 构造查询条件
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
            // 不查询任何结果
            queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
    
            // 1. 添加一个新的聚合,聚合类型为terms,聚合名称为country,聚合字段为age
            queryBuilder.addAggregation(AggregationBuilders.terms("country").field("country")
                // 2. 在国家聚合桶内进行嵌套聚合,求平均年龄
                .subAggregation(AggregationBuilders.avg("avg").field("age")));
    
            log.info("【queryBuilder】= {}", JSONUtil.toJsonStr(queryBuilder.build()));
    
            // 3. 查询
            AggregatedPage<Person> people = (AggregatedPage<Person>) repo.search(queryBuilder.build());
    
            // 4. 解析
            // 4.1. 从结果中取出名为 country 的那个聚合,因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型
            StringTerms country = (StringTerms) people.getAggregation("country");
            // 4.2. 获取桶
            List<StringTerms.Bucket> buckets = country.getBuckets();
            for (StringTerms.Bucket bucket : buckets) {
                // 4.3. 获取桶中的key,即国家名称  4.4. 获取桶中的文档数量
                log.info("{} 总共有 {} 人", bucket.getKeyAsString(), bucket.getDocCount());
                // 4.5. 获取子聚合结果:
                InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("avg");
                log.info("平均年龄:{}", avg);
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:SpringBoot整合elasticsearch

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