需求:实现在对数据库查询时,同时更新ES服务中指定索引的数据。若用户重建数据库,则需删除旧索引,查询数据库新数据,而后插入指定新索引中。

创建索引之前,进行数据操作部分(操作数据过程中同时更新当前索引数据):

点击重建索引按钮之后,进行对数据检索部分:

第一步,首先了解,安装Elasticsearch,注意各个版本的对应,否则会运行失败

elasticsearch:7.16.3

spring-boot-starter-parent:2.3.0.RELEASE

当运行之后输入http://localhost:9200/

如果有返回值则说明安装成功

第二步,引用ES(这里运用的RestHighLevelClient中封装好的方法)

<!--全文检索-->
        <!--添加springboot-elasticsearch依赖-->
        <!--es客户端,不使用springboot封装的客户端-->
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch</groupId>
            <artifactId>elasticsearch</artifactId>
            <version>7.5.2</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-client</artifactId>
            <version>7.6.2</version>
        </dependency>

application.yml中的配置如下:
 

elasticsearch:
  schema: http
  address: 127.0.0.1:9200
  connectTimeout: 5000
  socketTimeout: 5000
  connectionRequestTimeout: 5000
  maxConnectNum: 100
  maxConnectPerRoute: 100

全文检索模块后端接口结构:

 

 其中config配置类如下(必须使用)

/**
 * @Deacription ElasticSearch 配置
 * @Author hmf
 * @Date 2022/8/29
 * @Version 1.0
 **/
@Configuration
public class ElasticSearchConfiguration {
    /** 协议 */
    @Value("${elasticsearch.schema:http}")
    private String schema;

    /** 集群地址,如果有多个用“,”隔开 */
    @Value("${elasticsearch.address}")
    private String address;

    /** 连接超时时间 */
    @Value("${elasticsearch.connectTimeout}")
    private int connectTimeout;

    /** Socket 连接超时时间 */
    @Value("${elasticsearch.socketTimeout}")
    private int socketTimeout;

    /** 获取连接的超时时间 */
    @Value("${elasticsearch.connectionRequestTimeout}")
    private int connectionRequestTimeout;

    /** 最大连接数 */
    @Value("${elasticsearch.maxConnectNum}")
    private int maxConnectNum;

    /** 最大路由连接数 */
    @Value("${elasticsearch.maxConnectPerRoute}")
    private int maxConnectPerRoute;

    @Bean(name = "restHighLevelClient")
    public RestHighLevelClient restHighLevelClient() {
        // 拆分地址
        List<HttpHost> hostLists = new ArrayList<>();
        String[] hostList = address.split(",");
        for (String addr : hostList) {
            String host = addr.split(":")[0];
            String port = addr.split(":")[1];
            hostLists.add(new HttpHost(host, Integer.parseInt(port), schema));
        }
        // 转换成 HttpHost 数组
        HttpHost[] httpHost = hostLists.toArray(new HttpHost[]{});
        // 构建连接对象
        RestClientBuilder builder = RestClient.builder(httpHost);
        // 异步连接延时配置
        builder.setRequestConfigCallback(requestConfigBuilder -> {
            requestConfigBuilder.setConnectTimeout(connectTimeout);
            requestConfigBuilder.setSocketTimeout(socketTimeout);
            requestConfigBuilder.setConnectionRequestTimeout(connectionRequestTimeout);
            return requestConfigBuilder;
        });
        // 异步连接数配置
        builder.setHttpClientConfigCallback(httpClientBuilder -> {
            httpClientBuilder.setMaxConnTotal(maxConnectNum);
            httpClientBuilder.setMaxConnPerRoute(maxConnectPerRoute);
            return httpClientBuilder;
        });
        return new RestHighLevelClient(builder);
    }


}

 ENTITY中的实体类(返回前端,存入ES的数据格式)

其中Document必须提前设置好指定存入的index(可以理解成数据库的某个表) 

/**
 * @author hmf
 * @create 2022-08-29 10:28
 */

@Data
@Document(indexName = "gt_office_doc", type = "_doc")
public class OfficeDocVO {

    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String officeDocFileId;
    @JSONField(serialize = false)
    private MultipartFile docFileContent;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String docFileName;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String docFileKeyword;
    @Id
    private String officeDocId;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String docTitle;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String dispatchnoId;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String issuingAuthority;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String issuingAuthorityYear;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String postingSequenceNumber;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String orgUuid;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String orgName;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String drafterId;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String drafterName;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String uploaderName;
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date draftDate;
    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")
    private Date uploadTime;
    @Field(type = FieldType.Text, searchAnalyzer = "ik_smart", analyzer = "ik_max_word")
    private String previewUrl;
}

 Serivce中封装的主要方法(增删改查)

