es可以做到多字段模糊搜索,因其易用,被广大研发人员使用,最近小编就接到模糊搜索的需求,之前用过solr,通过看搜索引擎使用热度和排名,es近几年明显上升,小编公司所用也是es,那便来看看我们如何使用吧,本篇只做简单介绍es的使用方法,以及效果,关于原理小编日后补充。
es现在已有6.x版本,我们项目用的是5.5.3版本,更低版本的使用方式和高版本不太一样,这个大家注意下,可能影响到后来的使用。
一、es引入jar:
<!-- elasticsearch -->
<dependency>
<groupId>io.searchbox</groupId>
<artifactId>jest</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>5.6.13</version>
</dependency>
二、配置连接:
@Configuration
public class JestConfig {
@Value("${userName}")
private String userName;
@Value("${password}")
private String password;
@Value("${serverUri}")
private String serverUri;
private static String staticUrl;
private static String staticUserName;
private static String staticPassword;
@PostConstruct
private void init() {
JestConfig.staticUrl = serverUri;
JestConfig.staticUserName = userName;
JestConfig.staticPassword = password;
}
public static void getJestClient() throws Exception{
JestClientFactory factory = new JestClientFactory();
factory.setHttpClientConfig(new HttpClientConfig
.Builder("***")
.defaultCredentials("**", "**")
.gson(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create())
.connTimeout(5000)
.readTimeout(5000)
.multiThreaded(true)
.build());
JestClient jest= factory.getObject();
//删除索引
JestResult jr = jest.execute(new DeleteIndex.Builder("indexname").build());
System.out.println(jr.isSucceeded());
}
public static void main(String[] args) {
try {
getJestClient();
} catch (Exception e) {
e.printStackTrace();
}
}
@Bean
public JestClient getClient() {
JestClient client = null;
try {
JestClientFactory factory = new JestClientFactory();
factory.setHttpClientConfig(new HttpClientConfig
.Builder(serverUri)
.defaultCredentials(userName, password)
.gson(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create())
.connTimeout(3000)
.readTimeout(3000)
.multiThreaded(true)
.build());
client = factory.getObject();
} catch (Exception e) {
log.error("JestClient connection Exception :",e);
}
return client;
}
}
三、Jest管理es,写通用类:
@Autowired
private JestClient jestClient;
private String indexName = "indexname";
@Override
public void save(String typeName, String id, Object obj, String mappingStr) throws Exception {
createIndex();
if (!StringUtils.isEmpty(mappingStr)) {
if (!existMapping(typeName)) {
createIndexMapping(typeName, mappingStr);
}
}
Index index = new Index.Builder(obj).index(indexName).type(typeName).id(id).build();
JestResult result = jestClient.execute(index);
if (!result.isSucceeded()) {
throw new Exception(result.getErrorMessage());
}
}
@Override
public void updateById(String typeName, String id, Object obj) throws Exception {
// StringBuilder script =new StringBuilder();
// script.append("{").append( "\"doc\":").append(JSONObject.fromObject(obj)).append( "}");
// Update update = new Update.Builder(script).index(indexName).type(indexName).id(id).build();
// JestResult result = jestClient.execute(update);
Index index = new Index.Builder(obj).index(indexName).type(typeName).id(id).build();
JestResult result = jestClient.execute(index);
if (!result.isSucceeded()) {
throw new Exception(result.getErrorMessage());
}
if (!result.isSucceeded()) {
throw new Exception(result.getErrorMessage());
}
}
@Override
public void deleteById(String typeName, String id) throws Exception {
DocumentResult dr = jestClient.execute(new Delete.Builder(id).index(indexName).type(typeName).build());
if (!dr.isSucceeded() && dr.getResponseCode() != 404) {
throw new Exception(dr.getErrorMessage());
}
}
@Override
public QueryPageResEntity queryPage(String typeName, QueryPageReqEntity req) throws Exception {
QueryPageResEntity response = new QueryPageResEntity();
List list = new ArrayList();
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
if (req.getLimit() != null && req.getPage() != null) {
searchSourceBuilder.size(req.getLimit());
searchSourceBuilder.from((req.getPage() - 1) * req.getLimit());
}
List<QueryConditionEntity> matchQueryList = req.getMatchQueryList();
List<QueryConditionEntity> termsQueryList = req.getTermsQueryList();
List<QueryConditionEntity> wildcardQueryList = req.getWildcardQueryList();
List<QueryConditionEntity> shouldQueryList = req.getShouldQueryList();
List<QueryConditionEntity> sortList = req.getSortList();
if (req.isAllflag()) {
searchSourceBuilder.query(QueryBuilders.matchAllQuery());
} else {
matchQueryList.stream().forEach(m -> {
//QueryBuilders.multiMatchQuery()
QueryBuilder queryBuilder = QueryBuilders.matchQuery(m.getKey(), m.getValue());
searchSourceBuilder.query(queryBuilder);
});
termsQueryList.stream().forEach(m -> {
QueryBuilder queryBuilder = QueryBuilders.termQuery(m.getKey(), m.getValue());
searchSourceBuilder.query(queryBuilder);
});
wildcardQueryList.stream().forEach(m -> {
QueryBuilder queryBuilder = QueryBuilders.wildcardQuery(m.getKey(), "*" + m.getValue() + "*");
searchSourceBuilder.query(queryBuilder);
});
if (!shouldQueryList.isEmpty()) {
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
shouldQueryList.stream().forEach(m -> {
boolQueryBuilder.should(QueryBuilders.matchQuery(m.getKey(), m.getValue()));
});
searchSourceBuilder.query(boolQueryBuilder);
}
}
// QueryBuilder queryBuilder = QueryBuilders
// .termsQuery("author", new String[]{ "1111", "2222" });//多值完全匹配查询
// QueryBuilder queryBuilder = QueryBuilders
// .wildcardQuery("title", "*:*");//通配符和正则表达式查询
// 字段名模糊值:如 *123* ;?123*;?123?;*123?
// QueryBuilder queryBuilder = QueryBuilders
// .prefixQuery("author", "11");//前缀查询
// QueryBuilder queryBuilder = QueryBuilders
// .rangeQuery("date")
// .gte("2019-02-20T15:43:48")
// .lte("2019-02-20T15:55:45")
// .includeLower(true)
// .includeUpper(true);//区间查询
//QueryBuilder queryBuilder = QueryBuilders
// .queryStringQuery(QueryParser.escape("T:o\""));//文本检索,应该是将查询的词先分成词库中存在的词,
//然后分别去检索,存在任一存在的词即返回,查询词分词后是OR的关系。需要转义特殊字符
sortList.stream().forEach(m -> {
searchSourceBuilder.sort(m.getKey(), (SortOrder) m.getValue());
});
String query = searchSourceBuilder.toString();
Search search = new Search.Builder(query).addIndex(indexName).addType(typeName).build();
SearchResult result = jestClient.execute(search);
if (!result.isSucceeded()) {
response.setList(list);
response.setTotal(0);
return response;
}
List<Hit<Object, Void>> hits = result.getHits(req.getCls());
for (Hit<Object, Void> hit : hits) {
list.add(hit.source);
}
response.setList(list);
response.setTotal(result.getTotal().intValue());
return response;
}
private JestResult selectAllIndex() throws IOException {
Cat cat = new Cat.IndicesBuilder().build();
return jestClient.execute(cat);
}
/**
* 创建索引:如果索引不存在则创建,存在不处理
*/
private void createIndex() throws Exception {
boolean flag = existIndex();
if (!flag) {
JestResult result = jestClient.execute(new CreateIndex.Builder(indexName).build());
if (!result.isSucceeded()) {
throw new Exception(result.getErrorMessage());
}
}
}
/**
* 检查索引是否存在true false 判断
* false 不存在
* true 存在
* * @return
*/
private boolean existIndex() throws Exception {
IndicesExists indicesExists = new IndicesExists.Builder(indexName).build();
JestResult jestResult = jestClient.execute(indicesExists);
return jestResult.isSucceeded();
}
/**
* 检查映射关系是否存在
* false 不存在
* ture 存在
*
* @param typeName
* @return
* @throws Exception
*/
private boolean existMapping(String typeName) throws Exception {
GetMapping.Builder builder = new GetMapping.Builder();
builder.addIndex(indexName).addType(typeName);
JestResult jr = jestClient.execute(builder.build());
return jr.isSucceeded();
}
/***
* 创建映射
* @param typeName
* @param mappingStr
* @return
* @throws Exception
*/
private boolean createIndexMapping(String typeName, String mappingStr) throws Exception {
PutMapping putMapping = new PutMapping.Builder(indexName, typeName, mappingStr).build();
JestResult jr = jestClient.execute(putMapping);
return jr.isSucceeded();
}
/**
* 删除索引
*
* @param indexName
* @return
* @throws Exception
*/
private boolean deleteIndex(String indexName) throws Exception {
JestResult jr = jestClient.execute(new DeleteIndex.Builder(indexName).build());
return jr.isSucceeded();
}
private boolean deleteIndexType(String type)throws Exception{
JestResult jr = jestClient.execute(new DeleteIndex.Builder(indexName).type(type).build());
return jr.isSucceeded();
}
private Object getById(String indexName, String id, Class cls) throws Exception {
Get get = new Get.Builder(indexName, id).build();
JestResult result = jestClient.execute(get);
return result.getSourceAsObject(cls);
}
四、写自己的业务实现类,做字段mapping映射,然后建立索引:
@Override
public void addSearchCarModel(CarModelEasyepcModel modelEasyepc) {
try {
//建立映射后建立索引
jestService.save(indexTypeName,modelEasyepc.getModelId(),modelEasyepc,getQueryMappingStr(indexTypeName));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 建立映射
*/
private String getQueryMappingStr(String typeName) {
StringBuilder source = new StringBuilder();
source.append("{\"").append(typeName).append("\":{\"properties\":{")
.append("\"modelId\":{\"type\":\"string\"}")
.append(",\"yearPattern\":{\"type\":\"string\",\"index\":\"analyzed\",\"analyzer\":\"ik\",\"search_analyzer\": \"ik_smart\"}")
.append(",\"brandName\":{\"type\":\"string\",\"index\":\"analyzed\",\"analyzer\":\"ik\",\"search_analyzer\": \"ik_smart\"}")
.append(",\"brandId\":{\"type\":\"string\"}")
.append(",\"brandCode\":{\"type\":\"string\"}")
.append("}}}");
return source.toString();
}
存入之后,查询逻辑如下:
QueryPageReqEntity entity = new QueryPageReqEntity();
entity.setPage(1);
entity.setLimit(20);//固定取20条,不分页
try {
if (!StringUtils.isEmpty(carModelEasyepcReqModel.getModelKey())){
entity.setAllflag(false);
List<QueryConditionEntity> shouldQueryList = entity.getShouldQueryList();
QueryConditionEntity yearPattern = new QueryConditionEntity();
yearPattern.setKey("yearPattern");
yearPattern.setValue(carModelEasyepcReqModel.getModelKey());
QueryConditionEntity brandName = new QueryConditionEntity();
brandName.setKey("brandName");
cfgLevel.setValue(carModelEasyepcReqModel.getModelKey());
shouldQueryList.add(yearPattern);
shouldQueryList.add(brandName);
}
entity.setCls(CarModelEasyepcModel.class);
//entity set进去sortList todo
QueryPageResEntity res = null;
res = jestService.queryPage(indexTypeName,entity);
List list = res.getList();
List<CarModelEasyepcModel> listVo = new ArrayList<>();
list.stream().forEach(s -> {
CarModelEasyepcModel v = CarModelEasyepcModel.builder().build();
BeanUtils.copyProperties(s, v);
listVo.add(v);
});
CarModelEasyepcRespModel modelEasyepcRespModel = CarModelEasyepcRespModel.builder().model(listVo).build();
五、索引建立确认是否分词:
了解映射的建立:大家可以参考以下文章,字段指代的意思很明确:
https://www.iteye.com/blog/m635674608-2259804
“index”:"analyzed"代表字段分词,not_analyzed代表不分词;
“analyzer”:"ik"代表存入es的时候使用ik分词器;
“search_analyzer”: "ik_smart"代表查询es时分词方式;
ik_max_word:会将文本做最细粒度的拆分,例如「中华人民共和国国歌」会被拆分为「中华人民共和国、中华人民、中华、华人、人民共和国、人民、人、民、共和国、共和、和、国国、国歌」,会穷尽各种可能的组合;
ik_smart:会将文本做最粗粒度的拆分,例如「中华人民共和国国歌」会被拆分为「中华人民共和国、国歌」;
需要注意的是大家如果建立过一次映射,以后如果有什么改动千万记得删除原来的重新建立映射,否则不起作用,亲身体会,请大家注意!
六、效果:
调接口,根据“2012基本”匹配,即根据年款和品牌名称匹配,查出相应的结果。
总结:
好东西值得研究,单学会使用,不研究原理,可能遇到的某些问题自己解释不了,接下来还是需要看下原理。
加油,先尝尝甜头,再深入研究点东西,岂不乐哉?!