这里的案例是围绕着查询诗词来进行练习的
主页下载里有数据库,自取!!!!!!!
1.添加依赖
添加springboot整合es的依赖
<dependencies>
<!--redis的依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--es的依赖-->
<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>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</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>
</dependency>
<!--MyBatis Plus依赖-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
</dependencies>
2.nacos配置中心
必须在nacos提前配置好datasource.yml和redis.yml
datasource.yml的配置:
Data ID:datasource.yml
配置格式:选择YAML
spring:
datasource:
url: jdbc:mysql://192.168.*****:3306/${db.name}?serverTimezone=Asia/Shanghai
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
redis.yml:
Data ID:redis.yml
配置格式:选择YAML
spring:
data:
redis:
password: 123456
host: 192.168.****
port: 6379
3.添加配置
application.yml文件:
spring:
#服务名称
application:
name: poetry
#链接es的配置
elasticsearch:
uris: http://192.168.*****:9200
username: jipiao
password: 123456
#要链接数据库的名
db:
name: poetry
bootstrap.yml文件:
spring:
cloud:
nacos:
#nacos访问链接
server-addr: 192.168.******:8848 #访问路径
#从nacos配置中心里读取配置文件
config:
file-extension: yaml #配置中心配置的类型
shared-configs:
- data-id: datasource.yml #配置中心中要访使用的配置名称
refresh: true #开启热启动(动态刷新)
- data-id: redis.yml
refresh: true
4.编写启动类
@SpringBootApplication
@MapperScan("com.axjy.poetry")
public class GushiApplication {
public static void main(String[] args) {
SpringApplication.run(GushiApplication.class, args);
}
}
5.添加pojo实体类
package com.axjy.poetry.pojo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDateTime;
@Data
public class Poetry implements Serializable {
private String _class;
/**
* 诗词编号
*/
private Integer id;
/**
* 标题
*/
private String title;
/**
* 内容
*/
private String content;
/**
* 朝代
*/
private String dynasty;
/**
* 作者
*/
private String name;
/**
* 作者描述
*/
private String intro;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime updateTime;
}
6.添加mapper
package com.axjy.poetry.mapper;
import com.axjy.poetry.pojo.Poetry;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@Mapper
public interface PoetryMapper {
@Select("select t1.id, name,title,content,updateTime,t1.dynasty from poetry t1 join poetry_author t2 on t1.author_id=t2.id")
List<Poetry> searchAll();
}
7.添加一个测试类
创建测试类的原因是要进行创建索引和初始化数据
package com.axjy.poetry;
import com.axjy.poetry.elastic.DocSource;
import com.axjy.poetry.mapper.PoetryMapper;
import com.axjy.poetry.pojo.Poetry;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.client.elc.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import java.util.ArrayList;
import java.util.List;
@SpringBootTest
public class PoetryApplicationTest {
@Resource
private ElasticsearchTemplate elasticsearchTemplate;
@Resource
private PoetryMapper poetryMapper;
/**
* 创建索引
*/
@Test
void createIndex(){
//用于创建es索引
IndexOperations opt = elasticsearchTemplate.indexOps(DocSource.class);
//判断索引是否存在,存在就先删除再创建,不存在就直接创建
if (opt.exists()){
//删除索引
opt.delete();
}
//创建索引
opt.createWithMapping();
System.out.println("创建索引映射成功!");
}
/**
* 初始化数据源
*/
@SneakyThrows
@Test
void initData(){
List<Poetry> list = poetryMapper.searchAll();
List<IndexQuery> queries = new ArrayList<>();
int count=0;
for (int i=0;i < list.size();i++) {
Poetry poetry = list.get(i);
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(poetry.getId()+"");
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(poetry);
indexQuery.setSource(json);
queries.add(indexQuery);
if(i % 2000 == 0) {
elasticsearchTemplate.bulkIndex(queries, DocSource.class);
queries.clear();
System.out.println("提交了《--"+(++count)+"--》次");
}
}
if (queries.size()>0){
elasticsearchTemplate.bulkIndex(queries, DocSource.class);
queries.clear();
System.out.println("提交了《--"+(++count)+"--》次");
}
System.out.println("\n-------添加完毕!-------");
}
}
8.添加查询的测试类
package com.axjy.poetry;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.MatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.JsonData;
import com.axjy.poetry.pojo.Poetry;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
@SpringBootTest
public class SearchTest {
@Resource
private ElasticsearchClient client;
@SneakyThrows
@Test
void testMatch(){
LocalDate endDate2 = LocalDate.now();
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
String endDate = endDate2.format(fmt);
//查询条件
MatchQuery query1 = MatchQuery.of(builder -> builder.field("content").query("三峽"));
RangeQuery query2 = RangeQuery.of(builder -> builder.field("updateTime").lte(JsonData.of(endDate)));
BoolQuery bool = BoolQuery.of(builder -> builder.must(query1._toQuery(),query2._toQuery()));
//构建查询构造器
Highlight highlight = Highlight.of(builder -> builder.fields("content",h->h.preTags("\33[31;1m").postTags("\33[30;2m")));
//查询分页
SearchResponse<Poetry> resp = client.search(builder -> builder.index("poetry").highlight(highlight).query(bool._toQuery()).from(0).size(10), Poetry.class);
System.out.println("-----------"+resp.hits().total().value());
//得到命中
List<Hit<Poetry>> list = resp.hits().hits();
//高亮
List<Poetry> list2 = list.stream().map(hit->{
Poetry poetry = hit.source();
//得到高亮的片段
String segment = hit.highlight().get("content").get(0);
//内容的片段
poetry.setContent(segment);
return poetry;
}).toList();
list2.forEach(System.out::println);
}
}
9.将数据存入redis里面
添加一个测试文件
package com.axjy.poetry;
import com.axjy.poetry.pojo.Poetry;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.time.LocalDateTime;
@SpringBootTest
public class RedisTest {
@Resource
private RedisTemplate redisTemplate;
//将数据存储到redis当中
@Test
void save(){
Poetry poetry = new Poetry();
poetry.setName("李白");
poetry.setUpdateTime(LocalDateTime.now());
ValueOperations valueOperations = redisTemplate.opsForValue();
valueOperations.set("jp:1001",poetry);
}
//取出redis里的数据
@Test
void get(){
ValueOperations valueOperations = redisTemplate.opsForValue();
Poetry poetry = (Poetry) valueOperations.get("jp:1001");
System.out.println(poetry);
}
}
编写后台程序了
1.添加PoetryVo类、Pager分页类、DocSource文档类
PoetryVo类:
package com.axjy.poetry.elastic;
import lombok.Data;
import java.time.LocalDate;
@Data
public class PoetryVo {
private String keyword;
private String dynasty;
private LocalDate startDate;
private LocalDate endDate;
//排序
private String sort; //updateTime
private int pageNo;
private int pageSize;
}
Pager分页类:
package com.axjy.poetry.elastic;
import lombok.Data;
import java.util.List;
@Data
public class Pager {
private List rows;
private long total;
}
DocSource文档类:
package com.axjy.poetry.elastic;
import co.elastic.clients.util.DateTime;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.time.LocalDateTime;
@Document(indexName = "poetry")
public class DocSource {
/**
* 标题
*/
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String title;
/**
* 内容
*/
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String content;
/**
* 朝代
*/
@Field(type = FieldType.Keyword)
private String dynasty;
/**
* 作者
*/
@Field(type = FieldType.Text,analyzer = "ik_smart")
private String name;
/**
* 作者描述
*/
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String intro;
@Field(type = FieldType.Date,pattern = "uuuu-MM-dd||uuuu-MM-dd HH:mm")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime updateTime;
}
2.添加service
package com.axjy.poetry.service;
import com.axjy.poetry.elastic.Pager;
import com.axjy.poetry.elastic.PoetryVo;
public interface PoetryService {
Pager query(PoetryVo vo);
}
3.添加实现service接口的impl类
package com.axjy.poetry.service;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.ScoreSort;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.HighlightField;
import co.elastic.clients.json.JsonData;
import com.axjy.poetry.elastic.Pager;
import com.axjy.poetry.elastic.PoetryVo;
import com.axjy.poetry.mapper.PoetryMapper;
import com.axjy.poetry.pojo.Poetry;
import jakarta.annotation.Resource;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.time.format.DateTimeFormatter;
import java.util.List;
@Service("poetryService")
public class PoetryServiceImpl implements PoetryService{
@Resource
private ElasticsearchClient client;
@SneakyThrows
@Override
public Pager query(PoetryVo vo) {
//动态bool构造器
BoolQuery.Builder boolBuilder = new BoolQuery.Builder();
if (StringUtils.hasLength(vo.getDynasty())){
TermQuery query = TermQuery.of(builder -> builder.field("dynasty").value(vo.getDynasty()));
boolBuilder.must(query._toQuery());
}
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
if (vo.getStartDate()!=null){
RangeQuery query = RangeQuery.of(builder -> builder.field("updateTime").gte(JsonData.of(vo.getStartDate().format(fmt))));
boolBuilder.must(query._toQuery());
}
if (vo.getEndDate()!=null){
RangeQuery query = RangeQuery.of(builder -> builder.field("updateTime").lt(JsonData.of(vo.getEndDate().plusDays(1).format(fmt))));
boolBuilder.must(query._toQuery());
}
if (StringUtils.hasLength(vo.getKeyword())){
MultiMatchQuery query = MultiMatchQuery.of(builder -> builder.fields("name^4","title^3","content").query(vo.getKeyword()));
}
Highlight.Builder hb = new Highlight.Builder();
HighlightField hf = new HighlightField.Builder().build();
hb.preTags("<strong style=\"color: red;\">").postTags("</strong>");
hb.fields("name",hf);
hb.fields("content",hf);
hb.fields("title",hf);
Highlight highlight = hb.build();
int offset = (vo.getPageNo()-1)* vo.getPageSize();
//处理排序
SortOptions.Builder sort = new SortOptions.Builder();
if (StringUtils.hasLength(vo.getSort())){
if ("updateTime".equals(vo.getSort())){
sort.field(builder -> builder.field("updateTime").order(SortOrder.Desc));
}else if (vo.getSort().equals("tuijian")){
sort.score(ScoreSort.of(builder -> builder.order(SortOrder.Desc)));
}
}else {
sort.score(ScoreSort.of(builder -> builder.order(SortOrder.Desc)));
}
//处理高亮、分页、排序
SearchResponse<Poetry> resp = client.search(builder ->
builder.index("poetry")
.highlight(highlight)
.query(boolBuilder.build()._toQuery())
.from(offset)
.size(vo.getPageSize())
.sort(sort.build())
,Poetry.class);
long total = resp.hits().total().value();
System.out.println();
Pager pager = new Pager();
pager.setTotal(total);
List<Poetry> rows = resp.hits().hits().stream().map(hit->{
Poetry poetry = hit.source();
//处理分页高亮
//是否存在高亮状态(如果分页结果包含该文本时再进行处理高亮)
if (hit.highlight().containsKey("name")){
poetry.setName(hit.highlight().get("name").get(0));
}
if (hit.highlight().containsKey("title")){
poetry.setTitle(hit.highlight().get("title").get(0));
}
if (hit.highlight().containsKey("content")){
poetry.setContent(hit.highlight().get("content").get(0));
}
return poetry;
}).toList();
pager.setRows(rows);
return pager;
}
}
4.添加Result工具类
package com.axjy.poetry.controller;
import lombok.Data;
@Data
public class Result {
private boolean success;
private Object data;
private String code;
private String img;
public static Result success(Object data){
Result result = new Result();
result.setSuccess(true);
result.setData(data);
return result;
}
}
5.最后一步,添加Controller类
package com.axjy.poetry.controller;
import com.axjy.poetry.elastic.Pager;
import com.axjy.poetry.elastic.PoetryVo;
import com.axjy.poetry.service.PoetryService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@CrossOrigin
@RestController
public class PoetryController {
@Resource
private PoetryService poetryService;
@RequestMapping("/poetry/query")
private Result query(@RequestBody PoetryVo vo){
Pager pager = poetryService.query(vo);
return Result.success(pager);
}
}
最后,前端访问/poetry/query路径就可以直接访问查询了(记得传入要进行搜索的词语)