OFFICE_INDEX为设置好的全局常量,内容为:gt_office_doc (也就是index)

其中,初始化数据时候,是从数据库中查询LIST而后插入ES(批量操作)

@Async为异步请求,考虑当数据库中的数据较多,反应时间较长,加载批量操作数据的方法中

其中,全字段检索使用了multiMatchQuery,将需要匹配的字段写入,则可按照查询。

import static com.icss.audit.gt.constant.CommonConst.OFFICE_INDEX;
/**
 * @author hmf
 * @create 2022-08-29
 */
@Slf4j
@Service
public class EsIndexService {

    @Resource
    ElasticsearchRestTemplate elasticsearchRestTemplate;

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    @Resource
    OfficeDocumentDao officeDocumentDao;

    /**
     * 创建索引
     */
    public void createIndex() throws Exception {
        if (elasticsearchRestTemplate.indexOps(IndexCoordinates.of(OFFICE_INDEX)).exists()){
            deleteIndex();
        }
        // 创建索引配置信息,配置
        Settings settings = Settings.builder()
                .put("index.number_of_shards", 1)
                .put("index.number_of_replicas", 0)
                .build();
        // 新建创建索引请求对象,然后设置索引类型(ES 7.0 将不存在索引类型)和 mapping 与 index 配置
        CreateIndexRequest request = new CreateIndexRequest(OFFICE_INDEX, settings);
        // RestHighLevelClient 执行创建索引
        CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
        // 判断是否创建成功
        boolean isCreated = createIndexResponse.isAcknowledged();
       if (isCreated){
            //初始化数据
             initEsData();
        }
    }

    /**
     * 删除索引
     */
    public void deleteIndex()throws Exception {
            // 新建删除索引请求对象
            DeleteIndexRequest request = new DeleteIndexRequest(OFFICE_INDEX);
            // 执行删除索引
            AcknowledgedResponse acknowledgedResponse = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
            // 判断是否删除成功
           log.info("索引是否删除成功:" + acknowledgedResponse.isAcknowledged());
    }

    /**
     * 初始化索引数据
     */
    @Async
    public void initEsData() throws Exception{

            List<OfficeDocVO> resultlist = officeDocumentDao.initIndexData();
            //增加预览参数
            for(OfficeDocVO temp : resultlist){
                if (temp.getDocFileName().endsWith(".pdf")){
                    temp.setPreviewUrl("/officeDocument/PreviewOfficeDoc?id=");
                }
            }
            // 创建索引请求对象
            BulkRequest bulkRequest = new BulkRequest();
            // 准备批量插入的数据
            resultlist.forEach(user -> {
                // 设置请求对象
                IndexRequest request = new IndexRequest(OFFICE_INDEX);
                // 文档id
                request.id(user.getOfficeDocId());
                // 将json格式字符串放在请求中
                // 下面这种写法也可以写成:request.source(XContentType.JSON, "name", "张三", "age", "男", "age", 22);,其中"name"、"age"、 "age"是User对象中的字段名,而这些字段名称后面的值就是对应的值
                System.out.println("插入的数据为:"+user.toString());
                request.source(JSONObject.toJSONString(user), XContentType.JSON);
                // 将request添加到批量处理请求中
                bulkRequest.add(request);
            });
            // 3、发送请求到ES
            BulkResponse response = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            // 4、处理响应结果
            log.info("批量插入是否失败:" + response.hasFailures());
    }

    /**
     * 获取所有文档信息
     */
    public SearchResponse getDocument(PageData page) throws IOException {
        // 获取请求对象
        SearchRequest getRequest = new SearchRequest(OFFICE_INDEX, "", "");
        // 指定检索条件
        SearchSourceBuilder builder = new SearchSourceBuilder();

        Map<String, Object> condition = page.getCondition();
        String AuthorityYear = "";
        String Authority = "";
        String ownerUnit = "";

        int currentPage = (int) page.getCurrent();
        int pageSize = 10;
        int from = (currentPage - 1) * pageSize;
        builder.from(from);
        builder.size(pageSize);

        // 若无参则用来查询索引中全部的数据
        builder.query(QueryBuilders.matchAllQuery());
        getRequest.source(builder);

        if (condition != null) {
            if (condition.get("AuthorityYear") != "") {
                AuthorityYear = (String) condition.get("AuthorityYear");
                builder.query(QueryBuilders.termQuery("issuingAuthorityYear.keyword", AuthorityYear));
                getRequest.source(builder);
            }
            if (condition.get("Authority") != "") {
                Authority =  condition.get("Authority").toString();
                builder.query(QueryBuilders.termQuery("dispatchnoId.keyword", Authority));
                getRequest.source(builder);
            }
            /*全文检索*/
            if (condition.get("ownerUnit") != ""){
                ownerUnit = condition.get("ownerUnit").toString();
                //多个字段条件匹配查询(multiMatchQuery)
                builder.query(QueryBuilders.multiMatchQuery(ownerUnit,"docTitle","issuingAuthority","issuingAuthorityYear","postingSequenceNumber","orgName","drafterName","uploaderName"));
                getRequest.source(builder);
            }
        }
            // 3、发送请求到ES
            return restHighLevelClient.search(getRequest, RequestOptions.DEFAULT);

//            // 4、处理响应结果
//            for (SearchHit hit : response.getHits().getHits()) {
//                OfficeDocVO officeInfo = JSON.parseObject(hit.getSourceAsString(), OfficeDocVO.class);
//                log.info("所有信息:{}", officeInfo);
//            }

    }

