当前位置: 首页 > article >正文

SpringBoot2集成Elasticsearch8(使用spring-boot-starter-data-elasticsearch)

写在前面

使用spring-boot-starter-data-elasticsearch集成Elasticsearch8?
What? 官方写的不支持啊?让我们来看下官方给出的版本建议。
在这里插入图片描述

官方地址:
https://docs.spring.io/spring-data/elasticsearch/reference/elasticsearch/versions.html

实际需求

大部分稳定的生产系统仍然使用的是SpringBoot2.x,即使使用最新的SpringBoot2.7.x,
也会发现使用spring-boot-starter-data-elasticsearch去连接Elasticsearch8也是连不上的。具体报错就不贴了,但是你说我们升级SpringBoot3?当然可以,但是jdk也得升级,
生产系统那种运行的稳定的一批的客户能同意,领导能同意么?显然是,很难。

深入源码

废话不多说,先说改造方案,就是我们自己使用自定义的类覆盖官方的默认实现。
案例如下:

/** Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one* or more contributor license agreements. Licensed under the Elastic License* 2.0 and the Server Side Public License, v 1; you may not use this file except* in compliance with, at your election, the Elastic License 2.0 or the Server* Side Public License, v 1.*/
package org.elasticsearch.action;import cn.hutool.core.util.StrUtil;
import org.elasticsearch.Version;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.action.support.WriteRequest.RefreshPolicy;
import org.elasticsearch.action.support.WriteResponse;
import org.elasticsearch.action.support.replication.ReplicationResponse;
import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.StatusToXContentObject;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.IndexSettings;
import org.elasticsearch.index.seqno.SequenceNumbers;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser;import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Locale;
import java.util.Objects;import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken;
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_PRIMARY_TERM;
import static org.elasticsearch.index.seqno.SequenceNumbers.UNASSIGNED_SEQ_NO;/*** A base class for the response of a write operation that involves a single doc*/
public abstract class DocWriteResponse extends ReplicationResponse implements WriteResponse, StatusToXContentObject {private static final String _SHARDS = "_shards";private static final String _INDEX = "_index";private static final String _TYPE = "_type";private static final String _ID = "_id";private static final String _VERSION = "_version";private static final String _SEQ_NO = "_seq_no";private static final String _PRIMARY_TERM = "_primary_term";private static final String RESULT = "result";private static final String FORCED_REFRESH = "forced_refresh";/*** An enum that represents the results of CRUD operations, primarily used to communicate the type of* operation that occurred.*/public enum Result implements Writeable {CREATED(0),UPDATED(1),DELETED(2),NOT_FOUND(3),NOOP(4);private final byte op;private final String lowercase;Result(int op) {this.op = (byte) op;this.lowercase = this.name().toLowerCase(Locale.ROOT);}public byte getOp() {return op;}public String getLowercase() {return lowercase;}public static Result readFrom(StreamInput in) throws IOException {Byte opcode = in.readByte();switch (opcode) {case 0:return CREATED;case 1:return UPDATED;case 2:return DELETED;case 3:return NOT_FOUND;case 4:return NOOP;default:throw new IllegalArgumentException("Unknown result code: " + opcode);}}@Overridepublic void writeTo(StreamOutput out) throws IOException {out.writeByte(op);}}private final ShardId shardId;private final String id;private final String type;private final long version;private final long seqNo;private final long primaryTerm;private boolean forcedRefresh;protected final Result result;public DocWriteResponse(ShardId shardId, String type, String id, long seqNo, long primaryTerm, long version, Result result) {this.shardId = Objects.requireNonNull(shardId);this.type = StrUtil.isEmpty(type)?"_doc":type;this.id = Objects.requireNonNull(id);this.seqNo = seqNo;this.primaryTerm = primaryTerm;this.version = version;this.result = Objects.requireNonNull(result);}// needed for deserializationprotected DocWriteResponse(ShardId shardId, StreamInput in) throws IOException {this.shardId = shardId;String typeTmp = in.readString();type =  StrUtil.isEmpty(typeTmp)?"_doc":typeTmp;;id = in.readString();version = in.readZLong();seqNo = in.readZLong();primaryTerm = in.readVLong();forcedRefresh = in.readBoolean();result = Result.readFrom(in);}/*** Needed for deserialization of single item requests in {@link org.elasticsearch.action.index.IndexAction} and BwC* deserialization path*/protected DocWriteResponse(StreamInput in) throws IOException {super(in);shardId = new ShardId(in);String typeTmp = in.readString();type =  StrUtil.isEmpty(typeTmp)?"_doc":typeTmp;;id = in.readString();version = in.readZLong();if (in.getVersion().onOrAfter(Version.V_6_0_0_alpha1)) {seqNo = in.readZLong();primaryTerm = in.readVLong();} else {seqNo = UNASSIGNED_SEQ_NO;primaryTerm = UNASSIGNED_PRIMARY_TERM;}forcedRefresh = in.readBoolean();result = Result.readFrom(in);}/*** The change that occurred to the document.*/public Result getResult() {return result;}/*** The index the document was changed in.*/public String getIndex() {return this.shardId.getIndexName();}/*** The exact shard the document was changed in.*/public ShardId getShardId() {return this.shardId;}/*** The type of the document changed.** @deprecated Types are in the process of being removed.*/@Deprecatedpublic String getType() {return this.type;}/*** The id of the document changed.*/public String getId() {return this.id;}/*** Returns the current version of the doc.*/public long getVersion() {return this.version;}/*** Returns the sequence number assigned for this change. Returns {@link SequenceNumbers#UNASSIGNED_SEQ_NO} if the operation* wasn't performed (i.e., an update operation that resulted in a NOOP).*/public long getSeqNo() {return seqNo;}/*** The primary term for this change.** @return the primary term*/public long getPrimaryTerm() {return primaryTerm;}/*** Did this request force a refresh? Requests that set {@link WriteRequest#setRefreshPolicy(RefreshPolicy)} to* {@link RefreshPolicy#IMMEDIATE} will always return true for this. Requests that set it to {@link RefreshPolicy#WAIT_UNTIL} will* only return true here if they run out of refresh listener slots (see {@link IndexSettings#MAX_REFRESH_LISTENERS_PER_SHARD}).*/public boolean forcedRefresh() {return forcedRefresh;}@Overridepublic void setForcedRefresh(boolean forcedRefresh) {this.forcedRefresh = forcedRefresh;}/** returns the rest status for this response (based on {@link ShardInfo#status()} */@Overridepublic RestStatus status() {return getShardInfo().status();}/*** Return the relative URI for the location of the document suitable for use in the {@code Location} header. The use of relative URIs is* permitted as of HTTP/1.1 (cf. https://tools.ietf.org/html/rfc7231#section-7.1.2).** @param routing custom routing or {@code null} if custom routing is not used* @return the relative URI for the location of the document*/public String getLocation(@Nullable String routing) {final String encodedIndex;final String encodedType;final String encodedId;final String encodedRouting;try {// encode the path components separately otherwise the path separators will be encodedencodedIndex = URLEncoder.encode(getIndex(), "UTF-8");encodedType = URLEncoder.encode(getType(), "UTF-8");encodedId = URLEncoder.encode(getId(), "UTF-8");encodedRouting = routing == null ? null : URLEncoder.encode(routing, "UTF-8");} catch (final UnsupportedEncodingException e) {throw new AssertionError(e);}final String routingStart = "?routing=";final int bufferSizeExcludingRouting = 3 + encodedIndex.length() + encodedType.length() + encodedId.length();final int bufferSize;if (encodedRouting == null) {bufferSize = bufferSizeExcludingRouting;} else {bufferSize = bufferSizeExcludingRouting + routingStart.length() + encodedRouting.length();}final StringBuilder location = new StringBuilder(bufferSize);location.append('/').append(encodedIndex);location.append('/').append(encodedType);location.append('/').append(encodedId);if (encodedRouting != null) {location.append(routingStart).append(encodedRouting);}return location.toString();}public void writeThin(StreamOutput out) throws IOException {super.writeTo(out);writeWithoutShardId(out);}@Overridepublic void writeTo(StreamOutput out) throws IOException {super.writeTo(out);shardId.writeTo(out);writeWithoutShardId(out);}private void writeWithoutShardId(StreamOutput out) throws IOException {out.writeString(type);out.writeString(id);out.writeZLong(version);if (out.getVersion().onOrAfter(Version.V_6_0_0_alpha1)) {out.writeZLong(seqNo);out.writeVLong(primaryTerm);}out.writeBoolean(forcedRefresh);result.writeTo(out);}@Overridepublic final XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {builder.startObject();innerToXContent(builder, params);builder.endObject();return builder;}public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {ReplicationResponse.ShardInfo shardInfo = getShardInfo();builder.field(_INDEX, shardId.getIndexName());builder.field(_TYPE, type);builder.field(_ID, id).field(_VERSION, version).field(RESULT, getResult().getLowercase());if (forcedRefresh) {builder.field(FORCED_REFRESH, true);}builder.field(_SHARDS, shardInfo);if (getSeqNo() >= 0) {builder.field(_SEQ_NO, getSeqNo());builder.field(_PRIMARY_TERM, getPrimaryTerm());}return builder;}/*** Parse the output of the {@link #innerToXContent(XContentBuilder, Params)} method.** This method is intended to be called by subclasses and must be called multiple times to parse all the information concerning* {@link DocWriteResponse} objects. It always parses the current token, updates the given parsing context accordingly* if needed and then immediately returns.*/protected static void parseInnerToXContent(XContentParser parser, Builder context) throws IOException {XContentParser.Token token = parser.currentToken();ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser);String currentFieldName = parser.currentName();token = parser.nextToken();if (token.isValue()) {if (_INDEX.equals(currentFieldName)) {// index uuid and shard id are unknown and can't be parsed back for now.context.setShardId(new ShardId(new Index(parser.text(), IndexMetadata.INDEX_UUID_NA_VALUE), -1));} else if (_TYPE.equals(currentFieldName)) {context.setType(parser.text());} else if (_ID.equals(currentFieldName)) {context.setId(parser.text());} else if (_VERSION.equals(currentFieldName)) {context.setVersion(parser.longValue());} else if (RESULT.equals(currentFieldName)) {String result = parser.text();for (Result r : Result.values()) {if (r.getLowercase().equals(result)) {context.setResult(r);break;}}} else if (FORCED_REFRESH.equals(currentFieldName)) {context.setForcedRefresh(parser.booleanValue());} else if (_SEQ_NO.equals(currentFieldName)) {context.setSeqNo(parser.longValue());} else if (_PRIMARY_TERM.equals(currentFieldName)) {context.setPrimaryTerm(parser.longValue());}} else if (token == XContentParser.Token.START_OBJECT) {if (_SHARDS.equals(currentFieldName)) {context.setShardInfo(ShardInfo.fromXContent(parser));} else {parser.skipChildren(); // skip potential inner objects for forward compatibility}} else if (token == XContentParser.Token.START_ARRAY) {parser.skipChildren(); // skip potential inner arrays for forward compatibility}}/*** Base class of all {@link DocWriteResponse} builders. These {@link DocWriteResponse.Builder} are used during* xcontent parsing to temporarily store the parsed values, then the {@link Builder#build()} method is called to* instantiate the appropriate {@link DocWriteResponse} with the parsed values.*/public abstract static class Builder {protected ShardId shardId = null;protected String type = null;protected String id = null;protected Long version = null;protected Result result = null;protected boolean forcedRefresh;protected ShardInfo shardInfo = null;protected long seqNo = UNASSIGNED_SEQ_NO;protected long primaryTerm = UNASSIGNED_PRIMARY_TERM;public ShardId getShardId() {return shardId;}public void setShardId(ShardId shardId) {this.shardId = shardId;}public String getType() {return type;}public void setType(String type) {type =  StrUtil.isEmpty(type)?"_doc":type;;}public String getId() {return id;}public void setId(String id) {this.id = id;}public void setVersion(Long version) {this.version = version;}public void setResult(Result result) {this.result = result;}public void setForcedRefresh(boolean forcedRefresh) {this.forcedRefresh = forcedRefresh;}public void setShardInfo(ShardInfo shardInfo) {this.shardInfo = shardInfo;}public void setSeqNo(long seqNo) {this.seqNo = seqNo;}public void setPrimaryTerm(long primaryTerm) {this.primaryTerm = primaryTerm;}public abstract DocWriteResponse build();}
}

