關(guān)于注解式的分布式Elasticsearch的封裝案例
原生的Rest Level Client不好用,構(gòu)建檢索等很多重復(fù)操作。
對(duì)bboss-elasticsearch進(jìn)行了部分增強(qiáng):通過(guò)注解配合實(shí)體類(lèi)進(jìn)行自動(dòng)構(gòu)建索引和自動(dòng)刷入文檔,復(fù)雜的業(yè)務(wù)檢索需要自己在xml中寫(xiě)Dsl。用法與mybatis-plus如出一轍。
依賴(lài)
<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;
/**
* 啟動(dòng)時(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í)實(shí)體對(duì)應(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.*;
/**
* 為字段指定映射類(lèi)型
*
* @author sunziwen
* 2019/12/14 10:06
* @version 1.0
**/
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ESMapping {
//映射類(lèi)型
ESMappingType value();
//加權(quán)
int boost() default 1;
//分詞標(biāo)識(shí)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映射類(lèi)型枚舉(定義了大部分,有缺失請(qǐng)使用者補(bǔ)全)當(dāng)前版本基于elasticsearch 6.8
*
* @author sunziwen
* 2019/12/14 10:09
* @version 1.0
**/
public enum ESMappingType {
/**
* 全文搜索。
*/
text("text"),
/**
* keyword類(lèi)型適用于索引結(jié)構(gòu)化(排序、過(guò)濾、聚合),只能通過(guò)精確值搜索到。
*/
keyword("keyword"),
/
/**
* -128~127 在滿(mǎn)足需求的情況下,盡可能選擇范圍小的數(shù)據(jù)類(lèi)型。
*/
_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)類(lèi)型
*/
_doule("doule"),
/**
* 32位單精度IEEE 754浮點(diǎn)類(lèi)型
*/
_float("float"),
/**
* 16位半精度IEEE 754浮點(diǎn)類(lèi)型
*/
half_float("half_float"),
/**
* 縮放類(lèi)型的的浮點(diǎn)數(shù)
*/
scaled_float("scaled_float"),
/
/**
* 時(shí)間類(lèi)型
*/
date("date"),
_boolean("boolean"),
/**
* 范圍類(lèi)型
*/
range("range"),
/**
* 嵌套類(lèi)型
*/
nested("nested"),
/**
* 地理坐標(biāo)
*/
geo_point("geo_point"),
/**
* 地理地圖
*/
geo_shape("geo_shape"),
/**
* 二進(jìn)制類(lèi)型
*/
binary("binary"),
/**
* ip 192.168.1.2
*/
ip("ip");
private String value;
ESMappingType(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
工具類(lèi):對(duì)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;
}
}
核心類(lèi)兩個(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;
/**
* 抽象類(lèi)解析泛型
*
* @author sunziwen
* 2019/12/14 16:04
* @version 1.0
**/
public abstract class AbstractElasticBase<T> {
{ //初始化解析
ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();
// 獲取第一個(gè)類(lèi)型參數(shù)的真實(shí)類(lèi)型
Class<T> clazz = (Class<T>) pt.getActualTypeArguments()[0];
parseMapping(clazz);
}
/**
* 索引名稱(chēng)
*/
protected String indexName;
/**
* 索引類(lèi)型
*/
protected String indexType;
/**
* es寫(xiě)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();
//如果類(lèi)型為空,則采用索引名作為其類(lèi)型
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ù)類(lèi)型
if (ClassUtil.isSimpleTypeOrArray(field.getType())) {
//對(duì)字符串做大小限制、分詞設(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)類(lèi)型
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è)置對(duì)象類(lèi)型
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);
}
/**
* 自動(dòng)創(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í)體類(lèi)
* @param refresh 是否強(qiáng)制刷新
*/
protected String addDocument(T t, Boolean refresh) {
return starter.getRestClient().addDocument(indexName, indexType, t, "refresh=" + refresh);
}
/**
* 添加文檔
*
* @param ts 實(shí)體類(lèi)集合
* @param refresh 是否強(qiáng)制刷新
*/
protected String addDocuments(List<T> ts, Boolean refresh) {
return starter.getRestClient().addDocuments(indexName, indexType, ts, "refresh=" + refresh);
}
/**
* 分頁(yè)-添加文檔集合
*
* @param ts 實(shí)體類(lèi)集合
* @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);
}
/**
* 使用分頁(yè)添加文檔必須重寫(xiě)該類(lèi)
*
* @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);
}
}
寫(xiě)復(fù)雜Dsl的xml:(如何寫(xiě)Dsl請(qǐng)參考bBoss-elasticsearch文檔,用法類(lèi)似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;
/**
* 對(duì)應(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)為的他的唯一缺陷是不能通過(guò)實(shí)體配合注解實(shí)現(xiàn)自動(dòng)索引,還要每次手動(dòng)指定xml位置,手動(dòng)寫(xiě)mapping是很痛苦的事情,特此進(jìn)行了增強(qiáng))。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
淺談SpringMVC+Spring3+Hibernate4開(kāi)發(fā)環(huán)境搭建
MVC已經(jīng)是現(xiàn)代Web開(kāi)發(fā)中的一個(gè)很重要的部分,本文介紹一下SpringMVC+Spring3+Hibernate4的開(kāi)發(fā)環(huán)境搭建,有興趣的可以了解一下。2017-01-01
java注釋轉(zhuǎn)json插件開(kāi)發(fā)實(shí)戰(zhàn)詳解
這篇文章主要為大家介紹了java注釋轉(zhuǎn)json插件開(kāi)發(fā)實(shí)戰(zhàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
Springboot中Instant時(shí)間傳參及序列化詳解
這篇文章主要介紹了Springboot中Instant時(shí)間傳參及序列化詳解,Instant是Java8引入的一個(gè)精度極高的時(shí)間類(lèi)型,可以精確到納秒,但實(shí)際使用的時(shí)候不需要這么高的精確度,通常到毫秒就可以了,需要的朋友可以參考下2023-11-11
Java多線程常見(jiàn)案例分析線程池與單例模式及阻塞隊(duì)列
這篇文章主要介紹了多線程的常見(jiàn)案例,線程池(重點(diǎn))、單例模式、阻塞隊(duì)列,本文通過(guò)圖文實(shí)例相結(jié)合給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-05-05
JAVA實(shí)戰(zhàn)練習(xí)之圖書(shū)管理系統(tǒng)實(shí)現(xiàn)流程
隨著網(wǎng)絡(luò)技術(shù)的高速發(fā)展,計(jì)算機(jī)應(yīng)用的普及,利用計(jì)算機(jī)對(duì)圖書(shū)館的日常工作進(jìn)行管理勢(shì)在必行,本篇文章手把手帶你用Java實(shí)現(xiàn)一個(gè)圖書(shū)管理系統(tǒng),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平2021-10-10
Java中的鍵盤(pán)事件處理及監(jiān)聽(tīng)機(jī)制解析
這篇文章主要介紹了Java中的鍵盤(pán)事件處理及監(jiān)聽(tīng)機(jī)制解析,Java事件處理采用了委派事件模型,在這個(gè)模型中,當(dāng)事件發(fā)生時(shí),產(chǎn)生事件的對(duì)象將事件信息傳遞給事件的監(jiān)聽(tīng)者進(jìn)行處理,在Java中,事件源是產(chǎn)生事件的對(duì)象,比如窗口、按鈕等,需要的朋友可以參考下2023-10-10

