Spring Boot接入Elasticsearch
Spring boot 接入ES
最终效果:
效果丑,也就那么意思一下;因为sping boot已经集成了es,所以接入es只需要很简单的配置一下就好了。当然事先得下载好es,我用的是windows 6.5.3版本,没有遇到什么版本冲突问题,至于安装es和建立spring boot项目我就不细说了。
代码:
pom.xml
<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-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
<scope>true</scope>
</dependency>
</dependencies>
引入需要的jar包就好
application.yml
spring:
datasource:
hikari:
jdbc-url: jdbc:mysql://url:port/database
username: username
password: password
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://url:port/database
username: username
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
thymeleaf:
cache: false
prefix: classpath:templates/
suffix: .html
mode: HTML
encoding: UTF-8
data:
elasticsearch:
cluster-name: es-test
cluster-nodes: localhost:9300
repositories:
enabled: true
main:
allow-bean-definition-overriding: true
server:
port: 80
mybatis:
type-aliases-package: com.xxx.xxx.xxx
cluster-name就是自己设置的es集群名称,可以在es安装目录下面的config/elasticsearch.yml配置
实体类
package com.xxx.xxx.model;
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;
@Document(indexName = "test", type = "company")
public class Company {
@Id
private String id;
@Field(analyzer = "ik",searchAnalyzer = "ik",store = true,type = FieldType.Text)
private String cname;
private String ename;
private String iname;
private String scode;
private String amt;
private String person;
@Field
private Date btime;
@Field
private Date sbtime;
@Field
private Date ptime;
private String address;
private String tel;
private String fax;
private String web;
private String email;
private String sec;
@Field(analyzer = "ik",searchAnalyzer = "ik",store = true,type = FieldType.Text)
private String industry;
private String business;
private String recommend;
private String over;
private Date ctime;
@Field(analyzer = "ik",searchAnalyzer = "ik",store = true,type = FieldType.Text)
private String industry2;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id == null ? null : id.trim();
}
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname == null ? null : cname.trim();
}
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename == null ? null : ename.trim();
}
public String getIname() {
return iname;
}
public void setIname(String iname) {
this.iname = iname == null ? null : iname.trim();
}
public String getScode() {
return scode;
}
public void setScode(String scode) {
this.scode = scode == null ? null : scode.trim();
}
public String getAmt() {
return amt;
}
public void setAmt(String amt) {
this.amt = amt == null ? null : amt.trim();
}
public String getPerson() {
return person;
}
public void setPerson(String person) {
this.person = person == null ? null : person.trim();
}
public Date getBtime() {
return btime;
}
public void setBtime(Date btime) {
this.btime = btime;
}
public Date getSbtime() {
return sbtime;
}
public void setSbtime(Date sbtime) {
this.sbtime = sbtime;
}
public Date getPtime() {
return ptime;
}
public void setPtime(Date ptime) {
this.ptime = ptime;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address == null ? null : address.trim();
}
public String getTel() {
return tel;
}
public void setTel(String tel) {
this.tel = tel == null ? null : tel.trim();
}
public String getFax() {
return fax;
}
public void setFax(String fax) {
this.fax = fax == null ? null : fax.trim();
}
public String getWeb() {
return web;
}
public void setWeb(String web) {
this.web = web == null ? null : web.trim();
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email == null ? null : email.trim();
}
public String getSec() {
return sec;
}
public void setSec(String sec) {
this.sec = sec == null ? null : sec.trim();
}
public String getIndustry() {
return industry;
}
public void setIndustry(String industry) {
this.industry = industry == null ? null : industry.trim();
}
public String getBusiness() {
return business;
}
public void setBusiness(String business) {
this.business = business == null ? null : business.trim();
}
public String getRecommend() {
return recommend;
}
public void setRecommend(String recommend) {
this.recommend = recommend == null ? null : recommend.trim();
}
public String getOver() {
return over;
}
public void setOver(String over) {
this.over = over == null ? null : over.trim();
}
public Date getCtime() {
return ctime;
}
public void setCtime(Date ctime) {
this.ctime = ctime;
}
public String getIndustry2() {
return industry2;
}
public void setIndustry2(String industry2) {
this.industry2 = industry2 == null ? null : industry2.trim();
}
}
@Id注解就是插入到es中当id的字段,@Field表明你想要查询的字段,中文字段用到ik分词器,如果不想被解析可以设置type=FieldType.Keyword。我的数据是同事从网上扒下来的,所以字段看上去有点奇怪,另外这实体类是用mybatis generator逆向生成的。
Repository接口
package com.xxx.xxx.mapper;
import com.jyw.es.model.Company;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**
* @author jiangyw
* @date 2019/1/22 10:17
*/
public interface ICompanyRepository extends ElasticsearchRepository<Company, String> {
}
这就是crud操作的接口,可以自定义一些方法,类似于mybatis的mapper接口
Controller
package com.xxx.xxx.controller;
import com.alibaba.fastjson.JSONObject;
import com.jyw.es.model.Company;
import com.jyw.es.service.ICompany;
import com.jyw.es.mapper.ICompanyRepository;
import org.apache.lucene.document.Field;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
/**
* @author jiangyw
* @date 2019/1/18 20:02
*/
@RestController
@RequestMapping("company")
public class CompanyController {
@Resource
private ICompany companyService;
@Resource
private ICompanyRepository companyRepository;
@Resource
private ElasticsearchTemplate elasticsearchTemplate;
@GetMapping("all")
public String all() {
List<Company> list = companyService.listAll();
return JSONObject.toJSONString(list);
}
/**
* 把数据库里的数据导入es中
*/
@GetMapping("save")
public String save() {
List<Company> list = companyService.listAll();
Iterable<Company> companies = companyRepository.saveAll(list);
return JSONObject.toJSONString(companies);
}
/**
* 搜索
*
* @param keyword keyword
*/
@PostMapping("search")
public String search(@RequestParam("keyword") String keyword) {
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.should(QueryBuilders.matchQuery("cname", keyword));
queryBuilder.should(QueryBuilders.matchQuery("industry", keyword));
queryBuilder.should(QueryBuilders.matchQuery("industry2", keyword));
Pageable pageable = PageRequest.of(0, 10);
Iterable<Company> companies = companyRepository.search(queryBuilder, pageable);
return JSONObject.toJSONString(companies);
}
/**
* 搜索(高亮)
*
* @param keyword keyword
*/
@PostMapping("hsearch")
public String hSearch(@RequestParam("keyword") String keyword) {
Pageable pageable = PageRequest.of(0, 10);
BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
queryBuilder.should(QueryBuilders.matchQuery("cname", keyword));
queryBuilder.should(QueryBuilders.matchQuery("industry", keyword));
queryBuilder.should(QueryBuilders.matchQuery("industry2", keyword));
String preTag = "<font color='red'>";
String postTag = "</font>";
SearchQuery searchQuery = new NativeSearchQueryBuilder()
.withQuery(queryBuilder)
.withHighlightFields(new HighlightBuilder.Field("cname").preTags(preTag).postTags(postTag),
new HighlightBuilder.Field("industry").preTags(preTag).postTags(postTag),
new HighlightBuilder.Field("industry2").preTags(preTag).postTags(postTag)
)
.withPageable(pageable)
.build();
AggregatedPage<Company> companies = elasticsearchTemplate.queryForPage(searchQuery, Company.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<Company> list = new ArrayList<>();
for (SearchHit hit : response.getHits()) {
Company company = new Company();
HighlightField cname = hit.getHighlightFields().get("cname");
if (cname != null) {
company.setCname(cname.getFragments()[0].string());
}else {
company.setCname(hit.getSourceAsMap().get("cname").toString());
}
HighlightField industry = hit.getHighlightFields().get("industry");
if (industry != null) {
company.setIndustry(industry.getFragments()[0].toString());
}else {
company.setCname(hit.getSourceAsMap().get("industry").toString());
}
HighlightField industry2 = hit.getHighlightFields().get("industry2");
if (industry2 != null) {
company.setIndustry2(industry2.getFragments()[0].toString());
}else {
company.setCname(hit.getSourceAsMap().get("industry2").toString());
}
list.add(company);
}
return new AggregatedPageImpl<>((List<T>) list);
}
});
return JSONObject.toJSONString(companies);
}
}
就这几个简单的接口……先调用一下save接口,把MySQL数据中的数据存入es,可以用kibana查看数据:
可以看到数据已经成功导入了
前端代码
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!-- 最新版本的 Bootstrap 核心 CSS 文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<!-- 可选的 Bootstrap 主题文件(一般不用引入) -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
</head>
<body>
<div style="text-align: center">
<input id="keyword" placeholder="搜索">
<button id="button" type="button" onclick="search()">搜索</button>
</div>
<div style="text-align: center;height: auto;">
<ul id="ul">
</ul>
</div>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<!-- 最新的 Bootstrap 核心 JavaScript 文件 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script>
function search() {
var keyword = $("#keyword").val();
if (keyword !== "") {
$.post("http://192.168.1.52/company/hsearch", {keyword: keyword}, function (res) {
console.log(res);
var ul = $("#ul");
ul.empty();
res = JSON.parse(res);
var content = res.content;
for (var i = 0; i < content.length; i++) {
ul.append('<li>' + content[i].cname
+ '<br>'
+ content[i].industry
+ '<br>'
+ content[i].industry2
+ '</li>')
}
})
}
}
</script>
</body>
</html>
这里我并没有用到Thymeleaf模板,就是普通的前端html;所以服务端要设置一下,允许跨域请求。
package com.xxx.xxx.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* 允许跨域
*
* @author jiangyw
* @date 2019/1/22 15:25
*/
@Configuration
public class MyConfig {
@Bean
public CorsFilter config() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
source.registerCorsConfiguration("/**", corsConfiguration);
return new CorsFilter(source);
}
}
搜索结果数据
{
"content": [
{
"cname": "无锡<font color='red'>易</font><font color='red'>通</font>精密<font color='red'>机</font><font color='red'>械</font>股份有限公司",
"industry": "普<font color='red'>通</font><font color='red'>机</font><font color='red'>械</font>制造行业",
"industry2": "普<font color='red'>通</font><font color='red'>机</font><font color='red'>械</font>制造行业"
},
{
"cname": "上海首达包装<font color='red'>机</font><font color='red'>械</font>材料股份有限公司",
"industry": "<font color='red'>机</font><font color='red'>械</font>行业",
"industry2": "<font color='red'>机</font><font color='red'>械</font>行业"
},
{
"cname": "上海恒浥智能科技股份有限公司",
"industry": "其他<font color='red'>通</font>用<font color='red'>机</font><font color='red'>械</font>",
"industry2": "其他<font color='red'>通</font>用<font color='red'>机</font><font color='red'>械</font>业"
},
{
"cname": "上海狮虎能源科技股份有限公司",
"industry": "普<font color='red'>通</font><font color='red'>机</font><font color='red'>械</font>制造业",
"industry2": "普<font color='red'>通</font><font color='red'>机</font><font color='red'>械</font>制造业"
},
{
"cname": "上海大椿建筑<font color='red'>机</font><font color='red'>械</font>股份有限公司",
"industry": "其他专用<font color='red'>机</font><font color='red'>械</font>(行业代码 260207)",
"industry2": "其他专用<font color='red'>机</font><font color='red'>械</font>"
},
{
"cname": "上海隆友精密刀具股份有限公司",
"industry": "<font color='red'>机</font><font color='red'>械</font>制造",
"industry2": "<font color='red'>机</font><font color='red'>械</font>制造业"
},
{
"cname": "上海申雁制冷设备股份有限公司",
"industry": "<font color='red'>机</font><font color='red'>械</font>设备",
"industry2": "<font color='red'>机</font><font color='red'>械</font>设备业"
},
{
"cname": "积康螺杆制造(上海)股份有限公司",
"industry": "<font color='red'>机</font><font color='red'>械</font>基础件",
"industry2": "<font color='red'>机</font><font color='red'>械</font>基础件"
},
{
"cname": "上海明索重型<font color='red'>机</font><font color='red'>械</font>股份有限公司",
"industry": "<font color='red'>机</font>床工具行业",
"industry2": "<font color='red'>机</font>床工具行业"
},
{
"cname": "上海裕强户外用品股份有限公司",
"industry": "<font color='red'>机</font><font color='red'>械</font>制造行业",
"industry2": "<font color='red'>机</font><font color='red'>械</font>制造行业"
}
],
"empty": false,
"facets": [
],
"first": true,
"last": true,
"maxScore": 0.0,
"number": 0,
"numberOfElements": 10,
"pageable": "INSTANCE",
"size": 0,
"sort": {
"empty": true,
"sorted": false,
"unsorted": true
},
"totalElements": 10,
"totalPages": 1
}
可以看到通过高亮搜索得到的结果中,关键词已经被套上接口中设定好的标签
项目结构
总结
就是在项目中把MySQL中的数据导入es,然后通过es分词、高亮搜索;由于spring boot已经集成好了,所以并不需要我们做多少事情……