写在最后

如果想要了解改的内容,可以找到官方源码对比下实现,然后自己DEBUG一下,
然后就可以知道Elasticsearch7和Elasticsearch8在连接时候的区别了。
如果要问为什么,答案就是可以自己动态创建索引,还可以指定字段类型,就是
这么香而已。

相关文章:

SpringBoot2集成Elasticsearch8(使用spring-boot-starter-data-elasticsearch)

写在前面 使用spring-boot-starter-data-elasticsearch集成Elasticsearch8? What? 官方写的不支持啊?让我们来看下官方给出的版本建议。 官方地址: https://docs.spring.io/spring-data/elasticsearch/reference/elasticsearch/versions.…...

【平台优化】持续调度参数在高负载大集群中的影响

持续调度参数在高负载大集群中的影响 背景介绍2种调度通信方式对集群的影响社区相关的讨论结论 背景介绍 这几年经历了我们大数据的Yarn集群的几次扩容,集群从原先的800多台增加到1300多台到现在的1600多台,在集群规模不断增加的过程中,有遇…...

军事级加密通信系统——基于QML的战术地图加密传输

目录 基于QML的战术地图加密传输一、引言二、理论背景与安全需求2.1 战术地图数据的敏感性与安全性要求2.2 QML与PyQt5集成优势2.3 加密算法与数据传输模型三、系统架构与数据流图四、QML前端界面设计与交互功能4.1 QML界面优势与设计理念4.2 功能要求4.3 QML文件示例五、加密传…...

