之前一直沒有寫博客的習慣,但看到無數小夥伴都有自己的博客,加上最近開始做微服務的實戰項目了,縷縷續續的會搭建一些環境,心裏有點蠢蠢欲動.....記錄下幾周前搭建的過程,先從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.