准备
如果您对Elasticsearch还不了解,您可以先阅读我另外一篇文章看完这篇Elasticsearch还不会,你打我,先对Elasticsearch有一个基础的了解。
一、安装并运行Elasticsearch
在下载之前你应该确保你的 Java 版本保持在 1.8 及以上,这是 Elasticsearch 的硬性要求,可以自行打开命令行输入 java -version 来查看 Java 的版本
安装完 Java,就可以跟着Elasticsearch官网下载地址安装 Elasticsearch,直接下载压缩包比较简单。我的开发环境是Windows,因此我选择的是Windows版本。
下载压缩包后对其进行解压,进入Elasticsearch的bin目录,D:\install\elasticsearch-7.12.1\bin
,双击elasticsearch.bat
启动Elasticsearch
此时,Elasticsearch运行在本地的9200端口,在浏览器中输入地址http://localhost:9200/
如果看到以下信息就说明你的电脑已成功安装Elasticsearch
默认情况下,Elasticsearch 只允许本机访问,如果需要远程访问,可以修改 Elastic 安装目录的config/elasticsearch.yml文件,去掉network.host的注释,将它的值改成0.0.0.0,然后重新启动 Elasticsearch。
network.host: 0.0.0.0
上面代码中,设成0.0.0.0让任何人都可以访问。线上服务不要这样设置,要设成具体的 IP。
二、Elasticsearch可视化操作平台Kibana
Kibana 是一个开源的分析和可视化平台,旨在与 Elasticsearch 合作。Kibana 提供搜索、查看和与存储在 Elasticsearch 索引中的数据进行交互的功能。开发者或运维人员可以轻松地执行高级数据分析,并在各种图表、表格和地图中可视化数据。
你可以从 Elasticsearch 的官网下载Kibana,同样Kibana我们也选择下载版本7.12.1。解压文档后,进入kibana的bin目录D:\install\kibana-7.12.1-windows-x86_64\bin
,双击kibana.bat启动Kibana。
kibana启动后运行在5601端口上,我们可以在浏览器中输入http://localhost:5601
地址来访问kibana。
注意:启动kibana前必须先启动Elasticsearch,否则kibana会启动不成功。
开发中我们一般用得比较多的是Dev Tools工具
三、Elasticsearch中文分词器-IK分词器
3.1 中文分词
首先我们通过Postman发送GET请求查询分词效果
GET http://localhost:9200/_analyze
{
"text":"我爱你中国"
}
得到如下结果,可以发现ES的默认分词器无法识别中文:我、我爱你、中国这样的词汇,而是简单的将每个字拆完分为一个词,这显然不符合我们的使用要求,所以我们需要安装中文分词器来解决这个问题。
{
"tokens": [
{
"token": "我",
"start_offset": 0,
"end_offset": 1,
"type": "<IDEOGRAPHIC>",
"position": 0
},
{
"token": "爱",
"start_offset": 1,
"end_offset": 2,
"type": "<IDEOGRAPHIC>",
"position": 1
},
{
"token": "你",
"start_offset": 2,
"end_offset": 3,
"type": "<IDEOGRAPHIC>",
"position": 2
},
{
"token": "中",
"start_offset": 3,
"end_offset": 4,
"type": "<IDEOGRAPHIC>",
"position": 3
},
{
"token": "国",
"start_offset": 4,
"end_offset": 5,
"type": "<IDEOGRAPHIC>",
"position": 4
}
]
}
或用kibana请求得到效果(用kibana的话就不用再写IP地址和端口了)
GET _analyze
{
"text":"我爱你中国"
}
IK分词器是一款国人开发的相对简单的中文分词器。首先我们访问 https://github.com/medcl/elasticsearch-analysis-ik/releases 下载与ES对应版本的中文分词器。将解压后的后的文件夹放入ES根目录下的plugins/ik目录下(ik目录要手动创建),重启ES即可使用。
IK提供了两个分词算法ik_smart和ik_max_word。其中ik_smart为最少切分;ik_max_word为最细粒度划分。
- ik_max_word:会将文本做最细粒度的拆分,例如「我是程序员」会被拆分为「我、是、程序员、程序、员」。
- ik_smart:会将文本做最少切分,例如「我是程序员」会被拆分为「我、是、程序员」
GET _analyze
{
"analyzer":"ik_smart",
"text":"我爱你中国"
}
GET _analyze
{
"analyzer":"ik_smart",
"text":"我是程序员"
}
四、Spring Data Elasticsearch
4.1 Spring Data Elasticsearch简介
Spring Data Elasticsearch是Spring Data项目下的一个子模块。查看 Spring Data的官网:http://projects.spring.io/spring-data/
Spring Data 的使命是给各种数据访问提供统一的编程接口,不管是关系型数据库(如MySQL),还是非关系数据库(如Redis),或者类似Elasticsearch这样的索引数据库。从而简化开发人员的代码,提高开发效率。
4.2 Spring Data Elasticsearch注解
Spring Data Elasticsearch通过注解来声明字段的映射属性,有下面的三个注解
@Document
@Document
作用在类,标记实体类为文档对象,一般有四个属性
- indexName:对应索引库名称,mysql中数据库的概念
- type:对应在索引库中的类型,mysql中表的概念,新版已经没有该属性了
- shards:分片数量,默认5
- replicas:副本数量,默认1
@Id
@Id
作用在成员变量,标记一个字段作为id主键
@Field
@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分词器
五、代码实现
5.1 创建工程[es-product]
5.2 配置pom.xml
导入lombok、mybatis-plus、mysql依赖包,pom.xml如下
<?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.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.es</groupId>
<artifactId>product</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>es-product</name>
<description>Elasticsearch实现商品搜索</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</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>
<version>1.18.10</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
5.3 创建数据和数据表
1、创建数据
-- 创建数据库
create database es_mall_db character set utf8mb4;
-- 创建用户
create user 'es_mall_u '@'%' identified by 'es_mall_PWD_123';
-- 授权用户
grant all privileges on seata_db.* to 'es_mall_u'@'%';
-- 刷新
flush privileges;
2、创建数据表
CREATE TABLE `brand` (
`id` char(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'ID',
`brand_name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌名称',
`deleted` tinyint(1) DEFAULT '0' COMMENT '0正常 1删除',
PRIMARY KEY (`brand_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='品牌'
CREATE TABLE `category` (
`id` char(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'ID',
`category_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '分类名称',
`deleted` tinyint(1) DEFAULT '0' COMMENT '0正常 1删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品分类'
CREATE TABLE `merchant` (
`id` char(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'ID',
`merchant_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '店铺名称',
`address` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '地址',
`phone` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '联系电话',
`star` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '星级',
`deleted` tinyint(1) DEFAULT '0' COMMENT '0正常 1删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='店铺'
CREATE TABLE `product` (
`id` char(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'ID',
`merchant_id` char(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '店铺ID',
`brand_id` char(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '品牌ID',
`category_id` char(32) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '商品分类ID',
`product_name` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商品名称',
`product_no` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '商品编号(barcode)',
`images` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '图片',
`original_price` double DEFAULT '0' COMMENT '商品原价',
`discount_price` double DEFAULT '0' COMMENT '商品折扣价',
`stock` int(11) DEFAULT '0' COMMENT '库存',
`sales_volume` int(11) DEFAULT '0' COMMENT '销量',
`views` int(11) DEFAULT '0' COMMENT '浏览量',
`synopsis` varchar(500) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '简介',
`introduction` mediumtext COLLATE utf8mb4_unicode_ci COMMENT '详细介绍',
`publish_state` tinyint(2) DEFAULT '0' COMMENT '发布状态(0:否;1:是)',
`deleted` tinyint(1) DEFAULT '0' COMMENT '0正常 1删除',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='商品'
3、插入测试数据
insert into brand (id,brand_name) values (REPLACE(UUID(),"-",""),"小米");
insert into brand (id,brand_name) values (REPLACE(UUID(),"-",""),"华为");
insert into brand (id,brand_name) values (REPLACE(UUID(),"-",""),"Apple");
insert into category (id,category_name) values (REPLACE(UUID(),"-",""),"手机");
insert into category (id,category_name) values (REPLACE(UUID(),"-",""),"电脑");
insert into merchant (id,merchant_name,address,star) values (REPLACE(UUID(),"-",""),"华为专卖店","深圳",'5');
insert into merchant (id,merchant_name,address,star) values (REPLACE(UUID(),"-",""),"AC电子商城","珠海",'5');
insert into product (id,merchant_id,brand_id,category_id,product_name,product_no,original_price,discount_price,stock,synopsis,publish_state)
values (REPLACE(UUID(),"-",""),"6022b9dbb3ce11ebaf7000163e066303","968a74aab3cd11ebaf7000163e066303",'bb592145b3cd11ebaf7000163e066303','Apple iPhone 12 (A2404) 128GB 白色','A0001',6799,5800,100,'支持移动联通电信5G 双卡双待手机',1);
insert into product (id,merchant_id,brand_id,category_id,product_name,product_no,original_price,discount_price,stock,synopsis,publish_state)
values (REPLACE(UUID(),"-",""),"6022b9dbb3ce11ebaf7000163e066303","968a74aab3cd11ebaf7000163e066303",'bb592145b3cd11ebaf7000163e066303','Apple iPhone 8 (A2404) 256GB 黑色','B0001',4700,3500,50,'支持移动联通电信4G',1);
insert into product (id,merchant_id,brand_id,category_id,product_name,product_no,original_price,discount_price,stock,synopsis,publish_state)
values (REPLACE(UUID(),"-",""),"601ae548b3ce11ebaf7000163e066303","9059788ab3cd11ebaf7000163e066303",'bb592145b3cd11ebaf7000163e066303','华为mate40pro 5G手机 亮黑色 8+256G','C0001',7499,7000,200,'全网通(碎屏险套餐)',1);
5.4 配置application.yml
server:
port: 8080
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://47.105.146.74:3306/es_mall_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone =GMT%2B8
username: es_mall_u
password: es_mall_PWD_123
5.5 配置Elasticsearch信息
旧版Elasticsearch配置信息都是在application.yml里配置的,但如果用的是新版Elasticsearch,这样配置会提示已过时,新版我们需要用配置类
package com.es.product.confg;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
/**
* @author Alan Chen
* @description
* @date 2021/5/12
*/
@Configuration
@EnableElasticsearchRepositories
public class EsRestClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
}
5.6 entity类
创建entity包,并创建对应的实体类,entity类对应数据表
/**
* @author Alan Chen
* @description 品牌
* @date 2021/5/13
*/
@Data
public class Brand {
@TableId(type = IdType.UUID)
private String id;
//品牌名称
private String brandName;
//逻辑删除标志 0:未删除;1:已删除
@TableLogic
private Integer deleted;
}
/**
* @author Alan Chen
* @description 商品分类
* @date 2021/5/13
*/
@Data
public class Category {
@TableId(type = IdType.UUID)
private String id;
//分类名称
private String categoryName;
//逻辑删除标志 0:未删除;1:已删除
@TableLogic
private Integer deleted;
}
/**
* @author Alan Chen
* @description 店铺
* @date 2021/5/13
*/
@Data
public class Merchant {
@TableId(type = IdType.UUID)
private String id;
//店铺名称
private String merchantName;
//地址
private String address;
//联系电话
private String phone;
//星级
private String star;
//逻辑删除标志 0:未删除;1:已删除
@TableLogic
private Integer deleted;
}
/**
* @author Alan Chen
* @description 商品
* @date 2020-01-04
*/
@Data
public class Product{
@TableId(type = IdType.UUID)
private String id;
//店铺ID
private String merchantId;
//品牌ID
private String brandId;
//商品分类ID
private String categoryId;
//商品名称
private String productName;
//商品编号(barcode)
private String productNo;
//图片
private String images;
//商品原价
private double originalPrice;
//商品折扣价
private double discountPrice;
//库存
private Integer stock;
//销量
private Integer salesVolume;
//浏览量
private Integer views;
//简介
private String synopsis;
//详细介绍
private String introduction;
//发布状态
private int publishState;
//逻辑删除标志 0:未删除;1:已删除
@TableLogic
private Integer deleted;
}
5.7 es模块
新建es包,将Elasticsearch相关的类都放es包里,并在es创建dao、document、repository、service包
EsProduct
跨表组装需要的数据,存入ES里
package com.es.product.es.document;
import lombok.Data;
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;
/**
* @author Alan Chen
* @description ES类-商品
* @date 2021/5/13
*/
@Data
@Document(indexName = "es_product",shards = 1, replicas = 0)
public class EsProduct {
@Id
private String id;
//店铺名称
@Field(type = FieldType.Keyword)
private String merchantName;
//品牌名称
@Field(type = FieldType.Keyword)
private String brandName;
//商品分类名称
@Field(type = FieldType.Keyword)
private String categoryName;
//商品名称
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String productName;
//商品编号(barcode)
@Field(type = FieldType.Keyword)
private String productNo;
//图片
@Field(type = FieldType.Keyword)
private String images;
//商品原价
@Field(type = FieldType.Double)
private double originalPrice;
//商品折扣价
@Field(type = FieldType.Double)
private double discountPrice;
//库存
@Field(type = FieldType.Integer)
private Integer quantity;
//销量
@Field(type = FieldType.Integer)
private Integer salesVolume;
//简介
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String synopsis;
}
EsProductDao
继承mybatis的BaseMapper 类,查询商品数据
package com.es.product.es.dao;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.es.product.es.document.EsProduct;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author Alan Chen
* @description
* @date 2021/5/13
*/
@Repository
@Mapper
public interface EsProductDao extends BaseMapper {
/**
* 获取所有商品数据
* @return
*/
List<EsProduct> listAll();
/**
* 获取信息
* @param id
* @return
*/
EsProduct selectById(String id);
}
EsProductRepository
继承SpringBoot Data Elasticsearch的ElasticsearchRepository,操作ES
package com.es.product.es.repository;
import com.es.product.es.document.EsProduct;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
/**
* @author Alan Chen
* @description
* @date 2021/5/13
*/
public interface EsProductRepository extends ElasticsearchRepository<EsProduct,String> {
/**
* 根据价格区间查询(自定义方法-名称约定)
* @param price1
* @param price2
* @return
*/
List<EsProduct> findByOriginalPriceBetween(double price1, double price2);
}
package com.es.product.es.service;
import com.es.product.es.document.EsProduct;
import org.springframework.data.domain.Page;
import java.util.List;
/**
* @author Alan Chen
* @description
* @date 2021/5/13
*/
public interface IEsProductService {
/**
* 从数据库中导入商品到ES
* @return
*/
void importAll();
/**
* 新增/修改(id存在就是修改,否则就是插入)
* @param id
*/
void save(String id);
/**
* 根据id删除商品
* @param id
*/
void delete(String id);
/**
* 根据id获得商品
* @param id
* @return
*/
EsProduct get(String id);
/**
* 批量删除
* @param ids
*/
void deletes(List<String> ids);
/**
* 所有商品
* @return
*/
List<EsProduct> listAll();
/**
* 根据价格区间查询(自定义方法)
* @param price1
* @param price2
* @return
*/
List<EsProduct> queryByPriceBetween(double price1, double price2);
/**
* 名称查询(高级查询)
* @param productName
* @return
*/
Page<EsProduct> query(String productName);
/**
* 分页查询
* @param categoryName
* @param page
* @param size
* @return
*/
Page<EsProduct> pageSearch(String categoryName,int page ,int size);
/**
* 分页查询(排序)
* @param categoryName
* @param page
* @param size
* @return
*/
Page<EsProduct> pageSortSearch(String categoryName,int page ,int size);
}
package com.es.product.es.service.impl;
import com.es.product.es.dao.EsProductDao;
import com.es.product.es.document.EsProduct;
import com.es.product.es.repository.EsProductRepository;
import com.es.product.es.service.IEsProductService;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @author Alan Chen
* @description
* @date 2021/5/13
*/
@Service
public class EsProductServiceImpl implements IEsProductService {
@Autowired
EsProductDao esProductDao;
@Autowired
EsProductRepository esProductRepository;
/**
* 从数据库中导入商品到ES
* @return
*/
@Override
public void importAll() {
List<EsProduct> list = esProductDao.listAll();
esProductRepository.saveAll(list);
}
/**
* 新增/修改(id存在就是修改,否则就是插入)
* @param id
*/
@Override
public void save(String id) {
EsProduct esProduct = esProductDao.selectById(id);
esProductRepository.save(esProduct);
}
/**
* 根据id删除商品
* @param id
*/
@Override
public void delete(String id) {
esProductRepository.deleteById(id);
}
/**
* 根据id获得商品
* @param id
* @return
*/
@Override
public EsProduct get(String id) {
return esProductDao.selectById(id);
}
/**
* 批量删除
* @param ids
*/
@Override
public void deletes(List<String> ids) {
for(String id : ids){
esProductRepository.deleteById(id);
}
}
/**
* 所有商品(按价格排序)
* @return
*/
@Override
public List<EsProduct> listAll() {
Iterable<EsProduct> items = this.esProductRepository.findAll(Sort.by(Sort.Direction.DESC, "discountPrice"));
List<EsProduct> list = new ArrayList<>();
items.forEach(esProduct -> list.add(esProduct));
return list;
}
/**
* 根据价格区间查询(自定义方法)
* @param price1
* @param price2
* @return
*/
@Override
public List<EsProduct> queryByPriceBetween(double price1, double price2) {
return esProductRepository.findByOriginalPriceBetween(price1,price2);
}
/**
* 名称查询(高级查询)
* @param productName
* @return
*/
@Override
public Page<EsProduct> query(String productName) {
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 添加基本的分词查询
queryBuilder.withQuery(QueryBuilders.matchQuery("productName", productName));
// 执行搜索,获取结果
Page<EsProduct> esProducts = esProductRepository.search(queryBuilder.build());
return esProducts;
}
/**
* 分页查询
* @param categoryName
* @param page
* @param size
* @return
*/
@Override
public Page<EsProduct> pageSearch(String categoryName, int page, int size) {
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 添加基本的分词查询
queryBuilder.withQuery(QueryBuilders.termQuery("categoryName", categoryName));
// 设置分页参数
queryBuilder.withPageable(PageRequest.of(page, size));
// 执行搜索,获取结果
Page<EsProduct> esProducts = esProductRepository.search(queryBuilder.build());
return esProducts;
}
/**
* 分页查询(排序)
* @param categoryName
* @param page
* @param size
* @return
*/
@Override
public Page<EsProduct> pageSortSearch(String categoryName, int page, int size) {
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 添加基本的分词查询
queryBuilder.withQuery(QueryBuilders.termQuery("categoryName", categoryName));
// 设置分页参数
queryBuilder.withPageable(PageRequest.of(page, size));
// 排序
queryBuilder.withSort(SortBuilders.fieldSort("originalPrice").order(SortOrder.DESC));
// 执行搜索,获取结果
Page<EsProduct> esProducts = esProductRepository.search(queryBuilder.build());
return esProducts;
}
}
5.8 mapper文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.es.product.es.dao.EsProductDao">
<select id="listAll" resultType="com.es.product.es.document.EsProduct">
select
t.id,
t2.brand_name,
t3.category_name,
t4.merchant_name,
t.product_name,
t.product_no,
t.images,
t.original_price,
t.discount_price,
t.stock,
t.sales_volume,
t.synopsis
from product t
left join brand t2 on t.brand_id = t2.id
left join category t3 on t.category_id = t3.id
left join merchant t4 on t.merchant_id = t4.id
<where>
t.deleted = 0
and t2.deleted = 0
and t3.deleted = 0
and t4.deleted = 0
</where>
</select>
<select id="selectById" resultType="com.es.product.es.document.EsProduct">
select
t.id,
t2.brand_name,
t3.category_name,
t4.merchant_name,
t.product_name,
t.product_no,
t.images,
t.original_price,
t.discount_price,
t.stock,
t.sales_volume,
t.synopsis
from product t
left join brand t2 on t.brand_id = t2.id
left join category t3 on t.category_id = t3.id
left join merchant t4 on t.merchant_id = t4.id
<where>
t.deleted = 0
and t.id = #{id}
</where>
</select>
</mapper>
5.9 单元测试类
package com.es.product;
import com.es.product.es.document.EsProduct;
import com.es.product.es.service.IEsProductService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import java.util.List;
@SpringBootTest
class EsProductApplicationTests {
@Autowired
private IEsProductService esProductService;
/**
* 从数据库中导入商品到ES
* @return
*/
@Test
public void importAll(){
esProductService.importAll();
}
/**
* 新增/修改(id存在就是修改,否则就是插入)
*/
@Test
public void save(){
String id = "2e01a7b7b3cf11ebaf7000163e066303";
esProductService.save(id);
}
/**
* 根据id删除商品
*/
@Test
public void delete(){
String id = "2e01a7b7b3cf11ebaf7000163e066303";
esProductService.delete(id);
}
/**
* 根据id获得商品
* @return
*/
@Test
public void get(){
String id = "2e01a7b7b3cf11ebaf7000163e066303";
EsProduct esProduct = esProductService.get(id);
System.out.println(esProduct);
}
/**
* 批量删除
*/
@Test
public void deletes(){
}
/**
* 所有商品(按价格排序)
* @return
*/
@Test
public void listAll(){
List<EsProduct> list = esProductService.listAll();
System.out.println(list);
}
/**
* 根据价格区间查询(自定义方法)
* @return
*/
@Test
public void queryByPriceBetween(){
List<EsProduct> list = esProductService.queryByPriceBetween(4000,5000);
System.out.println(list);
}
/**
* 名称查询
* @return
*/
@Test
public void query(){
String productName = "Apple";
Page<EsProduct> page = esProductService.query(productName);
// 打印总条数
System.out.println(page.getTotalElements());
// 打印总页数
System.out.println(page.getTotalPages());
page.forEach(System.out::println);
}
/**
* 分页查询
* @return
*/
@Test
public void pageSearch(){
String categoryName = "手机";
int page = 1;
int size =2;
Page<EsProduct> esProducts = esProductService.pageSearch(categoryName,page,size);
// 打印总条数
System.out.println(esProducts.getTotalElements());
// 打印总页数
System.out.println(esProducts.getTotalPages());
// 每页大小
System.out.println(esProducts.getSize());
// 当前页
System.out.println(esProducts.getNumber());
esProducts.forEach(System.out::println);
}
/**
* 分页查询(排序)
* @return
*/
@Test
public void pageSortSearch(){
String categoryName = "手机";
int page = 1;
int size =2;
Page<EsProduct> esProducts = esProductService.pageSortSearch(categoryName,page,size);
// 打印总条数
System.out.println(esProducts.getTotalElements());
// 打印总页数
System.out.println(esProducts.getTotalPages());
// 每页大小
System.out.println(esProducts.getSize());
// 当前页
System.out.println(esProducts.getNumber());
esProducts.forEach(System.out::println);
}
}
六、测试接口详解
6.1 将商品数据全部导入ES
@Override
public void importAll() {
List<EsProduct> list = esProductDao.listAll();
esProductRepository.saveAll(list);
}
6.2 新增/修改(id存在就是修改,否则就是插入)
/**
* 新增/修改(id存在就是修改,否则就是插入)
* @param id
*/
@Override
public void save(String id) {
EsProduct esProduct = esProductDao.selectById(id);
esProductRepository.save(esProduct);
}
当业务模块对某一商品进行了更新(如改了商品名称、商品价格、销售后库存减少等)就可以调用该接口更新ES里的商品信息。
6.3 根据id删除商品
/**
* 根据id删除商品
* @param id
*/
@Override
public void delete(String id) {
esProductRepository.deleteById(id);
}
商品删除或下架后,可以调用该接口更新ES里的商品信息。
6.4 所有商品(按价格排序)
/**
* 所有商品(按价格排序)
* @return
*/
@Override
public List<EsProduct> listAll() {
Iterable<EsProduct> items = this.esProductRepository.findAll(Sort.by(Sort.Direction.DESC, "discountPrice"));
List<EsProduct> list = new ArrayList<>();
items.forEach(esProduct -> list.add(esProduct));
return list;
}
6.5 根据价格区间查询(自定义方法)
Spring Data 的另一个强大功能,是根据方法名称自动实现功能。比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。当然,方法名称要符合一定的约定:
我们来按照价格区间查询,定义这样的一个方法
/**
* 根据价格区间查询(自定义方法)
* @param price1
* @param price2
* @return
*/
@Override
public List<EsProduct> queryByPriceBetween(double price1, double price2) {
return esProductRepository.findByOriginalPriceBetween(price1,price2);
}
6.6 名称查询(高级查询)
虽然基本查询和自定义方法已经很强大了,但是如果是复杂查询(模糊、通配符、词条查询等)就显得力不从心了。此时,我们只能使用原生查询。
/**
* 名称查询(高级查询)
* @param productName
* @return
*/
@Override
public Page<EsProduct> query(String productName) {
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 添加基本的分词查询
queryBuilder.withQuery(QueryBuilders.matchQuery("productName", productName));
// 执行搜索,获取结果
Page<EsProduct> esProducts = esProductRepository.search(queryBuilder.build());
return esProducts;
}
6.7 分页查询
/**
* 分页查询
* @param categoryName
* @param page
* @param size
* @return
*/
@Override
public Page<EsProduct> pageSearch(String categoryName, int page, int size) {
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 添加基本的分词查询
queryBuilder.withQuery(QueryBuilders.termQuery("categoryName", categoryName));
// 设置分页参数
queryBuilder.withPageable(PageRequest.of(page, size));
// 执行搜索,获取结果
Page<EsProduct> esProducts = esProductRepository.search(queryBuilder.build());
return esProducts;
}
6.8 分页查询(排序)
/**
* 分页查询(排序)
* @param categoryName
* @param page
* @param size
* @return
*/
@Override
public Page<EsProduct> pageSortSearch(String categoryName, int page, int size) {
// 构建查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 添加基本的分词查询
queryBuilder.withQuery(QueryBuilders.termQuery("categoryName", categoryName));
// 设置分页参数
queryBuilder.withPageable(PageRequest.of(page, size));
// 排序
queryBuilder.withSort(SortBuilders.fieldSort("originalPrice").order(SortOrder.DESC));
// 执行搜索,获取结果
Page<EsProduct> esProducts = esProductRepository.search(queryBuilder.build());
return esProducts;
}
七、补充
7. 1 新版@Document
无type属性
在新版ES里,@Document
注解已经没有type属性了,因此我的EsProduct没有配置type
@Document(indexName = "es_product",shards = 1, replicas = 0)
public class EsProduct {}
7.2 新版ES删除索引文档
以前旧版ES删除索引文档是这样的,通过ID删除索引文档
DELETE /es_product/_doc/z8qEEHIBZBLFtWo4JEtR
现在新版ES不需要写文档类型_doc了
DELETE /es_product
八、Elasticsearch与Mysql数据同步问题
Mysql数据同步到ES中分为两种,分别是全量同步和增量同步。全量同步表示第一次建立好ES索引之后,将Mysql中所有数据一次性导入到ES中。增量同步表示Mysql中产生新的数据,这些新的数据包括三种情况,就是新插入Mysql中的数据,更新老的数据,删除的数据,这些数据的变动与新增都要同步到ES中,Elasticsearch与Mysql数据同步有三种方式,分别是:
1、通过Elasticsearch提供的API进行增删改查。
2、通过收集日志进行同步,如ES官方数据收集和同步组件logstash。
3、通过中间件进行数据全量、增量的数据同步,如阿里巴巴canal、go-mysql-elasticsearch等,二者都是基于Mysql的binlog订阅。