ElasticSearch 可观测性最佳实践

ElasticSearch 概述 ElasticSearch 是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理 PB 级别(大数据时代)的数据。ES 也使用 Java 开…...

(一)飞行器的姿态欧拉角, 欧拉旋转, 完全数学推导(基于坐标基的变换矩阵).(偏航角,俯仰角,横滚角)

(这篇写的全是基矢变换矩阵)不是坐标变换矩阵,坐标变换矩阵的话转置一下,之后会有推导. 是通过M转置变换到P撇点....

基于Spring Boot + Vue的银行管理系统设计与实现

基于Spring Boot Vue的银行管理系统设计与实现 一、引言 随着金融数字化进程加速,传统银行业务向线上化转型成为必然趋势。本文设计并实现了一套基于Spring Boot Vue的银行管理系统,通过模块化架构满足用户、银行职员、管理员三类角色的核心业务需求…...

数据库基础知识点(系列一)

1.数据库的发展历史分哪几个阶段?各有什么特点? 答:数据库技术经历了人工管理阶段、文件系统阶段和数据库系统三个阶段。 1)人工管理阶段 这个时期数据管理的特点是: 数据由计算或处理它的程序自行携带…...

Android Compose 层叠布局(ZStack、Surface)源码深度剖析(十三)

Android Compose 层叠布局(ZStack、Surface)源码深度剖析 一、引言 在 Android 应用开发领域,用户界面(UI)的设计与实现一直是至关重要的环节。随着技术的不断演进,Android Compose 作为一种全新的声明式…...

