關(guān)于注解式的分布式Elasticsearch的封裝案例
原生的Rest Level Client不好用,構(gòu)建檢索等很多重復(fù)操作。
對bboss-elasticsearch進(jìn)行了部分增強(qiáng):通過注解配合實(shí)體類進(jìn)行自動構(gòu)建索引和自動刷入文檔,復(fù)雜的業(yè)務(wù)檢索需要自己在xml中寫Dsl。用法與mybatis-plus如出一轍。
依賴
<dependency> <groupId>org.elasticsearch</groupId> <artifactId>elasticsearch</artifactId> </dependency> <dependency> <groupId>com.bbossgroups.plugins</groupId> <artifactId>bboss-elasticsearch-spring-boot-starter</artifactId> <version>5.9.5</version> <exclusions> <exclusion> <artifactId>slf4j-log4j12</artifactId> <groupId>org.slf4j</groupId> </exclusion> </exclusions> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.6</version> <scope>provided</scope> </dependency>
配置:
import com.rz.config.ElsConfig; import org.frameworkset.elasticsearch.boot.ElasticSearchBoot; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.Map; /** * 啟動時(shí)初始化bBoss * * @author sunziwen * @version 1.0 * @date 2019/12/12 16:54 **/ @Component @Order(value = 1) public class StartElastic implements ApplicationRunner { @Autowired private ElsConfig config; @Override public void run(ApplicationArguments args) throws Exception { Map properties = new HashMap(); properties.put("elasticsearch.rest.hostNames", config.getElsClusterNodes()); ElasticSearchBoot.boot(properties); } }
注解和枚舉:
package com.rz.szwes.annotations; import java.lang.annotation.*; /** * 標(biāo)識實(shí)體對應(yīng)的索引信息 * * @author sunziwen * 2019/12/13 10:14 * @version 1.0 **/ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ESDsl { /** * xml的位置 */ String value(); String indexName(); /** * elasticsearch7.x版本已經(jīng)刪除該屬性 */ String indexType() default ""; }
package com.rz.szwes.annotations; import java.lang.annotation.*; /** * 為字段指定映射類型 * * @author sunziwen * 2019/12/14 10:06 * @version 1.0 **/ @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ESMapping { //映射類型 ESMappingType value(); //加權(quán) int boost() default 1; //分詞標(biāo)識analyzed、not_analyzed String index() default "analyzed"; //分詞器ik_max_word、standard String analyzer() default "ik_max_word"; //String作為分組聚合字段的時(shí)候需要設(shè)置為true boolean fildData() default false; }
package com.rz.szwes.annotations; /** * Es映射類型枚舉(定義了大部分,有缺失請使用者補(bǔ)全)當(dāng)前版本基于elasticsearch 6.8 * * @author sunziwen * 2019/12/14 10:09 * @version 1.0 **/ public enum ESMappingType { /** * 全文搜索。 */ text("text"), /** * keyword類型適用于索引結(jié)構(gòu)化(排序、過濾、聚合),只能通過精確值搜索到。 */ keyword("keyword"), / /** * -128~127 在滿足需求的情況下,盡可能選擇范圍小的數(shù)據(jù)類型。 */ _byte("byte"), /** * -32768~32767 */ _short("short"), /** * -2^31~2^31-1 */ _integer("integer"), /** * -2^63~2^63-1 */ _long("long"), / /** * 64位雙精度IEEE 754浮點(diǎn)類型 */ _doule("doule"), /** * 32位單精度IEEE 754浮點(diǎn)類型 */ _float("float"), /** * 16位半精度IEEE 754浮點(diǎn)類型 */ half_float("half_float"), /** * 縮放類型的的浮點(diǎn)數(shù) */ scaled_float("scaled_float"), / /** * 時(shí)間類型 */ date("date"), _boolean("boolean"), /** * 范圍類型 */ range("range"), /** * 嵌套類型 */ nested("nested"), /** * 地理坐標(biāo) */ geo_point("geo_point"), /** * 地理地圖 */ geo_shape("geo_shape"), /** * 二進(jìn)制類型 */ binary("binary"), /** * ip 192.168.1.2 */ ip("ip"); private String value; ESMappingType(String value) { this.value = value; } public String getValue() { return value; } }
工具類:對HashMap進(jìn)行了增強(qiáng)
package com.rz.szwes.util; import java.util.HashMap; import java.util.function.Supplier; /** * 原始HashMap不支持Lambda表達(dá)式,特此包裝一個(gè) * * @author sunziwen * @version 1.0 * @date 2019/12/13 11:09 **/ public class LambdaHashMap<K, V> extends HashMap<K, V> { public static <K, V> LambdaHashMap<K, V> builder() { return new LambdaHashMap<>(); } public LambdaHashMap<K, V> put(K key, Supplier<V> supplier) { super.put(key, supplier.get()); //流式 return this; } }
核心類兩個(gè):
package com.rz.szwes.core; import cn.hutool.core.util.ClassUtil; import com.alibaba.fastjson.JSON; import com.frameworkset.orm.annotation.ESId; import com.rz.szwes.annotations.ESDsl; import com.rz.szwes.annotations.ESMapping; import com.rz.szwes.util.LambdaHashMap; import org.springframework.util.StringUtils; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Date; /** * 抽象類解析泛型 * * @author sunziwen * 2019/12/14 16:04 * @version 1.0 **/ public abstract class AbstractElasticBase<T> { { //初始化解析 ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass(); // 獲取第一個(gè)類型參數(shù)的真實(shí)類型 Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0]; parseMapping(clazz); } /** * 索引名稱 */ protected String indexName; /** * 索引類型 */ protected String indexType; /** * es寫dsl的文件路徑 */ protected String xmlPath; /** * 索引映射 */ protected String mapping; //將Class解析成映射JSONString private void parseMapping(Class<T> clazz) { if (clazz.isAnnotationPresent(ESDsl.class)) { ESDsl esDsl = clazz.getAnnotation(ESDsl.class); this.xmlPath = esDsl.value(); this.indexName = esDsl.indexName(); //如果類型為空,則采用索引名作為其類型 this.indexType = StringUtils.isEmpty(esDsl.indexType()) ? esDsl.indexName() : esDsl.indexType(); } else { throw new RuntimeException(clazz.getName() + "缺失注解[@ESDsl]"); } //構(gòu)建索引映射 LambdaHashMap<Object, Object> put = LambdaHashMap.builder() .put("mappings", () -> LambdaHashMap.builder() .put(indexType, () -> LambdaHashMap.builder() .put("properties", () -> { Field[] fields = clazz.getDeclaredFields(); LambdaHashMap<Object, Object> builder = LambdaHashMap.builder(); for (Field field : fields) { builder.put(field.getName(), () -> toEsjson(field)); } return builder; }))) ; this.mapping = JSON.toJSONString(put); } private LambdaHashMap<Object, Object> toEsjson(Field field) { //基本數(shù)據(jù)類型 if (ClassUtil.isSimpleTypeOrArray(field.getType())) { //對字符串做大小限制、分詞設(shè)置 if (new ArrayList<Class>(Collections.singletonList(String.class)).contains(field.getType())) { LambdaHashMap<Object, Object> put = LambdaHashMap.builder() .put("type", () -> "text") .put("fields", () -> LambdaHashMap.builder() .put("keyword", () -> LambdaHashMap.builder() .put("type", () -> "keyword") .put("ignore_above", () -> 256))); if (field.isAnnotationPresent(ESMapping.class)) { ESMapping esMapping = field.getAnnotation(ESMapping.class); //設(shè)置聚合分組 if (esMapping.fildData()) { put.put("fildData", () -> true); } //設(shè)置加權(quán) if (esMapping.boost() != 1) { put.put("boost", esMapping::boost); } //設(shè)置是否進(jìn)行分詞 if (!"analyzed".equals(esMapping.index())) { put.put("analyzed", esMapping::analyzer); } //分詞器 put.put("analyzer", esMapping::analyzer); } return put; } //設(shè)置默認(rèn)類型 return LambdaHashMap.builder().put("type", () -> { if (field.isAnnotationPresent(ESMapping.class)) { ESMapping esMapping = field.getAnnotation(ESMapping.class); return esMapping.value().getValue(); } if (new ArrayList<Class>(Arrays.asList(byte.class, Byte.class, short.class, Short.class, int.class, Integer.class, long.class, Long.class)).contains(field.getType())) { return "long"; } else if (new ArrayList<Class>(Arrays.asList(double.class, Double.class, float.class, Float.class)).contains(field.getType())) { return "double"; } else if (new ArrayList<Class>(Arrays.asList(Date.class, java.sql.Date.class, LocalDate.class, LocalDateTime.class, LocalTime.class)).contains(field.getType())) { return "date"; } else if (new ArrayList<Class>(Arrays.asList(boolean.class, Boolean.class)).contains(field.getType())) { return "boolean"; } return "text"; }); } else { //設(shè)置對象類型 LambdaHashMap<Object, Object> properties = LambdaHashMap.builder() .put("properties", () -> { Field[] fields = field.getType().getDeclaredFields(); LambdaHashMap<Object, Object> builder = LambdaHashMap.builder(); for (Field field01 : fields) { builder.put(field01.getName(), toEsjson(field01)); } return builder; }); if (field.isAnnotationPresent(ESMapping.class)) { ESMapping esMapping = field.getAnnotation(ESMapping.class); properties.put("type", esMapping.value().getValue()); } return properties; } } }
package com.rz.szwes.core; import lombok.extern.slf4j.Slf4j; import org.frameworkset.elasticsearch.boot.BBossESStarter; import org.frameworkset.elasticsearch.client.ClientInterface; import org.frameworkset.elasticsearch.client.ClientUtil; import org.springframework.beans.factory.annotation.Autowired; import java.util.*; /** * Elastic基礎(chǔ)函數(shù) * * @author sunziwen * @version 1.0 * @date 2019/12/13 9:56 **/ @Slf4j public class ElasticBaseService<T> extends AbstractElasticBase<T> { @Autowired private BBossESStarter starter; /** * Xml創(chuàng)建索引 */ protected String createIndexByXml(String xmlName) { ClientInterface restClient = starter.getConfigRestClient(xmlPath); boolean existIndice = restClient.existIndice(this.indexName); if (existIndice) { restClient.dropIndice(indexName); } return restClient.createIndiceMapping(indexName, xmlName); } /** * 自動創(chuàng)建索引 */ protected String createIndex() { ClientInterface restClient = starter.getRestClient(); boolean existIndice = restClient.existIndice(this.indexName); if (existIndice) { restClient.dropIndice(indexName); } log.debug("創(chuàng)建索引:" + this.mapping); return restClient.executeHttp(indexName, this.mapping, ClientUtil.HTTP_PUT); } /** * 刪除索引 */ protected String delIndex() { return starter.getRestClient().dropIndice(this.indexName); } /** * 添加文檔 * * @param t 實(shí)體類 * @param refresh 是否強(qiáng)制刷新 */ protected String addDocument(T t, Boolean refresh) { return starter.getRestClient().addDocument(indexName, indexType, t, "refresh=" + refresh); } /** * 添加文檔 * * @param ts 實(shí)體類集合 * @param refresh 是否強(qiáng)制刷新 */ protected String addDocuments(List<T> ts, Boolean refresh) { return starter.getRestClient().addDocuments(indexName, indexType, ts, "refresh=" + refresh); } /** * 分頁-添加文檔集合 * * @param ts 實(shí)體類集合 * @param refresh 是否強(qiáng)制刷新 */ protected void addDocumentsOfPage(List<T> ts, Boolean refresh) { this.delIndex(); this.createIndex(); int start = 0; int rows = 100; Integer size; do { List<T> list = pageDate(start, rows); if (list.size() > 0) { //批量同步信息 starter.getRestClient().addDocuments(indexName, indexType, ts, "refresh=" + refresh); } size = list.size(); start += size; } while (size > 0); } /** * 使用分頁添加文檔必須重寫該類 * * @param start 起始 * @param rows 項(xiàng)數(shù) * @return */ protected List<T> pageDate(int start, int rows) { return null; } /** * 刪除文檔 * * @param id id * @param refresh 是否強(qiáng)制刷新 * @return */ protected String delDocument(String id, Boolean refresh) { return starter.getRestClient().deleteDocument(indexName, indexType, id, "refresh=" + refresh); } /** * 刪除文檔 * * @param ids id集合 * @param refresh 是否強(qiáng)制刷新 * @return */ protected String delDocuments(String[] ids, Boolean refresh) { return starter.getRestClient().deleteDocumentsWithrefreshOption(indexName, indexType, "refresh=" + refresh, ids); } /** * id獲取文檔 * * @param id * @return */ protected T getDocument(String id, Class<T> clazz) { return starter.getRestClient().getDocument(indexName, indexType, id, clazz); } /** * id更新文檔 * * @param t 實(shí)體 * @param refresh 是否強(qiáng)制刷新 * @return */ protected String updateDocument(String id, T t, Boolean refresh) { return starter.getRestClient().updateDocument(indexName, indexType, id, t, "refresh=" + refresh); } }
寫復(fù)雜Dsl的xml:(如何寫Dsl請參考bBoss-elasticsearch文檔,用法類似mybatis標(biāo)簽)
<properties> </properties>
框架集成完畢,以下是使用示例:
定義數(shù)據(jù)模型:
package com.rz.dto; import com.frameworkset.orm.annotation.ESId; import com.rz.szwes.annotations.ESDsl; import com.rz.szwes.annotations.ESMapping; import com.rz.szwes.annotations.ESMappingType; import lombok.Data; import java.util.List; /** * 對應(yīng)elasticsearch服務(wù)器的數(shù)據(jù)模型 * * @author sunziwen * @version 1.0 * @date 2019/12/16 11:08 **/ @ESDsl(value = "elasticsearch/zsInfo.xml", indexName = "zsInfo") @Data public class ElasticZsInfoDto { @ESMapping(ESMappingType._byte) private int least_hit; private int is_must_zz; private int zs_level; private int cur_zs_ct; private int least_score_yy; private int least_score_yw; private int area_id; private String coll_name; private String coll_code; private long coll_pro_id; private int is_must_wl; private int cur_year; private int is_two; private long logo; @ESId private int id; private String area; private int college_id; private String is_must_yy; private int is_double; private int least_score_zz; private int least_score_wl; private String grade; private int is_nine; private String pro_name; private int least_score_sx; private int relevanceSort; private int pre_avg; private String is_must_dl; private String profession_code; private int least_score_sw; private String is_must_ls; private int grade_zk; private int least_score_wy; private int is_must_hx; private int profession_id; private String is_grad; private String is_must_yw; private int is_must_sw; private int least_score_ls; private int least_score_dl; private String zs_memo; private String is_must_sx; private String introduce; private int is_must_wy; private int grade_bk; private String pre_name; private int least_score_hx; private String coll_domain; private int pre_wch; private List<String> courses; }
定義服務(wù)
package com.rz.service; import com.rz.dto.ElasticZsInfoDto; import com.rz.szwes.core.ElasticBaseService; /** * 招生索引操作服務(wù) * * @author sunziwen * @version 1.0 * @date 2019/12/16 11:02 **/ public class ElasticZsInfoService extends ElasticBaseService<ElasticZsInfoDto> { }
完畢。
已經(jīng)可以進(jìn)行索引和文檔的crud操作了,至于復(fù)雜的檢索操作就需要在xml中定義了。這里只介紹了我增強(qiáng)的功能,大部分功能都在bBoss中定義好了,讀者可以去看bBoss文檔(筆者認(rèn)為的他的唯一缺陷是不能通過實(shí)體配合注解實(shí)現(xiàn)自動索引,還要每次手動指定xml位置,手動寫mapping是很痛苦的事情,特此進(jìn)行了增強(qiáng))。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
淺談SpringMVC+Spring3+Hibernate4開發(fā)環(huán)境搭建
MVC已經(jīng)是現(xiàn)代Web開發(fā)中的一個(gè)很重要的部分,本文介紹一下SpringMVC+Spring3+Hibernate4的開發(fā)環(huán)境搭建,有興趣的可以了解一下。2017-01-01java注釋轉(zhuǎn)json插件開發(fā)實(shí)戰(zhàn)詳解
這篇文章主要為大家介紹了java注釋轉(zhuǎn)json插件開發(fā)實(shí)戰(zhàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Springboot中Instant時(shí)間傳參及序列化詳解
這篇文章主要介紹了Springboot中Instant時(shí)間傳參及序列化詳解,Instant是Java8引入的一個(gè)精度極高的時(shí)間類型,可以精確到納秒,但實(shí)際使用的時(shí)候不需要這么高的精確度,通常到毫秒就可以了,需要的朋友可以參考下2023-11-11Java多線程常見案例分析線程池與單例模式及阻塞隊(duì)列
這篇文章主要介紹了多線程的常見案例,線程池(重點(diǎn))、單例模式、阻塞隊(duì)列,本文通過圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05JAVA實(shí)戰(zhàn)練習(xí)之圖書管理系統(tǒng)實(shí)現(xiàn)流程
隨著網(wǎng)絡(luò)技術(shù)的高速發(fā)展,計(jì)算機(jī)應(yīng)用的普及,利用計(jì)算機(jī)對圖書館的日常工作進(jìn)行管理勢在必行,本篇文章手把手帶你用Java實(shí)現(xiàn)一個(gè)圖書管理系統(tǒng),大家可以在過程中查缺補(bǔ)漏,提升水平2021-10-10