springboot结合elasticsearch使用的一些例子
1.ElasticsearchTemplate的基本使用
package com.test.controller; import com.test.dto.ProductDTO; import com.test.entity.Product; import com.test.utils.BeanUtils; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.index.query.MatchQueryBuilder; import org.elasticsearch.index.query.Operator; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder; import org.elasticsearch.search.sort.FieldSortBuilder; import org.elasticsearch.search.sort.SortBuilders; import org.elasticsearch.search.sort.SortOrder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.ResultsExtractor; import org.springframework.data.elasticsearch.core.query.*; import org.springframework.web.bind.annotation.*; import java.util.ArrayList; import java.util.List; import java.util.Map; import static org.elasticsearch.index.query.QueryBuilders.*; /** * 复杂的操作使用 elasticsearchTemplate * 查询具体组合可以查看 # QueryBuilders 类 * * @author test * @date 2018/7/2. */ @RestController @RequestMapping("/temp/product") public class ProductTemplateController { private final ElasticsearchTemplate elasticsearchTemplate; @Autowired public ProductTemplateController(ElasticsearchTemplate elasticsearchTemplate) { this.elasticsearchTemplate = elasticsearchTemplate; } /** * 查询所有 */ @GetMapping public Object findAll( ) { SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(matchAllQuery()).build(); return elasticsearchTemplate.queryForList(searchQuery, Product.class); } /** * 分页查询 */ @GetMapping("/page") public Object findPage(@RequestParam(required = false, defaultValue = "1") Integer page, @RequestParam(required = false, defaultValue = "10") Integer size) { SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(matchAllQuery()) .withPageable( PageRequest.of(page - 1, size, Sort.Direction.ASC, "price")) //页码是从0 开始 .build(); return elasticsearchTemplate.queryForList(searchQuery, Product.class); } /** * 单独的排序 */ @GetMapping("/sort") public Object findSort( ) { FieldSortBuilder fieldSortBuilder = SortBuilders.fieldSort("price").order(SortOrder.ASC); SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(matchAllQuery()) .withSort(fieldSortBuilder) .build(); return elasticsearchTemplate.queryForList(searchQuery, Product.class); } /** * 根据id查询 */ @GetMapping("/{id}") public Object findById(@PathVariable String id) { GetQuery getQuery = new GetQuery(); getQuery.setId(id); return elasticsearchTemplate.queryForObject(getQuery, Product.class); } /** * 根据名称查询 * 模糊查询 * <p> * { * id: "2", * name: "牙", * desc: "string", * price: 0, * producer: "string", * tags: [ * "string" * ] * }, * { * id: "1", * name: "牙膏", * desc: "string", * price: 0, * producer: "string", * tags: [ * "string" * ] * }, * { * id: "3", * name: "膏", * desc: "string", * price: 0, * producer: "string", * tags: [ * "string" * ] * } * <p> * 查询牙膏,会查询出来所有 */ @GetMapping("/find-by-name") public Object findByName(@RequestParam String name) { SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery(matchQuery("name", name)).build(); return elasticsearchTemplate.queryForList(searchQuery, Product.class); } /** * 单字段包含所有输入 * 这里只会查询出 包含整个name字段的,也就是牙膏只会查询出上面的黑人牙膏 */ @GetMapping("/contain") public Object contain(@RequestParam String name) { SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery( matchQuery("name", name) .operator(Operator.AND) //默认使用的是or .minimumShouldMatch("100%") //匹配率 ) .build(); return elasticsearchTemplate.queryForList(searchQuery, Product.class); } /** * 指定查询的字段 */ @GetMapping("/source-filter") public Object sourceFilter( ) { //只显示名字和价格 String[] include = {"name", "price"}; FetchSourceFilter fetchSourceFilter = new FetchSourceFilter(include, null); //两个参数分别是要显示的和不显示的 SearchQuery searchQuery = new NativeSearchQueryBuilder() .withSourceFilter(fetchSourceFilter) .build(); return elasticsearchTemplate.queryForList(searchQuery, Product.class); } /** * bool查询 */ @GetMapping("/bool") public Object bool( ) { SearchQuery searchQuery = new NativeSearchQueryBuilder() .withFilter( boolQuery() .must(matchQuery("price", 40)) ) .build(); return elasticsearchTemplate.queryForList(searchQuery, Product.class); } /** * filter查询 */ @GetMapping("/filter") public Object filter(@RequestParam String filed, @RequestParam String filedName, @RequestParam(required = false, defaultValue = "price") String rangeName, @RequestParam(required = false, defaultValue = "0") String range) { SearchQuery searchQuery = new NativeSearchQueryBuilder() .withFilter( boolQuery() .must(matchQuery(filed, filedName)) .filter( rangeQuery(rangeName) .gte(range) ) ) .build(); return elasticsearchTemplate.queryForList(searchQuery, Product.class); } /** * phrase查询 */ @GetMapping("/phrase") public Object phrase(@RequestParam String filed, @RequestParam String filedName) { SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery( matchPhraseQuery(filed, filedName) ) .build(); return elasticsearchTemplate.queryForList(searchQuery, Product.class); } /** * highlight查询 */ @GetMapping("/highlight") public Object highlight(@RequestParam String filed, @RequestParam String filedName) { //高亮显示的词必须是查询的词 final List<HighlightBuilder.Field> fields = new HighlightBuilder().field(filed).fields(); SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery( matchPhraseQuery(filed, filedName) ) .withHighlightFields(fields.toArray(new HighlightBuilder.Field[0])) .build(); //如果不想使用分页的写法 return elasticsearchTemplate.query(searchQuery, (ResultsExtractor<Object>) response -> { List<ProductDTO> chunk = new ArrayList<>(); for (SearchHit searchHit : response.getHits()) { //没有数据 if (response.getHits().getHits().length <= 0) { return null; } //hit转换成bean ProductDTO productDTO = BeanUtils.mapToBean(searchHit.getSourceAsMap(), new ProductDTO()); productDTO.setHighlighted(searchHit.getHighlightFields().get("name").fragments()[0].toString()); chunk.add(productDTO); } return chunk; }); // return elasticsearchTemplate.queryForPage(searchQuery, Product.class, new SearchResultMapper() { // @Override // public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) { // List<ProductDTO> chunk = new ArrayList<>(); // for (SearchHit searchHit : response.getHits()) { // if (response.getHits().getHits().length <= 0) { // return null; // } // //hit转换成bean // ProductDTO productDTO = BeanUtils.mapToBean(searchHit.getSourceAsMap(), new ProductDTO()); // productDTO.setHighlighted(searchHit.getHighlightFields().get("name").fragments()[0].toString()); // chunk.add(productDTO); // } // if (chunk.size() > 0) { // return new AggregatedPageImpl<>((List<T>) chunk); // } // return null; // } // }); } /** * 根据id删除 */ @DeleteMapping("/{id}") public void deleteById(@PathVariable String id) { elasticsearchTemplate.delete(Product.class, id); } /** * 根据指定字段删除 * 不是完全匹配,类似模糊匹配 */ @DeleteMapping("/delete-by-name/{name}") public void deleteByName(@PathVariable String name) { DeleteQuery deleteQuery = new DeleteQuery(); deleteQuery.setQuery(termQuery("name", name)); //类似模糊删除 elasticsearchTemplate.delete(deleteQuery, Product.class); } /** * 更新 */ @PutMapping public void update(@RequestBody Product product) { IndexRequest indexRequest = new IndexRequest(); Map<String, Object> map = BeanUtils.beanToMap(product); indexRequest.source(map, XContentType.JSON); //更新整个实体 // indexRequest.source("name",product.getName()); //更新指定的字段 UpdateQuery updateQuery = new UpdateQueryBuilder().withId(product.getId()) .withClass(Product.class).withIndexRequest(indexRequest).build(); elasticsearchTemplate.update(updateQuery); } /** * 批量更新 */ @PutMapping("/batch") public void updateBatch(@RequestBody List<Product> products) { List<UpdateQuery> queries = new ArrayList<>(); for (Product product : products) { IndexRequest indexRequest = new IndexRequest(); Map<String, Object> map = BeanUtils.beanToMap(product); indexRequest.source(map, XContentType.JSON); //更新整个实体 UpdateQuery updateQuery = new UpdateQueryBuilder().withId(product.getId()) .withClass(Product.class).withIndexRequest(indexRequest).build(); queries.add(updateQuery); } elasticsearchTemplate.bulkUpdate(queries); } }
Product类:
package com.test.entity; import lombok.Data; 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.io.Serializable; import java.util.List; /** * @author test * @date 2018/7/2. */ @Data @Document(indexName = "goods", type = "product") //要加,不然报空指针异常 public class Product implements Serializable { @Id private String id; private String name; private String desc; private int price; private String producer; /** * 聚合这些操作用单独的数据结构(fielddata)缓存到内存里了,需要单独开启 * 要么这里设置 * 要着通过rest配置 * PUT goods/_mapping/product * { * "properties": { * "tags": { * "type": "text", * "fielddata": true * } * } * } */ @Field(fielddata = true, type = FieldType.Text) private List<String> tags; }
ProductDTO类
package com.test.dto; import com.test.entity.Product; import lombok.Data; import lombok.EqualsAndHashCode; /** * @author test * @date 2018/7/2. */ @EqualsAndHashCode(callSuper = true) @Data public class ProductDTO extends Product { private String highlighted; }
BeanUtils类
package com.test.utils;
import com.google.common.collect.Maps;
import org.springframework.cglib.beans.BeanMap;
import java.util.Map;
/**
* @author test
* @date 2018/7/2.
*/
public class BeanUtils {
/**
* 将对象装换为map
*/
public static <T> Map<String, Object> beanToMap(T bean) {
Map<String, Object> map = Maps.newHashMap();
if (bean != null) {
BeanMap beanMap = BeanMap.create(bean);
for (Object key : beanMap.keySet()) {
map.put(key + "", beanMap.get(key));
}
}
return map;
}
/**
* 将map装换为javabean对象
*/
public static <T> T mapToBean(Map<String, Object> map, T bean) {
BeanMap beanMap = BeanMap.create(bean);
beanMap.putAll(map);
return bean;
}
}
2.ElasticsearchTemplate的统计用法
package com.test.controller; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.search.aggregations.Aggregation; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.aggregations.bucket.terms.StringTerms; import org.elasticsearch.search.aggregations.bucket.terms.Terms; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.elasticsearch.core.ElasticsearchTemplate; import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder; import org.springframework.data.elasticsearch.core.query.SearchQuery; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; import java.util.Map; import static org.elasticsearch.index.query.QueryBuilders.matchAllQuery; import static org.elasticsearch.index.query.QueryBuilders.matchQuery; /** * 练习题 * 需求1: 对名称中包含 牙膏 的商品,计算每个tag下的商品数量 * 需求2:先分组,再算每组的平均值,计算每个tag下的商品的平均价格 * 需求3: 计算每个tag下的商品的平均价格,并且按照平均价格降序排序 * 需求4: 按照指定的价格范围区间进行分组,然后在每组内再按照tag进行分组,最后再计算每组的平均价格 * * @author test * @date 2018/7/2. */ @RestController @RequestMapping("/practice") public class PracticeController { private final ElasticsearchTemplate elasticsearchTemplate; @Autowired public PracticeController(ElasticsearchTemplate elasticsearchTemplate) { this.elasticsearchTemplate = elasticsearchTemplate; } @GetMapping("/four") public Object four( ) { SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery( matchAllQuery() ) .addAggregation( AggregationBuilders.range("range_by_price") .field("price") .addRange(0, 10) .addRange(10, 20) .addRange(20, 30) .addRange(30, 100) .subAggregation( AggregationBuilders.terms("group_by_tags") .field("tags") //.order(Terms.Order.aggregation("price", false)) .subAggregation( AggregationBuilders.avg("price").field("price")) ) ) .build(); return elasticsearchTemplate.query(searchQuery, SearchResponse::toString); } @GetMapping("/three") public Object three( ) { SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery( matchAllQuery() ) .addAggregation( AggregationBuilders.terms("group_by_tags") .field("tags") //.order(Terms.Order.aggregation("price", false)) .subAggregation( AggregationBuilders.avg("price").field("price")) ) .build(); return elasticsearchTemplate.query(searchQuery, SearchResponse::toString); } @GetMapping("/two") public Object two( ) { SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery( matchAllQuery() ) .addAggregation( AggregationBuilders.terms("group_by_tags") .field("tags") .subAggregation( AggregationBuilders.avg("price").field("price")) ) .build(); //这里直接返回es原封结果到前端,实际根据需求取值 return elasticsearchTemplate.query(searchQuery, SearchResponse::toString); } @GetMapping("/one") public Object one(@RequestParam String name) { Map<String, Long> map = new HashMap<>(); SearchQuery searchQuery = new NativeSearchQueryBuilder() .withQuery( matchQuery("name", name) ) .addAggregation( AggregationBuilders.terms("all_tags") .field("tags") ) .build(); Aggregations aggregations = elasticsearchTemplate.query(searchQuery, SearchResponse::getAggregations); Aggregation aggregation = aggregations.asMap().get("all_tags"); if (aggregation == null) { return null; } StringTerms modelTerms = (StringTerms) aggregation; for (Terms.Bucket actionTypeBucket : modelTerms.getBuckets()) { //actionTypeBucket.getKey().toString()聚合字段的相应名称,actionTypeBucket.getDocCount()相应聚合结果 map.put(actionTypeBucket.getKey().toString(), actionTypeBucket.getDocCount()); } return map; } }
3.继承Repository的用法
package com.test.controller; import com.test.entity.Product; import com.test.repository.ProductRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * 使用productRepository 做基本的增删改查 * * @author test * @date 2018/7/2. */ @RestController @RequestMapping("/base/product") public class ProductBaseController { private final ProductRepository productRepository; @Autowired public ProductBaseController(ProductRepository productRepository) { this.productRepository = productRepository; } @PostMapping public void add(@RequestBody Product product) { productRepository.save(product); } @GetMapping public Object findAll( ) { return productRepository.findAll(); //根据自己的需求然后再做数据处理 } @GetMapping("/{id}") public Object findById(@PathVariable String id) { return productRepository.findById(id); } @GetMapping("/find-by-name/{name}") public Object findByName(@PathVariable String name) { return productRepository.findByName(name); } @DeleteMapping("/{id}") public void deleteById(@PathVariable String id) { productRepository.deleteById(id); } @DeleteMapping("/delete-by-name/{name}") public void deleteByName(@PathVariable String name) { productRepository.deleteByName(name); } }
ProductRepository类:
package com.test.repository; import com.test.entity.Product; import org.springframework.data.elasticsearch.repository.ElasticsearchCrudRepository; import java.util.List; /** * @author test * @date 2018/7/2. */ public interface ProductRepository extends ElasticsearchCrudRepository<Product, String> { void deleteByName(String name); List<Product> findByName(String name); }