之前一直没有写博客的习惯,但看到无数小伙伴都有自己的博客,加上最近开始做微服务的实战项目了,缕缕续续的会搭建一些环境,心里有点蠢蠢欲动.....记录下几周前搭建的过程,先从es开始写,照着别人的从环境搭建到postman测试踩了不少坑。
centos7上的环境搭建:
1. docker pull elasticsearch:6.4.0
2. 运行es
docker run -d --name es -p 9200:9200 -p 9300:9300 -e ES_JAVA_OPTS="-Xms300m -Xmx300m" -e "discovery.type=single-node" docker.io/elasticsearch:6.4.0
-xms 初始内存 -xmx最大内存 ,我的是学生机1核2G之前按照默认的来会爆炸.....
3. 进入es修改配置文件:
docker
exec
-it es
/bin/bash
cd
/usr/share/elasticsearch/config/
vi
elasticsearch.yml
cluster.name: recommend
network.host: 0.0.0.0 //不写0.0.0.0有可能在idea上报错,玄学问题.
http.port: 9200
transport.tcp.port: 9300
node.master: true
node.data: true
discovery.zen.ping.unicast.hosts: ["xx.xxx.xxx.xxx:9300"]
discovery.zen.minimum_master_nodes: 1
node.ingest: true
bootstrap.memory_lock: false
node.max_local_storage_nodes: 2
http.cors.enabled: true
http.cors.allow-origin: /.*/
4.安装ik分词器
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v6.4.0/elasticsearch-analysis-ik-6.4.0.zip
重启下es
es环境搭建好了,开始在idea上配置
1.导入依赖
<dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> <version>6.4.0</version> </dependency> <dependency> <groupId>org.elasticsearch.client</groupId> <artifactId>transport</artifactId> <version>6.4.0</version> </dependency>
2. application.yml的配置
3.获取yml中的配置信息:
@Component @ConfigurationProperties(prefix = "elasticsearch") @Data public class ESProperties { private String ip; private String port; private String pool; private String clusterName; private String nodeName; }
4.配置类:
@Configuration public class ESConfig { @Autowired private ESProperties esProperties; /** * @author 刘其佳 * @description * 创建并初始化TransportClient对象,使用该对象对ES进行增删查改 * cluster.name:集群名字 * node.name:节点名字 * client.transport.sniff:客户端(Java项目)一致监视ES的节点状态(节点数),不再需要手动添加节点,如果有新的节点产生了,会自动加载进项目中 * thread_pool.search.size:线程池 * * @param * param *: * @date 2019/9/18 * @return org.elasticsearch.client.transport.TransportClient * @throws */ @Bean("transportClient") public TransportClient getTransportClient(){ //1、创建TransportClient对象 TransportClient transportClient=null; try{ //2、设置Java对ES的集群信息 Settings settings=Settings.builder().put("cluster.name",esProperties.getClusterName()) .put("node.name", esProperties.getNodeName()) 之前按照别人的配置的,一直报错,花了一上午的时间,才注意到这里要为false,搭建es集群,为true .put("client.transport.sniff", false) .put("thread_pool.search.size", esProperties.getPool()).build(); //3、初始化TransportClient对象 transportClient= new PreBuiltTransportClient(settings); //4、配置对ES的连接信息 TransportAddress transportAddress=new TransportAddress(InetAddress.getByName(esProperties.getIp()),Integer.parseInt(esProperties.getPort())); //5、把对ES的连接对象放到transportClient对象中 transportClient.addTransportAddress(transportAddress); }catch (UnknownHostException e){ e.printStackTrace(); } return transportClient; } }
5.工具类
import com.recommend.common.dto.Result; import org.apache.commons.lang3.StringUtils; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.action.admin.indices.create.CreateIndexResponse; import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequest; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse; import org.elasticsearch.action.delete.DeleteResponse; import org.elasticsearch.action.get.GetRequestBuilder; import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.update.UpdateRequest; import org.elasticsearch.action.update.UpdateResponse; import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.text.Text; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.SortOrder; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; import javax.annotation.Resource; import java.lang.reflect.Field; import java.util.*; public class ESUtil { @Resource private TransportClient client; /** * 创建索引 * * @param index * @return */ public void createIndex(String index) { // isIndexExist:判断索引是否存在 if (isIndexExist(index)) { throw new RuntimeException("重复创建索引"); } CreateIndexResponse indexresponse = client.admin().indices().prepareCreate(index).execute().actionGet(); // indexresponse.isAcknowledged():创建索引是否成功,return Boolean类型(true:表示成功,false:失败) if(!indexresponse.isAcknowledged()) { throw new RuntimeException("创建索引失败"); } } /** * 删除索引 * * @param index * @return */ public void deleteIndex(String index) { if (!isIndexExist(index)) { throw new RuntimeException("索引不存在"); } DeleteIndexResponse dResponse = client.admin().indices().prepareDelete(index).execute().actionGet(); if (!dResponse.isAcknowledged()) { throw new RuntimeException("删除索引失败"); } } /** * 判断索引是否存在 * * @param index * @return */ public boolean isIndexExist(String index) { IndicesExistsResponse inExistsResponse = client.admin().indices().exists(new IndicesExistsRequest(index)).actionGet(); return inExistsResponse.isExists(); } /** * @Author: LX * @Description: 判断index下指定type是否存在 * @Date: // : * @Modified by: */ public boolean isTypeExist(String index, String type) { return isIndexExist(index) ? client.admin().indices().prepareTypesExists(index).setTypes(type).execute().actionGet().isExists() : false; } /** * 数据添加,正定ID * * @param mapObj 要增加的数据 * @param index 索引,类似数据库 * @param type 类型,类似表 * @param id 数据ID * @return */ public void addData(Map<String, Object> mapObj, String index, String type, String id) { IndexResponse response = client.prepareIndex(index, type, id).setSource(mapObj).get(); // response.getId():就是添加数据后ES为这条数据所生成的id // 需要返回添加数据是否成功 String status = response.status().toString(); // 添加数据后所返回的状态(如果成功就是code:-->OK) // eq:sacii --> 小写字母和大写字母不一样 // status:-->OK // ok if("OK".equals(status.toUpperCase())||"CREATED".equals(status.toUpperCase())) { } else { throw new RuntimeException("添加数据失败"); } } /** * 数据添加 * * @param mapObj 要增加的数据 * @param index 索引,类似数据库 * @param type 类型,类似表 * @return */ public void addData(Map<String, Object> mapObj, String index, String type) { addData(mapObj,index, type, UUID.randomUUID().toString().replaceAll("-", "").toUpperCase()); } /** * @author 刘其佳 * @description * 将对象转化为map类型 * @param * param *:object * @date // * @return java.util.Map<java.lang.String,java.lang.Object> * @throws */ public Map<String, Object> objectTurnMap(Object object){ Map<String, Object> result = new HashMap<String, Object>(); //获得类的属性名 数组 Field[] fields = object.getClass().getDeclaredFields(); try { for (Field field : fields) { field.setAccessible(true); String name = new String(field.getName()); result.put(name, field.get(object)); } }catch (Exception e){ e.printStackTrace(); } return result; } /** * 通过ID删除数据 * * @param index 索引,类似数据库 * @param type 类型,类似表 * @param id 数据ID */ public void deleteDataById(String index, String type, String id) { DeleteResponse response = client.prepareDelete(index, type, id).execute().actionGet(); if(!"OK".equals(response.status().toString().toUpperCase())) { throw new RuntimeException("删除数据失败或没有该数据"); } } /** * 通过ID 更新数据 * * @param mapObj 要增加的数据 * @param index 索引,类似数据库 * @param type 类型,类似表 * @param id 数据ID * @return */ public void updateDataById(Map<String, Object> mapObj, String index, String type, String id) { UpdateRequest updateRequest = new UpdateRequest(); updateRequest.index(index).type(type).id(id).doc(mapObj); ActionFuture<UpdateResponse> update = client.update(updateRequest); if(!"OK".equals(update.actionGet().status().toString().toUpperCase())) { throw new RuntimeException("更新数据失败"); } } /** * 通过ID获取数据 * * @param index 索引,类似数据库 * @param type 类型,类似表 * @param id 数据ID * @param fields 需要显示的字段,逗号分隔(缺省为全部字段) * @return */ public Map<String, Object> searchDataById(String index, String type, String id, String fields) { GetRequestBuilder getRequestBuilder = client.prepareGet(index, type, id); if (StringUtils.isNotEmpty(fields)) { getRequestBuilder.setFetchSource(fields.split(","), null); } GetResponse getResponse = getRequestBuilder.execute().actionGet(); return getResponse.getSource(); } /** * 使用分词查询 * * @param index 索引名称 * @param type 类型名称,可传入多个type逗号分隔 * @param query 查询条件 * @param size 文档大小限制 * @param fields 需要显示的字段,逗号分隔(缺省为全部字段) * @param sortField 排序字段 * @param highlightField 高亮字段 * @return */ public List<Map<String, Object>> searchListData(String index, String type, QueryBuilder query, Integer size, String fields, String sortField, String highlightField) { SearchRequestBuilder searchRequestBuilder = client.prepareSearch(index); if (StringUtils.isNotEmpty(type)) { searchRequestBuilder.setTypes(type.split(",")); } if (StringUtils.isNotEmpty(highlightField)) { HighlightBuilder highlightBuilder = new HighlightBuilder(); // 设置高亮字段 highlightBuilder.field(highlightField); searchRequestBuilder.highlighter(highlightBuilder); } searchRequestBuilder.setQuery(query); if (StringUtils.isNotEmpty(fields)) { searchRequestBuilder.setFetchSource(fields.split(","), null); } searchRequestBuilder.setFetchSource(true); if (StringUtils.isNotEmpty(sortField)) { searchRequestBuilder.addSort(sortField, SortOrder.DESC); } if (size != null && size > 0 ) { searchRequestBuilder.setSize(size); } //打印的内容 可以在 Elasticsearch head 和 Kibana 上执行查询 SearchResponse searchResponse = searchRequestBuilder.execute().actionGet(); long totalHits = searchResponse.getHits().totalHits; long length = searchResponse.getHits().getHits().length; if (searchResponse.status().getStatus() == 200 ) { // 解析对象 return setSearchResponse(searchResponse, highlightField); } return null; } /** * 高亮结果集 特殊处理 * * @param searchResponse * @param highlightField */ private List<Map<String, Object>> setSearchResponse(SearchResponse searchResponse, String highlightField) { List<Map<String, Object>> sourceList = new ArrayList<Map<String, Object>>(); StringBuffer stringBuffer = new StringBuffer(); for (SearchHit searchHit : searchResponse.getHits().getHits()) { searchHit.getSourceAsMap().put("id", searchHit.getId()); if (StringUtils.isNotEmpty(highlightField)) { // System.out.println("遍历 高亮结果集,覆盖 正常结果集" + searchHit.getSourceAsMap()); Text[] text = searchHit.getHighlightFields().get(highlightField).getFragments(); if (text != null) { for (Text str : text) { stringBuffer.append(str.string()); } //遍历 高亮结果集,覆盖 正常结果集 searchHit.getSourceAsMap().put(highlightField, stringBuffer.toString()); } } sourceList.add(searchHit.getSourceAsMap()); } return sourceList; } }
5.测试
实体类和mapper懒得放上来了,只要把article和mapper换成对应的操作就行,拿的是之前社团的接口做的测试....
使用之前要在启动类bean注入esutil
sevice层
@Service @Transactional public class TestEsServiceImpl implements TestEsService { @Autowired private ArticleMapper articleMapper; @Autowired private ESUtil esUtil; @Override public void createIndex(String index) { esUtil.createIndex(index); } @Override public void deleteIndex(String index) { esUtil.deleteIndex(index); } @Override public Result addDataBySql(String id) { Example example = new Example(Article.class); Example.Criteria criteria = example.createCriteria(); criteria.andEqualTo("id", id); Article article = articleMapper.selectByExample(example).get(0); if(article!=null){ Map<String, Object> dataMap=new HashMap<String, Object>(); dataMap.put("id",article.getId()); dataMap.put("title",article.getTitle()); dataMap.put("createtime",article.getCreatetime()); dataMap.put("hearts",article.getHearts()); esUtil.addData(dataMap,"article","game",id); return Result.ok(); }else{ return Result.not_ok("不存在该文章数据"); } } @Override public Result addAllData(String index) { List<Article> articlesList = articleMapper.selectAll(); if(articlesList.size()>0){ for (Article article : articlesList) { Map<String , Object> mapObj=esUtil.objectTurnMap(article); esUtil.addData(mapObj,index,"game",article.getId()); } } return Result.ok(); } @Override public Result deleteDataById(String id) { esUtil.deleteDataById("article", "game", id); return Result.ok(); } /** QueryBuilder:定义了查询条件(是全部查询 还是模糊查询 还是分页查询。。。。) size:所要查询出的条数 field:所查询的字段(如果查询所有就直接写null) sortField:id,age...(根据字段进行排序,如果不需要设置则传null) highlightField:把搜索关键字进行高亮显示(如果不需要则传null) **/ @Override public List<Map<String, Object>> selectAll() { //1、创建QueryBuilder对象(BoolQueryBuilder是Builder的实现类) BoolQueryBuilder boolQueryBuilder= QueryBuilders.boolQuery(); //2、创建所要搜索到额条件(查询所有数据) MatchAllQueryBuilder matchAllQueryBuilder= QueryBuilders.matchAllQuery(); //3、把搜索的条件放入到BoolQueryBuilder中 BoolQueryBuilder must = boolQueryBuilder.must(matchAllQueryBuilder); //4、返回 return esUtil.searchListData("article","game",must,100,null,null,null); } @Override public Map<String, Object> selectOneById(String id) { return esUtil.searchDataById("article","game",id,null); } @Override public List<Map<String, Object>> selectLikeAll(String title) { //1、创建QueryBuilder对象 BoolQueryBuilder boolQueryBuilder=QueryBuilders.boolQuery(); //2、创建查询条件 // matchPhraseQuery有两个参数: //name:字段名字 //text:所需要模糊匹配的值(也就是SQL语句中like后面所匹配的值) // MatchPhraseQueryBuilder matchPhraseQueryBuilder=QueryBuilders.matchPhraseQuery("username","zhang"); MatchPhraseQueryBuilder matchPhraseQueryBuilder=QueryBuilders.matchPhraseQuery("title",title); //3、把查询条件放到BoolQueryBuilder对象中 BoolQueryBuilder must=boolQueryBuilder.must(matchPhraseQueryBuilder); return esUtil.searchListData("article","game",must,10,null,null,"title"); }
controller层:
@RestController @RequestMapping("/es") public class TestEsController { @Autowired private TestEsService testEsService; @PostMapping("/createIndex/{index}") public Result createIndex(@PathVariable String index) { testEsService.createIndex(index); return Result.ok(); } @DeleteMapping("/deleteIndex/{index}") public Result deleteIndex(@PathVariable String index) { testEsService.deleteIndex(index); return Result.ok(); } @PostMapping("/data") public Result addData() { testEsService.addData(); return Result.ok(); } @PostMapping("/data/{articleId}") public Result addDataBySql(@PathVariable String articleId) { testEsService.addDataBySql(articleId); return Result.ok(); } @PostMapping("/data/all/{index}") public Result addAllArticleToIndex(@PathVariable String index) { testEsService.addAllData(index); return Result.ok(); } @DeleteMapping("/delete/{articleId}") public Result deleteDataById(@PathVariable String articleId) { testEsService.deleteDataById(articleId); return Result.ok(); } @GetMapping("/select") public Result selectAll() { List<Map<String, Object>> maps = testEsService.selectAll(); return Result.ok(maps); } @GetMapping("/select/{articleId}") public Result selectByOne(@PathVariable String articleId) { Map<String, Object> map = testEsService.selectOneById(articleId); return Result.ok(map); } @GetMapping("/select/like/{title}") public Result selectLike(@PathVariable String title) { List<Map<String, Object>> maps = testEsService.selectLikeAll(title); return Result.ok(maps); } }
最后在postman测试
先创建索引,我之前已经创建过了..
加入数据
搜索文章.
待优化的问题
1.es 7.x已经移除了type,封装esutil的工具包依旧有用type,待更新
2. es依赖的包中提供的TransportClient,将在未来es 8.x移除
3.可采用springboot提供的es简便方式
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency>
类似Jpa的操作,和封装es提供的接口,各有利弊....
参考了无数大佬的文章,才终于爬出一个个坑.....
end.