#写在前面
无聊看了看自己的博客,发现上一次更新居然是一月份的事情,而且还是自己的无厘头小说,看来是就连自己的二十岁生日也想不到有什么可以写的内容。
但是随着最近我的视频网站开发完成,想着写几篇博客记录一下自己学过的东西。
属于是代码小鬼,内行人看个笑话就好。
#过程
其实在四月份,我就完成了视频网站的基础功能的实现,选择走的极简路线,换句话说就是没那么多花里胡哨的东西,再换句话说,就是我不会整那些玩意。
在我准备开始专心优化代码的时候,前端一个不起眼的地方倒是突然吸引了我的注意,一个搜索框。
这时我才突然想起来,我的视频网站还差一个最基础的功能,那就是搜索功能,算是自己打自己的脸了。
那这个功能怎么实现呢,其实很简单,模糊查询就可以了。 说到这里,相信已经有人开始看不下去了。
模糊查询谁不会啊,总不会还有人不会吧? 这么简单就实现了,那你这篇博客写个毛线。
那肯定不会只写那么简单的内容,于是接下来就开始今天的内容
SpringBoot集成Elasticsearch
#关于Elasticsearch
首先,Elaticsearch是什么?
它是一个分布式、RESTful风格的搜索和数据分析引擎。简单来说,就是你可以把你的数据丢到上面,在上面进行搜索和数据的分析。
#如何下载Elasticsearch
https://www.elastic.co/cn/downloads/past-releases
在官网就可以下到你想要的zip包


分别是Elaticsearch的本体,以及使其可视化的Kibana。这两个都是开包即用的,所以说很方便。
下载解压之后,分别运行其bin目录下的elasticsearch.bat和kibana.bat
接着访问 http://localhost:5601
, 就可以看到Kibana的用户界面了。
#如何集成Elasticsearch
接下来讲如何讲Elaticsearch集成到我的视频网站,也就是springboot项目当中。
#在pom.xml中添加相关依赖
万变不离其宗,第一件事情肯定是为其添加相关依赖。
1 2 3 4 5
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
|
#客户端配置文件 RestClientConfig
接着是其配置文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
@Configuration public class RestClientConfig extends AbstractElasticsearchConfiguration { @Value("${es.host:localhost}") private String esHost; @Value("${es.port:9200}") private Integer esPort;
@Override @Bean public RestHighLevelClient elasticsearchClient() { final ClientConfiguration clientConfiguration = ClientConfiguration.builder() .connectedTo(esHost+":"+esPort) .build(); return RestClients.create(clientConfiguration).rest(); } }
|
记得不要忘了在application.properties上面配置你的Elasticsearch端口,默认为9200
1 2
| es.host=localhost es.port=9200
|
在domain中添加文档对象 EsVideo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| package com.makiori.server.domain.es;
import com.fasterxml.jackson.annotation.JsonFormat; 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;
import java.util.Date;
@Data @Document(indexName = "video") public class EsVideo {
@Id private String id; @Field(type = FieldType.Text, analyzer = "ik_max_word") private String title;
private String item;
private Integer time;
private String vod;
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date createdAt;
@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8") private Date updatedAt;
private String cover;
private String status;
}
|
添加EsVideoRepository接口用于操作Elasticsearch
有了类,就得想办法操作Elaticsearch, 我们通过继承ElasticsearchRepository
获得操作Elaticsearch的手段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.makiori.server.repository;
import com.makiori.server.domain.es.EsVideo; import org.springframework.data.elasticsearch.annotations.Highlight; import org.springframework.data.elasticsearch.annotations.HighlightField; import org.springframework.data.elasticsearch.annotations.Query; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface EsVideoRepository extends ElasticsearchRepository<EsVideo, String> {
@Highlight(fields = { @HighlightField(name = "title") })
@Query("{\"match\":{\"title\":\"?0\"}}") SearchHits<EsVideo> find(String keyword);
}
|
添加EsVideoService
添加EsVideoService,实现相应操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| package com.makiori.server.service.es;
import com.makiori.server.domain.es.EsVideo; import com.makiori.server.domain.Video; import com.makiori.server.mapper.my.MyVideoMapper; import com.makiori.server.repository.EsVideoRepository; import com.makiori.server.util.CopyUtil; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import java.util.Iterator; import java.util.List;
@Service public class EsVideoService {
@Resource private EsVideoRepository esVideoRepository;
@Resource private MyVideoMapper myVideoMapper;
public int importAll() { List<EsVideo> esVideoList = myVideoMapper.getAllEsVideoList(null); Iterable<EsVideo> esVideoIterable = esVideoRepository.saveAll(esVideoList); Iterator<EsVideo> iterator = esVideoIterable.iterator(); int result = 0; while(iterator.hasNext()) { result++; iterator.next(); } return result; }
public SearchHits<EsVideo> searchVideo (String keyword) {
return esVideoRepository.find(keyword); }
public void esInsert(Video video) { EsVideo esVideo = CopyUtil.copy(video, EsVideo.class); try { esVideoRepository.save(esVideo); } catch (Exception e) { e.printStackTrace(); }
}
public void esUpdate(Video video) { EsVideo esVideo = CopyUtil.copy(video, EsVideo.class);
try{ esVideoRepository.save(esVideo); } catch (Exception e) { e.printStackTrace(); }
}
public void esDelete(String id) { try{ esVideoRepository.deleteById(id); } catch (Exception e) { e.printStackTrace(); } }
}
|
添加EsVideoController定义接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package com.makiori.search.controller;
import com.makiori.server.domain.es.EsVideo; import com.makiori.server.dto.ResponseDto; import com.makiori.server.service.es.EsVideoService; import org.springframework.data.elasticsearch.core.SearchHits; import org.springframework.util.StopWatch; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController @RequestMapping("/esvideo") public class EsVideoController {
@Resource private EsVideoService esVideoService;
@PostMapping("/importAll") public ResponseDto importAllList() { ResponseDto responseDto = new ResponseDto(); int count = esVideoService.importAll(); responseDto.setContent(count); return responseDto; }
@PostMapping("/search/{key}") public ResponseDto search(@PathVariable String key) {
ResponseDto responseDto = new ResponseDto(); final StopWatch stopWatch = new StopWatch(); stopWatch.start();
final SearchHits<EsVideo> searchHits = esVideoService.searchVideo(key);
stopWatch.stop(); final double totalTimeSeconds = stopWatch.getTotalTimeSeconds(); responseDto.setContent(searchHits.getSearchHits());
return responseDto; }
}
|
学习思考
由于是第一次使用这玩意,所以第一次集成的时候并没有考虑将其分离出一个模块来,但毕竟是分布式,所以还是得想法设法将其服务分离出来。
其实现在还有一些未解决的问题,比如说如何同步搜索引擎与数据库中的数据呢。
网上看来的一个侵入性比较强的方法,就是在对数据库进行增删改查的同时,对搜索引擎也进行相同的操作。忽略侵入性的问题,如果当搜索引擎崩溃的时候,就会堆积出莫名奇妙的数据。或者像我这样项目删除添加了事务注释之后,会导致无法完成任务而使得整个软件崩溃。那么分布式就变得没有一点意义了。