JVM常用概念之身份哈希码

问题 当我们调用Object.hashCode时,如果没有用户没有提供哈希码,会发生什么? System.identityHashCode如何工作?它是否获取对象地址? 基础知识 在 Java 中,每个对象都有equals和hashCode ,即…...

vue 对接 paypal 订阅和支付

一个是支付一个是订阅,写的时候尝试把他们放到一个里面,但是会报错,所以分开写了 我们的页面,前三个为订阅最后一个是支付,我把他们放到一个数组里面循环展示的,所以我们判断的时候只要判断id是否为4&#…...

Spring Boot - 动态编译 Java 类并实现热加载

为什么需要动态编译? 想象这样一个场景:你的系统需要实时更新业务规则,但重启服务会导致用户体验中断;或者你正在开发一款低代码平台,允许用户编写自定义逻辑并即时生效。这时,动态编译并加载 Java 类的能…...

基于javaweb的SpringBoot实习管理系统设计与实现(源码+文档+部署讲解)

技术范围:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:免费功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论…...

流影---开源网络流量分析平台(一)(小白超详细)

目录 流影介绍 一、技术架构与核心技术 二、核心功能与特性 流影部署 流影介绍 一、技术架构与核心技术 模块化引擎设计 流影采用四层模块化架构:流量探针(数据采集)、网络行为分析引擎(特征提取)、威胁检测引擎&…...

