如何让Elasticsearch Java Client返回泛型类型并映射子类字段
问题场景
使用Elasticsearch Java Client 8.12版本,通过以下代码执行字段查询:
public List<BaseEventBean> logs(Map<String,String> params) { SearchResponse<BaseEventBean> response = null; String[] targetFields = new String[]{ "username", "email", "user_role", "ip_address", "target_type", "country_code" }; try { response = client.search(s -> s.index(INDEX).query(q -> q.bool(b -> b.must(prepareParam(params)))). source(it -> it.filter(item -> item.includes(Arrays.asList(targetFields)))).size(10000), BaseEventBean.class); } catch (Exception e) { log.error("SEARCHING PROCESS HAS BEEN FAIL " + e.getMessage()); return null; } return response.hits().hits().stream().map(Hit::source).collect(Collectors.toList()); }
父类BaseEventBean定义:
@Getter @Setter @JsonIgnoreProperties(ignoreUnknown = true) public class BaseEventBean { @JsonProperty("id") private String id; @JsonProperty("event_type") private String eventTypes; @JsonProperty("event_description") private String eventDescription; @JsonProperty("ip_address") private String ipAddress; @JsonProperty("username") private String username; @JsonProperty("email") private String email; @JsonProperty("user_role") private String userRole; @JsonProperty("target_type") private String targetType; }
存在多个继承自BaseEventBean的子类,例如:
@Setter @Getter @JsonIgnoreProperties(ignoreUnknown = true) public class BlacklistCountriesEventBean extends BaseEventBean { @JsonProperty("country_code") private String countryCode; @JsonProperty("country_name") private String countryName; }
问题:查询时指定BaseEventBean类型,无法映射子类的额外字段;子类数量较多,无法将子类字段添加到父类中,需解决该问题,同时询问是否可以先让Elasticsearch返回原始数据再自行映射。
解决方案
方案1:获取原始JSON数据后自行映射
完全可以先获取ES返回的原始JSON,再根据业务逻辑自行反序列化为对应的子类。具体实现如下:
- 将查询泛型改为
JsonNode,拿到原始JSON数据 - 根据JSON中的标识字段(如
event_type)判断目标子类 - 使用Jackson完成反序列化
示例代码:
public List<BaseEventBean> logs(Map<String,String> params) { SearchResponse<JsonNode> response = null; String[] targetFields = new String[]{ "username", "email", "user_role", "ip_address", "target_type", "country_code", "country_name" }; ObjectMapper objectMapper = new ObjectMapper(); try { response = client.search(s -> s.index(INDEX).query(q -> q.bool(b -> b.must(prepareParam(params)))). source(it -> it.filter(item -> item.includes(Arrays.asList(targetFields)))).size(10000), JsonNode.class); } catch (Exception e) { log.error("SEARCHING PROCESS HAS BEEN FAIL " + e.getMessage()); return null; } return response.hits().hits().stream().map(hit -> { JsonNode jsonNode = hit.source(); String eventType = jsonNode.get("event_type").asText(); try { // 根据event_type匹配对应子类 switch (eventType) { case "blacklist_country": return objectMapper.treeToValue(jsonNode, BlacklistCountriesEventBean.class); // 补充其他子类的case分支 default: return objectMapper.treeToValue(jsonNode, BaseEventBean.class); } } catch (JsonProcessingException e) { log.error("Deserialization failed", e); return null; } }).filter(Objects::nonNull).collect(Collectors.toList()); }
注意:需确保targetFields包含所有子类需要的字段,避免ES返回时过滤掉必要数据。
方案2:配置Jackson多态反序列化
通过Jackson多态注解,让框架自动根据指定字段判断并反序列化为对应子类,无需手动处理。
- 在父类
BaseEventBean上添加多态注解:
@Getter @Setter @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "event_type" // 用于区分子类的标识字段 ) @JsonSubTypes({ @JsonSubTypes.Type(value = BlacklistCountriesEventBean.class, name = "blacklist_country"), // 添加其他子类的映射关系,name对应event_type的具体取值 }) public class BaseEventBean { // 原有字段保持不变 }
- 原查询代码无需修改,直接使用
BaseEventBean.class作为泛型即可:
public List<BaseEventBean> logs(Map<String,String> params) { SearchResponse<BaseEventBean> response = null; String[] targetFields = new String[]{ "username", "email", "user_role", "ip_address", "target_type", "country_code", "country_name" }; try { response = client.search(s -> s.index(INDEX).query(q -> q.bool(b -> b.must(prepareParam(params)))). source(it -> it.filter(item -> item.includes(Arrays.asList(targetFields)))).size(10000), BaseEventBean.class); } catch (Exception e) { log.error("SEARCHING PROCESS HAS BEEN FAIL " + e.getMessage()); return null; } return response.hits().hits().stream().map(Hit::source).collect(Collectors.toList()); }
该方案适合子类数量固定、event_type有明确区分值的场景,无需手动处理反序列化逻辑。
方案3:自定义Elasticsearch实体映射器
如果需要全局统一的灵活映射规则,可以自定义ES的JsonpMapper,替换默认映射器实现自定义反序列化逻辑。
示例代码:
// 配置自定义ObjectMapper,可添加多态规则、序列化特性等 ObjectMapper customMapper = new ObjectMapper(); JsonpMapper jsonpMapper = JacksonJsonpMapper.builder(customMapper).build(); // 创建ES客户端时指定自定义映射器 ElasticsearchClient client = new ElasticsearchClient( RestClient.builder(new HttpHost("localhost", 9200, "http")), jsonpMapper ); // 后续查询代码保持不变,直接使用BaseEventBean.class即可
该方案适合需要全局统一配置反序列化规则的场景。
内容的提问来源于stack exchange,提问作者abedalrazaq alshrofat
相关产品推荐
相关产品推荐

