文章目录
- ProductController.java
- Product.java
- ElasticsearchSyncListener.java
- ProductElasticSearchMapper.java
- ProductMapper.java
- ProductDeletedEvent.java
- ProductServiceImpl.java
- SyncProductService.java
- IProductService.java
- ElasticSearchSpringDemoApplication.java
- ServletInitializer.java
- product.sql
- 同步
- ProductMapper.xml
- application.yaml
- pom.xml
ProductController.java
package com.example.controller;import com.example.service.IProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RestController;import java.util.Map;
@RestController
@RequestMapping("product")
public class ProductController {@Autowiredprivate IProductService productService;@GetMapping("saveProductToES")public String saveProductToES(){productService.saveProductFromDBToES();return "ok";}@GetMapping("delete/{productId}")public String deleteProduct(@PathVariable Integer productId){return productService.deleteProduct(productId);}@GetMapping("kw/{kw}/page/{pageNum}")public Map<String, Object> getByKeyword(@PathVariable("kw") String keyword,@PathVariable("pageNum") Integer pageNum){if(pageNum == null)pageNum = 1;Map<String, Object> result = productService.getByNameAndInfo(keyword, keyword, pageNum);return result;}}
Product.java
package com.example.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Document(indexName = "myproduct")
public class Product implements Serializable {private static final long serialVersionUID = 1L;@TableId(value = "product_id",type = IdType.AUTO)@Idprivate Integer productId;@Field(type = FieldType.Keyword)private String productName;private BigDecimal productPrice;private String productImg;private Integer productCount;@Field( type = FieldType.Date,name = "update_time",format = {},pattern = "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd'T'HH:mm:ss'+08:00' || strict_date_opotional_time || epoch_millis")private LocalDateTime createTime;@Field( type = FieldType.Date,name = "update_time",format = {},pattern = "yyyy-MM-dd HH:mm:ss || yyyy-MM-dd'T'HH:mm:ss'+08:00' || strict_date_opotional_time || epoch_millis")private LocalDateTime updateTime;@Field(type = FieldType.Text,analyzer = "ik_smart",searchAnalyzer = "ik_max_word")private String productInfo;public Integer getProductId() {return productId;}public void setProductId(Integer productId) {this.productId = productId;}public String getProductName() {return productName;}public void setProductName(String productName) {this.productName = productName;}public BigDecimal getProductPrice() {return productPrice;}public void setProductPrice(BigDecimal productPrice) {this.productPrice = productPrice;}public String getProductImg() {return productImg;}public void setProductImg(String productImg) {this.productImg = productImg;}public Integer getProductCount() {return productCount;}public void setProductCount(Integer productCount) {this.productCount = productCount;}public LocalDateTime getCreateTime() {return createTime;}public void setCreateTime(LocalDateTime createTime) {this.createTime = createTime;}public LocalDateTime getUpdateTime() {return updateTime;}public void setUpdateTime(LocalDateTime updateTime) {this.updateTime = updateTime;}public String getProductInfo() {return productInfo;}public void setProductInfo(String productInfo) {this.productInfo = productInfo;}@Overridepublic String toString() {return "Product{" +"productId=" + productId +", productName=" + productName +", productPrice=" + productPrice +", productImg=" + productImg +", productCount=" + productCount +", createTime=" + createTime +", updateTime=" + updateTime +", productInfo=" + productInfo +"}";}
}
ElasticsearchSyncListener.java
package com.example.listener;import com.example.service.impl.ProductDeletedEvent;
import com.example.mapper.ProductElasticSearchMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;@Component
public class ElasticsearchSyncListener {@Autowiredprivate ProductElasticSearchMapper productElasticSearchMapper;@EventListenerpublic void handleProductDeletedEvent(ProductDeletedEvent event) {Integer productId = event.getProductId();productElasticSearchMapper.deleteById(productId);}
}
ProductElasticSearchMapper.java
package com.example.mapper;import com.example.entity.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;import java.util.List;@Repository
public interface ProductElasticSearchMapper extends ElasticsearchRepository<Product,Integer> {public List<Product> findByProductName(String productName);public List<Product> findByProductInfo(String productInfo);}
ProductMapper.java
package com.example.mapper;import com.example.entity.Product;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;import java.util.List;public interface ProductMapper extends BaseMapper<Product> {}
ProductDeletedEvent.java
package com.example.service.impl;import org.springframework.context.ApplicationEvent;
public class ProductDeletedEvent extends ApplicationEvent {private final Integer productId;public ProductDeletedEvent(Object source, Integer productId) {super(source);this.productId = productId;}public Integer getProductId() {return productId;}
}
ProductServiceImpl.java
package com.example.service.impl;import com.example.entity.Product;
import com.example.mapper.ProductElasticSearchMapper;
import com.example.mapper.ProductMapper;
import com.example.service.IProductService;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.*;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Service
public class ProductServiceImpl implements IProductService {@Autowiredprivate ProductMapper productMapper;@Autowiredprivate ProductElasticSearchMapper productElasticSearchMapper;@Autowiredprivate ElasticsearchRestTemplate restTemplate;@Autowiredprivate ApplicationEventPublisher applicationEventPublisher;@Overridepublic boolean saveProductFromDBToES() {List<Product> productList = productMapper.selectList(null);Iterable<Product> products = productElasticSearchMapper.saveAll(productList);return true;}@Overridepublic String deleteProduct(Integer productId) {int rows = productMapper.deleteById(productId);if(rows>0){applicationEventPublisher.publishEvent(new ProductDeletedEvent(this, productId));return "删除成功";}return "删除失败";}@Overridepublic Map<String,Object> getByNameAndInfo(String productName, String productInfo, Integer pageNum) {PageRequest page = PageRequest.of(pageNum - 1, 3);BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();if(productName !=null){QueryBuilder queryBuilder = QueryBuilders.queryStringQuery(productName);boolQueryBuilder.must(queryBuilder);}else{if(productInfo !=null)boolQueryBuilder.must(new MatchQueryBuilder("productInfo",productInfo));}SortBuilder sortBuilder = SortBuilders.fieldSort("productPrice").order(SortOrder.DESC);NativeSearchQueryBuilder builder=new NativeSearchQueryBuilder();NativeSearchQuery query=builder.withQuery(boolQueryBuilder).withPageable(page).withSort(sortBuilder).withHighlightFields(new HighlightBuilder.Field("productInfo"),new HighlightBuilder.Field("productName")).withHighlightBuilder(new HighlightBuilder().preTags("<span style='color:red'>").postTags("</span>")).build();SearchHits<Product> search = restTemplate.search(query, Product.class);List<Product> productList= new ArrayList<>();for(SearchHit<Product> searchHit:search){Map<String ,List<String>> highlightFields = searchHit.getHighlightFields();String highLightProName = highlightFields.get("productName") ==null ?searchHit.getContent().getProductName() :highlightFields.get("productName").get(0);String highLightProInfo = highlightFields.get("productInfo") ==null ?searchHit.getContent().getProductInfo() :highlightFields.get("productInfo").get(0);searchHit.getContent().setProductName(highLightProName );searchHit.getContent().setProductInfo(highLightProInfo);productList.add(searchHit.getContent());}SearchPage<Product> searchPage= SearchHitSupport.searchPageFor(search,query.getPageable());long totalElements=searchPage.getTotalElements();int totalPages=searchPage.getTotalPages();int currentPageForDisplay=searchPage.getPageable().getPageNumber() + 1;System.out.println(currentPageForDisplay);Map<String,Object> map=new HashMap<>();map.put("totalElements",totalElements); map.put("totalPages",totalPages); map.put("currentPage",currentPageForDisplay); map.put("productList",productList); return map;}}
SyncProductService.java
package com.example.service.impl;import com.example.service.IProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;@Service
public class SyncProductService {@Autowiredprivate IProductService productService;@Scheduled(fixedRate = 2000) public void syncProductsFromDBToES() {productService.saveProductFromDBToES();}
}
IProductService.java
package com.example.service;import com.example.entity.Product;
import com.baomidou.mybatisplus.extension.service.IService;import java.util.Map;
public interface IProductService {public boolean saveProductFromDBToES();public String deleteProduct(Integer productId);public Map<String,Object> getByNameAndInfo(String productName, String productInfo, Integer pageNum);}
ElasticSearchSpringDemoApplication.java
package com.example;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;@SpringBootApplication
@MapperScan("com.example.mapper")
@EnableScheduling
public class ElasticSearchSpringDemoApplication {public static void main(String[] args) {SpringApplication.run(ElasticSearchSpringDemoApplication.class, args);}}
ServletInitializer.java
package com.example;import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;public class ServletInitializer extends SpringBootServletInitializer {@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder application) {return application.sources(ElasticSearchSpringDemoApplication.class);}}
product.sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
DROP TABLE IF EXISTS `product`;
CREATE TABLE `product` (`product_id` int(0) NOT NULL AUTO_INCREMENT,`product_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,`product_price` decimal(10, 2) NULL DEFAULT NULL,`product_img` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,`product_count` int(0) NULL DEFAULT NULL,`create_time` datetime(0) NULL DEFAULT NULL,`update_time` datetime(0) NULL DEFAULT NULL,`product_info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,PRIMARY KEY (`product_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
同步
增量同步:设计一个机制来跟踪数据库中的更改(如使用数据库的binlog日志),并仅同步自上次同步以来发生的更改。这通常比较复杂,但可以实现实时或近实时的数据同步。定期全量同步:可以定期(如每小时、每天)运行saveProductFromDBToES方法来进行全量同步。这种方法比较简单,但可能会导致数据在一定时间窗口内不同步。删除操作同步:在应用程序中添加逻辑,以便在数据库记录被删除时,也在Elasticsearch中删除相应的文档。这通常需要在数据库删除操作的地方添加额外的代码或使用触发器。使用监听器或事件驱动:如果使用的是支持事件驱动或变更数据捕获(CDC)的数据库或框架,可以配置监听器来捕获数据库更改事件,并据此更新Elasticsearch中的数据。
ProductMapper.xml
<?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.example.mapper.ProductMapper"></mapper>
application.yaml
server:servlet:context-path: /es_demospring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://localhost:3306/cloud_product_db?useSSL=true&useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghaiusername: rootpassword: 123456elasticsearch:uris: localhost:9200connection-timeout: 5ssocket-timeout: 30s
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.7.6</version><relativePath/> </parent><groupId>com.example</groupId><artifactId>elasticSearchSpringDemo</artifactId><version>0.0.1-SNAPSHOT</version><packaging>war</packaging><name>elasticSearchSpringDemo</name><description>elasticSearchSpringDemo</description><properties><java.version>1.8</java.version></properties><dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.28</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.2</version></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.1</version></dependency><dependency><groupId>org.freemarker</groupId><artifactId>freemarker</artifactId><version>2.3.31</version></dependency><dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest-high-level-client</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><dependency><groupId>org.apache.lucene</groupId><artifactId>lucene-core</artifactId><version>8.11.1</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><scope>provided</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>