    /**
     * 新增索引数据
     */
    public void addEsDoc(OfficeDocVO officeDocVO)throws IOException{
            // 定义请求对象
        IndexRequest request = new IndexRequest(OFFICE_INDEX);
                // 设置文档id
        request.id(officeDocVO.getOfficeDocId());
                // 将json格式字符串放在请求中
        request.source(JSONObject.toJSONString(officeDocVO), XContentType.JSON);
                // 3、发送请求到ES
        IndexResponse response = restHighLevelClient.index(request, RequestOptions.DEFAULT);

    }

    /**
     * 更新索引数据
     */
    public void updateEsDoc(OfficeDocVO officeDocVO)throws IOException{
        UpdateRequest request = new UpdateRequest();
        request.index(OFFICE_INDEX).id(officeDocVO.getOfficeDocId());
        // 拓展:局部更新也可以这样写:request.doc(XContentType.JSON, "name", "李四", "age", 25);,其中"name"和"age"是User对象中的字段名称,而"小美"和20是对应的字段值
        request.doc(JSONObject.toJSONString(officeDocVO), XContentType.JSON);
        // 3、发送请求到ES
        UpdateResponse response = restHighLevelClient.update(request, RequestOptions.DEFAULT);
    }

    /**
     * 删除索引数据
     * @param OfficeDocId
     * @throws Exception
     */
    public void deleteEsDoc(String OfficeDocId)throws Exception{
        // 2、定义请求对象
        DeleteRequest request = new DeleteRequest(OFFICE_INDEX);
        request.id(OfficeDocId);
        // 3、发送请求到ES
        DeleteResponse response = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
    }
}

EsSearchContoller

/**
 * @author hmf
 * @create 2022-08-26 10:44
 */
@Api(tags = "ES全文检索")
@RestController
@CrossOrigin
@RequestMapping("/EsSearch")
public class EsSearchContoller {

    @Resource
    RestHighLevelClient client;


    @Resource
    EsIndexService esIndexService;


    @ApiOperation("初始化ES数据以及index")
    @RequestMapping("/initIndex")
    public void createIndex()throws Exception{
         esIndexService.createIndex();
    }

    @ApiOperation("ES数据查询以及全文检索")
    @RequestMapping("/getEsData")
    public SearchResponse getEsData(@RequestBody PageData page)throws IOException{
       return esIndexService.getDocument(page);
    }



}

第三步,前端获取数据 

需要注意从ES查询出来的返回值存储在res.hits.hits中

由于传输中,将数据库里的数据时间类型转入ES后会转成时间戳,返回前端后展示数据会变成时间戳。

在后端加注解也是方法之一,

但这里是用前端进行转换。

 // 表格初始化
     Initialization(data){
      let condition = {
        AuthorityYear:data.parentid,
        Authority:data.dispatchnoId,
        ownerUnit:this.formInline.ownerUnit,
      };
      let size = this.page.size;
      let current=this.page.currentPage;
      let Params = {condition,size,current}  
      this.request('/EsSearch/getEsData',Params,'post').then((res) =>{
          if (res.hits.hits.length > 0){
            res.hits.hits.forEach(element => {
              element.sourceAsMap.draftDate = this.Time(element.sourceAsMap.draftDate);
              element.sourceAsMap.uploadTime = this.Time(element.sourceAsMap.uploadTime);
            });
            this.tableData = res.hits.hits;
          }else{
            //表格重置为空
            this.tableData=[];
          }
          //重置显示总数
          this.page.total=res.hits.totalHits.value;
        })
     },
 Time(time) { //处理时间
        // return moment(parseInt(e)).format('YYYY-MM-DD');
       //将13位时间戳转换成时间格式 输出为2018-10-09
       let date = new Date(time);
       let year = date.getFullYear();
       let month = date.getMonth() + 1;
       let day = date.getDate();
       month = month < 10 ? "0" + month : month;
       day = day < 10 ? "0" + day : day;
       var myDate = ''
       myDate = year + '-' + month + '-' + day;
       return myDate
      },

 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