|
| 1 | +# SpringBoot整合Elasticsearch+IK |
| 2 | + |
| 3 | +支持作者就star一下,谢谢 🎉🎉 |
| 4 | + |
| 5 | +ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。 |
| 6 | + |
| 7 | +Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。 |
| 8 | + |
| 9 | +**安装相关软件** |
| 10 | +| 软件名称 | 软件版本 | 下载地址 | |
| 11 | +|:--|:--|:--| |
| 12 | +| Elasticsearch | 6.2.4 |[elasticsearch官网下载](https://www.elastic.co/cn/downloads/past-releases) | |
| 13 | +| IK中文分词器| 6.2.4 |[ik分词器官网下载](https://github.com/medcl/elasticsearch-analysis-ik/releases) | |
| 14 | +| kibana| 6.2.4 |[kibana官网下载](https://www.elastic.co/cn/downloads/past-releases) | |
| 15 | + |
| 16 | +>如果实在找不到,那么群文件夹:957406675 |
| 17 | +
|
| 18 | +安装分词插件,分别解压下载好的三个文件,然后解压IK,复制到`elasticsearch`安装目录下的plugins文件夹中。 |
| 19 | + |
| 20 | +运行`elasticsearch/bin/elasticsearch.bat`文件,浏览器访问: http://localhost:9200/ 会得到相应的版本信息,如: |
| 21 | +```json |
| 22 | +{ |
| 23 | + "name": "Bb-td48", |
| 24 | + "cluster_name": "elasticsearch", |
| 25 | + "cluster_uuid": "_IM0iQAeToWALU0tq7rsZQ", |
| 26 | + "version": { |
| 27 | + "number": "6.2.4", |
| 28 | + "build_hash": "ccec39f", |
| 29 | + "build_date": "2018-04-12T20:37:28.497551Z", |
| 30 | + "build_snapshot": false, |
| 31 | + "lucene_version": "7.2.1", |
| 32 | + "minimum_wire_compatibility_version": "5.6.0", |
| 33 | + "minimum_index_compatibility_version": "5.0.0" |
| 34 | + }, |
| 35 | + "tagline": "You Know, for Search" |
| 36 | +} |
| 37 | +``` |
| 38 | +然后再运行`kibana/bin/kibana.bat`文件,浏览器访问:http://localhost:5601 ,可以看到kibana的控制台页面。 |
| 39 | + |
| 40 | +到此相关软件安装完成,下面开始springboot整合elasticsearch。 |
| 41 | +# 开始整合 |
| 42 | +依赖 |
| 43 | +```xml |
| 44 | + <dependency> |
| 45 | + <groupId>org.springframework.boot</groupId> |
| 46 | + <artifactId>spring-boot-starter-data-elasticsearch</artifactId> |
| 47 | + </dependency> |
| 48 | + 还有lombok,自己加一下 |
| 49 | +``` |
| 50 | +application.yml |
| 51 | +```yml |
| 52 | +spring: |
| 53 | + data: |
| 54 | + elasticsearch: |
| 55 | + cluster-name: elasticsearch |
| 56 | + cluster-nodes: 127.0.0.1:9300 |
| 57 | +``` |
| 58 | +实体类 |
| 59 | +```java |
| 60 | +@Data |
| 61 | +@AllArgsConstructor |
| 62 | +@NoArgsConstructor |
| 63 | +@Document(indexName = "item", type = "docs", shards = 1, replicas = 0) |
| 64 | +public class Item { |
| 65 | + @Id |
| 66 | + private Long id; |
| 67 | + |
| 68 | + @Field(type = FieldType.Text, analyzer = "ik_max_word") |
| 69 | + private String title; //标题 |
| 70 | + |
| 71 | + @Field(type = FieldType.Keyword) |
| 72 | + private String category;// 分类 |
| 73 | + |
| 74 | + @Field(type = FieldType.Keyword) |
| 75 | + private String brand; // 品牌 |
| 76 | + |
| 77 | + @Field(type = FieldType.Double) |
| 78 | + private Double price; // 价格 |
| 79 | + |
| 80 | + @Field(index = false, type = FieldType.Keyword) |
| 81 | + private String images; // 图片地址 |
| 82 | +} |
| 83 | +``` |
| 84 | +Spring Data通过注解来声明字段的映射属性,有下面的三个注解: |
| 85 | + |
| 86 | +- `@Document` 作用在类,标记实体类为文档对象,一般有两个属性 |
| 87 | + - indexName:对应索引库名称 |
| 88 | + - type:对应在索引库中的类型 |
| 89 | + - shards:分片数量,默认5 |
| 90 | + - replicas:副本数量,默认1 |
| 91 | +- `@Id` 作用在成员变量,标记一个字段作为id主键 |
| 92 | +- `@Field` 作用在成员变量,标记为文档的字段,并指定字段映射属性: |
| 93 | + - type:字段类型,是是枚举:FieldType |
| 94 | + - index:是否索引,布尔类型,默认是true |
| 95 | + - store:是否存储,布尔类型,默认是false |
| 96 | + - analyzer:分词器名称 |
| 97 | + |
| 98 | +repository |
| 99 | +```java |
| 100 | +public interface ItemRepository extends ElasticsearchRepository<Item, Long> { |
| 101 | + |
| 102 | + /** |
| 103 | + * 根据价格区间查询 |
| 104 | + * |
| 105 | + * @param price1 |
| 106 | + * @param price2 |
| 107 | + * @return |
| 108 | + */ |
| 109 | + List<Item> findByPriceBetween(double price1, double price2); |
| 110 | +} |
| 111 | +``` |
| 112 | +**下面的测试类是重点** |
| 113 | +```java |
| 114 | +@RunWith(SpringRunner.class) |
| 115 | +@SpringBootTest |
| 116 | +public class SpringbootElasticsearchApplicationTests { |
| 117 | + |
| 118 | + @Autowired |
| 119 | + private ElasticsearchTemplate elasticsearchTemplate; |
| 120 | + |
| 121 | + @Autowired |
| 122 | + private ItemRepository itemRepository; |
| 123 | + |
| 124 | + /** |
| 125 | + * 创建索引 |
| 126 | + */ |
| 127 | + @Test |
| 128 | + public void createIndex() { |
| 129 | + // 创建索引,会根据Item类的@Document注解信息来创建 |
| 130 | + elasticsearchTemplate.createIndex(Item.class); |
| 131 | + // 配置映射,会根据Item类中的id、Field等字段来自动完成映射 |
| 132 | + elasticsearchTemplate.putMapping(Item.class); |
| 133 | + } |
| 134 | + |
| 135 | + /** |
| 136 | + * 删除索引 |
| 137 | + */ |
| 138 | + @Test |
| 139 | + public void deleteIndex() { |
| 140 | + elasticsearchTemplate.deleteIndex("item"); |
| 141 | + } |
| 142 | + |
| 143 | + /** |
| 144 | + * 新增 |
| 145 | + */ |
| 146 | + @Test |
| 147 | + public void insert() { |
| 148 | + Item item = new Item(1L, "小米手机7", "手机", "小米", 2999.00, "https://img12.360buyimg.com/n1/s450x450_jfs/t1/14081/40/4987/124705/5c371b20E53786645/c1f49cd69e6c7e6a.jpg"); |
| 149 | + itemRepository.save(item); |
| 150 | + } |
| 151 | + |
| 152 | + /** |
| 153 | + * 批量新增 |
| 154 | + */ |
| 155 | + @Test |
| 156 | + public void insertList() { |
| 157 | + List<Item> list = new ArrayList<>(); |
| 158 | + list.add(new Item(2L, "坚果手机R1", "手机", "锤子", 3999.00, "https://img12.360buyimg.com/n1/s450x450_jfs/t1/14081/40/4987/124705/5c371b20E53786645/c1f49cd69e6c7e6a.jpg")); |
| 159 | + list.add(new Item(3L, "华为META20", "手机", "华为", 4999.00, "https://img12.360buyimg.com/n1/s450x450_jfs/t1/14081/40/4987/124705/5c371b20E53786645/c1f49cd69e6c7e6a.jpg")); |
| 160 | + list.add(new Item(4L, "iPhone X", "手机", "iPhone", 5100.00, "https://img12.360buyimg.com/n1/s450x450_jfs/t1/14081/40/4987/124705/5c371b20E53786645/c1f49cd69e6c7e6a.jpg")); |
| 161 | + list.add(new Item(5L, "iPhone XS", "手机", "iPhone", 5999.00, "https://img12.360buyimg.com/n1/s450x450_jfs/t1/14081/40/4987/124705/5c371b20E53786645/c1f49cd69e6c7e6a.jpg")); |
| 162 | + // 接收对象集合,实现批量新增 |
| 163 | + itemRepository.saveAll(list); |
| 164 | + } |
| 165 | + |
| 166 | + /** |
| 167 | + * 修改 |
| 168 | + * |
| 169 | + * :修改和新增是同一个接口,区分的依据就是id,这一点跟我们在页面发起PUT请求是类似的。 |
| 170 | + */ |
| 171 | + |
| 172 | + /** |
| 173 | + * 删除所有 |
| 174 | + */ |
| 175 | + @Test |
| 176 | + public void delete() { |
| 177 | + itemRepository.deleteAll(); |
| 178 | + } |
| 179 | + |
| 180 | + /** |
| 181 | + * 基本查询 |
| 182 | + */ |
| 183 | + @Test |
| 184 | + public void query() { |
| 185 | + // 查询全部,并按照价格降序排序 |
| 186 | + Iterable<Item> items = itemRepository.findAll(Sort.by("price").descending()); |
| 187 | + items.forEach(item -> System.out.println("item = " + item)); |
| 188 | + } |
| 189 | + |
| 190 | + /** |
| 191 | + * 自定义方法 |
| 192 | + */ |
| 193 | + @Test |
| 194 | + public void queryByPriceBetween() { |
| 195 | + // 根据价格区间查询 |
| 196 | + List<Item> list = itemRepository.findByPriceBetween(5000.00, 6000.00); |
| 197 | + list.forEach(item -> System.out.println("item = " + item)); |
| 198 | + } |
| 199 | + |
| 200 | + /** |
| 201 | + * 自定义查询 |
| 202 | + */ |
| 203 | + @Test |
| 204 | + public void search() { |
| 205 | + // 构建查询条件 |
| 206 | + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); |
| 207 | + // 添加基本分词查询 |
| 208 | + queryBuilder.withQuery(QueryBuilders.matchQuery("title", "小米手机")); |
| 209 | + // 搜索,获取结果 |
| 210 | + Page<Item> items = itemRepository.search(queryBuilder.build()); |
| 211 | + // 总条数 |
| 212 | + long total = items.getTotalElements(); |
| 213 | + System.out.println("total = " + total); |
| 214 | + items.forEach(item -> System.out.println("item = " + item)); |
| 215 | + } |
| 216 | + |
| 217 | + /** |
| 218 | + * 分页查询 |
| 219 | + */ |
| 220 | + @Test |
| 221 | + public void searchByPage() { |
| 222 | + // 构建查询条件 |
| 223 | + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); |
| 224 | + // 添加基本分词查询 |
| 225 | + queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机")); |
| 226 | + // 分页: |
| 227 | + int page = 0; |
| 228 | + int size = 2; |
| 229 | + queryBuilder.withPageable(PageRequest.of(page, size)); |
| 230 | + // 搜索,获取结果 |
| 231 | + Page<Item> items = itemRepository.search(queryBuilder.build()); |
| 232 | + long total = items.getTotalElements(); |
| 233 | + System.out.println("总条数 = " + total); |
| 234 | + System.out.println("总页数 = " + items.getTotalPages()); |
| 235 | + System.out.println("当前页:" + items.getNumber()); |
| 236 | + System.out.println("每页大小:" + items.getSize()); |
| 237 | + items.forEach(item -> System.out.println("item = " + item)); |
| 238 | + } |
| 239 | + |
| 240 | + /** |
| 241 | + * 排序 |
| 242 | + */ |
| 243 | + @Test |
| 244 | + public void searchAndSort() { |
| 245 | + // 构建查询条件 |
| 246 | + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); |
| 247 | + // 添加基本分词查询 |
| 248 | + queryBuilder.withQuery(QueryBuilders.termQuery("category", "手机")); |
| 249 | + // 排序 |
| 250 | + queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.ASC)); |
| 251 | + // 搜索,获取结果 |
| 252 | + Page<Item> items = this.itemRepository.search(queryBuilder.build()); |
| 253 | + // 总条数 |
| 254 | + long total = items.getTotalElements(); |
| 255 | + System.out.println("总条数 = " + total); |
| 256 | + items.forEach(item -> System.out.println("item = " + item)); |
| 257 | + } |
| 258 | + |
| 259 | + /** |
| 260 | + * 聚合为桶 |
| 261 | + */ |
| 262 | + @Test |
| 263 | + public void testAgg() { |
| 264 | + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); |
| 265 | + // 不查询任何结果 |
| 266 | + queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); |
| 267 | + // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand |
| 268 | + queryBuilder.addAggregation(AggregationBuilders.terms("brands").field("brand")); |
| 269 | + // 2、查询,需要把结果强转为AggregatedPage类型 |
| 270 | + AggregatedPage<Item> aggPage = (AggregatedPage<Item>) itemRepository.search(queryBuilder.build()); |
| 271 | + // 3、解析 |
| 272 | + // 3.1、从结果中取出名为brands的那个聚合, |
| 273 | + // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型 |
| 274 | + StringTerms agg = (StringTerms) aggPage.getAggregation("brands"); |
| 275 | + // 3.2、获取桶 |
| 276 | + List<StringTerms.Bucket> buckets = agg.getBuckets(); |
| 277 | + // 3.3、遍历 |
| 278 | + for (StringTerms.Bucket bucket : buckets) { |
| 279 | + // 3.4、获取桶中的key,即品牌名称 |
| 280 | + System.out.println(bucket.getKeyAsString()); |
| 281 | + // 3.5、获取桶中的文档数量 |
| 282 | + System.out.println(bucket.getDocCount()); |
| 283 | + } |
| 284 | + } |
| 285 | + |
| 286 | + /** |
| 287 | + * 嵌套聚合,求平均值 |
| 288 | + */ |
| 289 | + @Test |
| 290 | + public void testSubAgg() { |
| 291 | + NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder(); |
| 292 | + // 不查询任何结果 |
| 293 | + queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null)); |
| 294 | + // 1、添加一个新的聚合,聚合类型为terms,聚合名称为brands,聚合字段为brand |
| 295 | + queryBuilder.addAggregation( |
| 296 | + AggregationBuilders.terms("brands").field("brand") |
| 297 | + .subAggregation(AggregationBuilders.avg("priceAvg").field("price")) // 在品牌聚合桶内进行嵌套聚合,求平均值 |
| 298 | + ); |
| 299 | + // 2、查询,需要把结果强转为AggregatedPage类型 |
| 300 | + AggregatedPage<Item> aggPage = (AggregatedPage<Item>) this.itemRepository.search(queryBuilder.build()); |
| 301 | + // 3、解析 |
| 302 | + // 3.1、从结果中取出名为brands的那个聚合, |
| 303 | + // 因为是利用String类型字段来进行的term聚合,所以结果要强转为StringTerm类型 |
| 304 | + StringTerms agg = (StringTerms) aggPage.getAggregation("brands"); |
| 305 | + // 3.2、获取桶 |
| 306 | + List<StringTerms.Bucket> buckets = agg.getBuckets(); |
| 307 | + // 3.3、遍历 |
| 308 | + for (StringTerms.Bucket bucket : buckets) { |
| 309 | + // 3.4、获取桶中的key,即品牌名称 3.5、获取桶中的文档数量 |
| 310 | + System.out.println(bucket.getKeyAsString() + ",共" + bucket.getDocCount() + "台"); |
| 311 | + |
| 312 | + // 3.6.获取子聚合结果: |
| 313 | + InternalAvg avg = (InternalAvg) bucket.getAggregations().asMap().get("priceAvg"); |
| 314 | + System.out.println("平均售价:" + avg.getValue()); |
| 315 | + } |
| 316 | + } |
| 317 | +} |
| 318 | +``` |
0 commit comments