随着大数据的兴起,面对越来越多的数据和越来越复杂的业务场景,系统对后端也提出了更高的要求,尤其是用户体验上,低延迟、快速响应已经成为检验后端程序是否高效很重要的标准,在后端的数据存储框架中,elasticsearch原本在海量数据的日志分析中有着较多的运用,在提升查询效率和响应方面有着不俗的表现,由于在业务中也有使用,现在对java操作es的基本使用做一些介绍,以备后续参考;
环境准备,windows或者linux上搭建的es服务器,之前有讲解过es的安装,大家可参考之前的博客试着安装一下,本次不做过多说明;
此处由于是讲述API的时候,直接上代码,我这里使用的是springboot项目,大家只需要在pom文件添加依赖即可,
org.elasticsearch
elasticsearch
6.1.1
org.elasticsearch.client
transport
6.1.1
org.elasticsearch.plugin
transport-netty4-client
6.1.1
第二步,配置一个全局加载es的bean,这样springboot启动后就可以被注入了,这里直接上代码,yml的配置,
elastic:
clusterName: test-es-cluster
ip1: 192.168.111.230
ip2: 192.168.111.231
ip3: 192.168.111.232
ip4: 192.168.111.233
port: 9300
编写一个类,作为全局的bean引用,
public class ElasticsearchConfig {
@Value("${elastic.clusterName}")
private String clusterName;
@Value("${elastic.ip1}")
private String ip1;
@Value("${elastic.ip2}")
private String ip2;
@Value("${elastic.port}")
private Integer port;
@Bean
public TransportClient elClient() throws UnknownHostException {
Settings settings = Settings.builder()
.put("cluster.name", clusterName).put("client.transport.sniff", true).build();
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new TransportAddress(InetAddress.getByName(ip1), port))
.addTransportAddress(new TransportAddress(InetAddress.getByName(ip2), port));
return client;
}
}
下面就是有关操作es的增删改查的具体代码,
1、创建索引,
private static final Logger logger = LoggerFactory.getLogger(EsCRUDtestController.class);
@Autowired
private TransportClient client;
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 创建索引
* @param name
* @param age
* @param country
* @param gid
* @return
*/
@GetMapping("/createIndex")
@ResponseBody
public ResponseEntity createIndex(String name,int age,String city){
try {
XContentBuilder content = XContentFactory.jsonBuilder()
.startObject()
.field("name",name)
.field("age",age)
.field("createDate",new Date())
.field("city",city)
.endObject();
IndexResponse result = this.client.prepareIndex("test","myes").setSource(content).get();
String index = result.getIndex();
String type = result.getType();
String id = result.getId();
logger.info("索引名称:" + index);
logger.info("type类型:" + type);
logger.info("唯一序列编号:" + id);
return new ResponseEntity(result.getId(), HttpStatus.OK);
} catch (IOException e) {
e.printStackTrace();
return new ResponseEntity(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
通过上述代码,就成功创建了一个名为"test"的索引,运行项目访问以下,可以看到结果,
注意这个地方返回的字符串意思是创建的这条记录的唯一标识,类似于mysql表中的唯一主键的概率,后面可以通过这个字符串查询某一条记录;我们可以多创建几条记录
2、查询所有,不带条件,
/**
* 不带条件
* @return
*/
@RequestMapping("/getAllData")
@ResponseBody
public Object getAllData(){
SearchResponse searchResponse = client.prepareSearch("test")
.setTypes("myes")
.setSearchType(SearchType.QUERY_THEN_FETCH)
.setQuery(QueryBuilders.matchAllQuery()) //查询所有
//.setQuery(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("name",name)))
.setExplain(true)
.setFrom(0)
.setSize(10000)
//.addSort("age", SortOrder.DESC)//排序
.execute()
.actionGet();
SearchHits hits = searchResponse.getHits();
SearchHit[] searchHits = hits.getHits();
List datas = new ArrayList<>();
//遍历,将数据取出来
for(SearchHit s : searchHits){
BasicUser user = new BasicUser();
Map single = s.getSourceAsMap();
user.setName(single.get("name").toString());
user.setCity(single.get("city").toString());
user.setAge(single.get("age").toString());
datas.add(user);
}
return datas;
}
这里查出来了所有的数据,注意,默认情况下,如果不加.setFrom(0) 和.setSize(10000)这两个参数,es查出来10条数据,最多可以一次性查询出10000条数据,这两个参数也可以作为es浅分页的设置参数,
3、带条件的查询,单个查询条件,
/**
* 带单个条件
* @return
*/
@RequestMapping("/getDataByName")
@ResponseBody
public Object getDataByName(String name){
SearchResponse searchResponse = client.prepareSearch("test")
.setTypes("myes")
.setSearchType(SearchType.QUERY_THEN_FETCH)
.setQuery(QueryBuilders.matchAllQuery()) //查询所有
.setQuery(QueryBuilders.boolQuery().must(QueryBuilders.termQuery("name",name)))
.setExplain(true)
.setFrom(0)
.setSize(10000)
//.addSort("age", SortOrder.DESC)//排序
.execute()
.actionGet();
SearchHits hits = searchResponse.getHits();
SearchHit[] searchHits = hits.getHits();
List datas = new ArrayList<>();
//遍历,将数据取出来
for(SearchHit s : searchHits){
BasicUser user = new BasicUser();
Map single = s.getSourceAsMap();
user.setName(single.get("name").toString());
user.setCity(single.get("city").toString());
user.setAge(single.get("age").toString());
datas.add(user);
}
return datas;
}
运行程序,浏览器输入访问地址,可以看到,name=mayun的这条记录被查到了
如果需要匹配多个字段,只需要在查询条件中继续拼接其他字段即可,即再添加setQuery这样的,
4、更新数据,
更新数据时,一般我们是通过唯一ID去更新比较精确,上面我们在生成数据的时候那一个字符串就是某条记录的唯一标识,但是实际业务中,我们并不知道那唯一ID,而是根据某个业务字段去更新一条数据或这条数据的多个字段,因此需要先查出唯一ID再去更新,
/**
* 更新某条记录的字段
* @param id
* @param name
* @param age
* @param city
* @return
* @throws EsException
*/
@RequestMapping("/updateResult")
@ResponseBody
public String updateResult(String name) throws EsException{
SearchResponse response = this.client.prepareSearch("test")
.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
.setTypes("myes")
//.setQuery(QueryBuilders.matchAllQuery()) //查询所有
.setQuery(QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("name",name)))
.execute().actionGet();
SearchHit[] hits = response.getHits().getHits();
if (hits.length == 0) {
throw new EsException("未获取到记录");
}
String id = hits[0].getId();
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.index("test");
updateRequest.type("myes");
updateRequest.id(id);
try {
updateRequest.doc(jsonBuilder()
.startObject()
.field("name","马云叔叔")
.field("city","苏州")
.endObject());
} catch (IOException e) {
e.printStackTrace();
}
try {
this.client.update(updateRequest).get();
logger.info("数据更新成功");
return "success";
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
logger.info("数据更新失败");
e.printStackTrace();
return "fail";
}
return null;
}
运行这段程序,可以看到成功更新,这里我们更新name=mayun的这条记录,
更新成功我们看看是否可以获取到刚刚更新后的数据,再次调用刚刚获取所有数据的接口可以看到,
说明我们队name=mayun的这条数据已经成功更新;
5、删除数据,
删除数据时候和更新类似,一般我们只想删根据某个字段删除一条或者多条记录而不知道我们生成记录时候的ID,同样需要先获取这条记录的ID值然后执行删除,当然我这里只模拟了一条,实际业务场景下可能有多条,大家只需要遍历那个hits ,将所有的ID取出来分别删除即可,下面看删除部分的代码,我们删除name=mahuateng的数据,代码如下,
/**
* 根据某字段删除
* @param name
* @return
* @throws EsException
*/
@RequestMapping("/deleteByName")
@ResponseBody
public String deleteByName(String name) throws EsException{
SearchResponse response = this.client.prepareSearch("test")
.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
.setTypes("myes")
//.setQuery(QueryBuilders.matchAllQuery()) //查询所有
.setQuery(QueryBuilders.boolQuery()
.must(QueryBuilders.termQuery("name",name)))
.execute().actionGet();
SearchHit[] hits = response.getHits().getHits();
if (hits.length == 0) {
throw new EsException("未获取到记录");
}
String id = hits[0].getId();
this.client.prepareDelete("test","myes",id).get();
return "success";
}
运行程序,浏览器输入,http://localhost:7075/myes/deleteByName?name=mahuateng
看结果,
可以看到,name = mahuateng的这条数据已经被我们成功删除
6、模糊查询,
es中的模糊查询有点特殊,在业务中使用的时候也遇到了一些坑,网上给的答案也五花八门,都未能很好的解决我的问题,一开始我的需求是想做成像mysql那样,只需要给我一个字符串,我就可以去模糊匹配数据库所有记录,遗憾的是在es中这样的实现并没有直接给出来,下面先上代码再看结果,
/**
* 模糊查询
* @param name
* @return
* @throws EsException
*/
@RequestMapping("/searcDataLike")
@ResponseBody
public Object searcDataLike(String search) throws EsException{
//拼装模糊查询的条件,根据保存的es字段匹配
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
boolQueryBuilder.should(QueryBuilders.wildcardQuery("name",("*"+search+"*")));
boolQueryBuilder.should(QueryBuilders.wildcardQuery("city",("*"+search+"*")));
SearchResponse response = client.prepareSearch("test").setTypes("myes")
.setScroll(TimeValue.timeValueMinutes(15)) //游标维持时间
.setQuery(boolQueryBuilder)
.setFrom(0)
.setSize(10000)
.setExplain(true)
.execute()
.actionGet();
SearchHit[] hits = response.getHits().getHits();
if (hits.length == 0) {
return null;
}
List datas = new ArrayList<>();
//遍历,将数据取出来
for(SearchHit s : hits){
BasicUser user = new BasicUser();
Map single = s.getSourceAsMap();
user.setName(single.get("name").toString());
user.setCity(single.get("city").toString());
user.setAge(single.get("age").toString());
datas.add(user);
}
return datas;
}
运行程序,浏览器输入:
http://localhost:7075/myes/searcDataLike?search=li
可以看到name = liyanhong的这条记录出来了,不信我们再试,
到这里,模糊查询的效果基本可以了,其实原理和mysql的模糊匹配差不多,前提是你需要给出要模糊匹配的字段啊,这样就可以匹配出es中字段name , city的数据啦!这样是不是很简单了
本次关于ES的java操作的API到此结束了,希望上述的API操作方法对各位伙伴有用,可以直接拿到工作中使用即可,更深入的像es的深度分页等内容,大家有兴趣可以在此基础上深入研究,感谢观看!