前言
本文整合基于Springboot2.0+,es版本6.5.4,使用spring-boot-starter-data-elasticsearch包。由于該包實(shí)際引用的是spring-data-elasticsearch,所以需要注意spring-data-elasticsearch和es版本的對應(yīng)關(guān)系,具體可在這里查看。
- 注:雖然官網(wǎng)標(biāo)明es的6.5.0+版本需要對應(yīng)
spring-data-elasticsearch的3.2.X,但由于項(xiàng)目中Springboot版本限制在2.0.3,因此spring-data-elasticsearch的版本也被限制在了3.0.8,經(jīng)過測試基本的插入查詢等功能均可以正常使用,但是否會(huì)有一些高版本的功能受到影響暫不可知。
pom依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
<relativePath/>
</parent>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
配置參數(shù)
spring:
data:
elasticsearch:
cluster-name: esCluster
cluster-nodes: 127.0.0.1:9300 #配置es節(jié)點(diǎn)信息,逗號(hào)分隔,如果沒有指定,則啟動(dòng)ClientNode(9200端口是http查詢使用的。9300集群使用。這里使用9300.)
代碼實(shí)現(xiàn)
創(chuàng)建baen對象
@Data
@Document(indexName = "testgoods", type = "goods")
public class TestGoodsBo {
@Id
private long id;
//@Field(type = FieldType.Text)
private String name;
private BigDecimal price;
private long stock;
@Version
private Long version;
}
首先創(chuàng)建bean對象,其中幾個(gè)常用注解含義如下
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Document {
String indexName();//索引庫的名稱,個(gè)人建議以項(xiàng)目的名稱命名
String type() default "";//類型,個(gè)人建議以實(shí)體的名稱命名
short shards() default 5;//默認(rèn)分區(qū)數(shù)
short replicas() default 1;//每個(gè)分區(qū)默認(rèn)的備份數(shù)
String refreshInterval() default "1s";//刷新間隔
String indexStoreType() default "fs";//索引文件存儲(chǔ)類型
}
@Document作用于類上,經(jīng)測試代碼初始化時(shí)若es中沒有對應(yīng)的索引,則會(huì)在es中創(chuàng)建一個(gè)。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@Documented
@Inherited
public @interface Field {
FieldType type() default FieldType.Auto;#自動(dòng)檢測屬性的類型
FieldIndex index() default FieldIndex.analyzed;#默認(rèn)情況下分詞
DateFormat format() default DateFormat.none;
String pattern() default "";
boolean store() default false;#默認(rèn)情況下不存儲(chǔ)原文
String searchAnalyzer() default "";#指定字段搜索時(shí)使用的分詞器
String indexAnalyzer() default "";#指定字段建立索引時(shí)指定的分詞器
String[] ignoreFields() default {};#如果某個(gè)字段需要被忽略
boolean includeInParent() default false;
}
@Field作用于屬性上,經(jīng)測試該注解的屬性有時(shí)會(huì)與現(xiàn)有的屬性沖突,造成異常,錯(cuò)誤信息如下,所以建議es中映射已建立的情況下,不要使用該注解。
Caused by: java.lang.IllegalArgumentException: Mapper for [name] conflicts with existing mapping in other types:
[mapper [name] has different [analyzer]]
@Id和@Version分別用來綁定es中的_id和_version字段。
創(chuàng)建Repository對象
public interface GoodsRepository extends ElasticsearchRepository<TestGoodsBo,Long> , PagingAndSortingRepository<TestGoodsBo,Long> {
List<TestGoodsBo> findByNameAndPrice(String name, Long price);
List<TestGoodsBo> findByNameOrPrice(String name, Long price);
Page<TestGoodsBo> findByName(String name,Pageable page);
Page<TestGoodsBo> findByNameNot(String name,Pageable page);
Page<TestGoodsBo> findByPriceBetween(long price,Pageable page);
Page<TestGoodsBo> findByNameLike(String name,Pageable page);
@Query("{\"bool\" : {\"must\" : {\"term\" : {\"message\" : \"?0\"}}}}")
Page<TestGoodsBo> findByMessage(String message, Pageable pageable);
}
es的操作主要通過自定義的Repository對象完成,該對象可以通過繼承模板接口ElasticsearchRepository<T, ID extends Serializable>實(shí)現(xiàn),該模板提供了save、findById、findAll和search等通用方法的實(shí)現(xiàn),同時(shí)還支持通過規(guī)定的名稱格式自定義操作方法,自定義的規(guī)則見上述方法名。(通過規(guī)定格式的名稱注入方法實(shí)現(xiàn)的方式非常有趣,暫時(shí)還不了解原理,后面可以研究一下源碼)
調(diào)用過程
@Slf4j
@RestController
@RequestMapping(value = "/search")
public class SearchController {
@Autowired
private GoodsRepository repository;
@Autowired
private ElasticsearchTemplate elasticsearchTemplate;
@RequestMapping(value = "/insert")
public TestGoodsBo insert(@RequestBody TestGoodsBo bo) {
repository.save(bo);
return bo;
}
@RequestMapping(value = "/get")
public TestGoodsBo get() {
Optional<TestGoodsBo> result = repository.findById(1L);
return result.get();
}
@RequestMapping(value = "/find")
public List<TestGoodsBo> find(String name,Pageable page){
return repository.findByName(name, page);
}
@GetMapping(value = "/search")
public Page<TestGoodsBo> search(String name, @PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)Pageable pageable) {
// //通過ElasticsearchTemplate實(shí)現(xiàn)
// QueryBuilder queryBuilder = QueryBuilders.matchQuery("name", name);
// SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(queryBuilder).withHighlightFields().build();
// Page<TestGoodsBo> sampleEntities = elasticsearchTemplate.queryForPage(searchQuery, TestGoodsBo.class);
// //Pageable對象的手動(dòng)實(shí)現(xiàn)
// Sort sort = new Sort(Sort.Direction.ASC,"name");
// Pageable page = PageRequest.of(0,10,sort);
Page<TestGoodsBo> sampleEntities = repository.search(QueryBuilders.matchQuery("name", name),pageable);
return sampleEntities;
}
}
Controller的實(shí)現(xiàn)比較簡單,以從es搜索文檔為例,可以通過注入之前的Repository對象或者是注入ElasticsearchTemplate對象實(shí)現(xiàn),查詢規(guī)則不復(fù)雜的情況下前者更為簡單一些,具體實(shí)現(xiàn)看上述代碼片段便能理解。
說明:這里要特別說明的一點(diǎn)是經(jīng)過我的測試
findByName相較于search方法有一定的局限性,比如我的es設(shè)置了ik和pinyin混合的分詞器時(shí),中文搜索兩者都沒問題,但使用拼音首字母搜索只有后者能搜索到結(jié)果,所以選擇使用哪個(gè)方法需要慎重。
Pageable對象
然后要重點(diǎn)講解一下Pageable類型的對象,該對象可以幫助我們完成分頁和排序操作,有手動(dòng)和自動(dòng)兩種方式實(shí)現(xiàn)。
- 手動(dòng)方式
Sort sort = new Sort(Sort.Direction.ASC,"name");
Pageable page = PageRequest.of(0,10,sort);
- 自動(dòng)方式
@GetMapping(value = "/search")
public Page<TestGoodsBo> search(String name, @PageableDefault(value = 15, sort = { "id" }, direction = Sort.Direction.DESC)Pageable pageable)
自動(dòng)方式可以在request傳參的同時(shí)就根據(jù)傳入的參數(shù)來組裝Pageable對象,同時(shí)還能使用@PageableDefault注解設(shè)定默認(rèn)值,因此更推薦使用。Spring支持的request參數(shù)如下:
- page,第幾頁,從0開始,默認(rèn)為第0頁
- size,每一頁的大小,默認(rèn)為20
- sort,排序相關(guān)的信息,以property,property(,ASC|DESC)的方式組織,例如sort=firstname&sort=lastname,desc表示在按firstname正序排列基礎(chǔ)上按lastname倒序排列