Spring Boot事件机制详解

Spring Boot事件机制详解 1. 事件机制基础 1.1 什么是事件驱动架构 事件驱动架构(Event-Driven Architecture, EDA)是一种软件设计模式,其中系统组件通过事件的发布与订阅进行通信。在Spring Boot中,事件机制为应用程序提供了松耦合的组件间通信方式&…...

【商城实战(63)】配送区域与运费设置全解析

【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配&#xf…...

2025高频面试算法总结篇【字符串】

文章目录 直接刷题链接直达无重复字符的最长子串给定一个数,删除K位得到最小值至多包含 K 个不同字符的最长子串字符串的排列至少有K个重复字符的最长子串 直接刷题链接直达 如何找出一个字符串中的最大不重复子串 3. 无重复字符的最长子串 给定一个数&#xff0…...

Python散点密度图(Scatter Density Plot):数据可视化的强大工具

在数据驱动决策的时代,能够高效地处理和可视化多变量数据是一项 crucial 的技能。今天,我们就来深入探讨散点密度图(Scatter Density Plot),这是一种将散点图和核密度估计相结合的数据可视化技术,主要用于展示大量数据点在二维平面上的分布情况。 一、散点密度图的特点 …...

Oracle 数据库安全评估(DBSAT)简明过程

下载DBSAT 从这里下载。 实际是从MOS中下载,即:Oracle Database Security Assessment Tool (DBSAT) (Doc ID 2138254.1)。 最新版本为3.1.0 (July 2024),名为dbsat.zip,近45MB。 $ ls -lh dbsat.zip -rw-rw-r-- 1 oracle oins…...

【T2I】Divide Bind Your Attention for Improved Generative Semantic Nursing

CODE: GitHub - boschresearch/Divide-and-Bind: Official implementation of "Divide & Bind Your Attention for Improved Generative Semantic Nursing" (BMVC 2023 Oral) ABSTRACT 新兴的大规模文本到图像生成模型,如稳定扩散(SD),已…...

【2025】基于springboot+uniapp的企业培训打卡小程序设计与实现(源码、万字文档、图文修改、调试答疑)

基于 Spring Boot uniapp 的企业培训打卡小程序设计与实现 系统功能结构图如下: 一、课题背景 在当今快节奏的商业环境中,企业培训对于员工的成长和企业的发展至关重要。为了满足企业对高效培训管理和员工便捷学习的需求,基于 Spring Boot …...

腾讯面经,有点难度~

今天分享组织内的朋友在腾讯安全的实习面经。 内容涵盖了QPS测试方法、SQL聚合查询、Linux进程管理、Redis数据结构与持久化、NAT原理、Docker隔离机制、Go语言GMP调度模型、协程控制、系统调用流程、变量逃逸分析及map操作等等知识点。 下面是我整理的面经详解: …...

LeetCode(704):二分查找

二分查找 题目链接 题目&#xff1a;给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 #include<stdio.h> //左闭…...

探索AI的无限可能,体验智能对话的未来,大模型 API 演示

探索AI的无限可能&#xff0c;体验智能对话的未来&#xff0c;大模型 API 演示 效果展示&#xff1a; 项目概述 这是一个基于 Vue 3 TypeScript Vite 构建的 Vista AI 演示项目&#xff0c;旨在提供一个简洁易用的界面来展示 Vista AI 大语言模型的能力。项目包含 API 演示…...

26考研——图_图的存储(6)

408答疑 文章目录 二、图的存储图的存储相关概念邻接矩阵存储方式邻接矩阵的定义顶点的度计算邻接矩阵的特点邻接矩阵的局限性 应用场景邻接矩阵的幂次意义&#xff08;了解即可&#xff09; 邻接表存储方式邻接表定义邻接表结构邻接表的特点 邻接矩阵和邻接表的适用性差异十字…...

Spark读取文件系统的数据(sbt打包测试)-入门级别Demo

学习目标 通过本关卡练习&#xff0c;您将学到&#xff1a; 如何使用Spark访问本地文件和HDFS文件Spark应用程序的编写、编译和运行方法 相关知识 操作系统&#xff1a;Ubuntu 16.04&#xff1b; Spark版本&#xff1a;2.4.0&#xff1b; Hadoop版本&#xff1a;3.1.3。 编…...

5.1 位运算专题:LeetCode 面试题 01.01. 判定字符是否唯一

1. 题目链接 LeetCode 面试题 01.01. 判定字符是否唯一 2. 题目描述 实现一个算法&#xff0c;确定一个字符串的所有字符是否全部唯一&#xff08;即没有重复字符&#xff09;。要求如下&#xff1a; 不使用额外的数据结构&#xff08;如哈希表&#xff09;字符串仅包含小写…...

datawhale组队学习--大语言模型—task4:Transformer架构及详细配置

第五章 模型架构 在前述章节中已经对预训练数据的准备流程&#xff08;第 4 章&#xff09;进行了介绍。本章主 要讨论大语言模型的模型架构选择&#xff0c;主要围绕 Transformer 模型&#xff08;第 5.1 节&#xff09;、详细 配置&#xff08;第 5.2 节&#xff09;、主流架…...

Python虚拟环境:从入门到实战指南

目录 一、为什么需要Python虚拟环境&#xff1f; 二、如何创建Python虚拟环境&#xff1f; 1. 使用venv&#xff08;Python 3.3内置&#xff09; 2. 使用virtualenv&#xff08;第三方工具&#xff09; 3. 使用conda&#xff08;适合数据科学项目&#xff09; 三、虚拟环…...

如何提升 Java 开发能力?

如何提升 Java 开发能力&#xff1f; 要系统提升 Java 开发能力&#xff0c;需从 基础巩固、技术拓展、实战经验、持续学习 四个维度入手。以下是详细的进阶路径和具体建议&#xff1a; 一、夯实 Java 核心基础 深入理解语言特性 必学内容&#xff1a; JVM 原理&#xff1a…...

《TCP/IP网络编程》学习笔记 | Chapter 21:异步通知 I/O 模型

《TCP/IP网络编程》学习笔记 | Chapter 21&#xff1a;异步通知 I/O 模型 《TCP/IP网络编程》学习笔记 | Chapter 21&#xff1a;异步通知 I/O 模型同步与异步同步异步对比同步 I/O 的缺点异步 I/O 的优点 理解异步通知 I/O 模型实现异步通知 I/O 模型WSAEventSelect 函数和通知…...