修改ik分词器源码实现直连数据库动态增量更新词汇

news2024/11/25 6:47:55

 谈到es的中文分词器,肯定少不了ik分词器.现ik分词器有两种获取主词汇和停用词的方法:

一是通过ik\config目录下的main.dic和stopword.dic获取,但是每次修改后要重启才能生效
二是通过提供接口返回所有词汇的接口,接口路径配置在.但是该方式每次都需要将所有词汇返回,效率不高.

 本次目的就是通过jdbc直接连接数据库来实现增量更新词汇.我们要做的就是找到添加主词汇和停用词汇的方法,然后再通过jdbc获取数据库词汇来调用该方法来更新词汇

 下载ik源码,我下载的是7.17.6本版.因为es使用的是7.17.7,为防止启动报错,下载后我将版本改成了7.17.7.

词汇更新介绍

(1)找到Dictionary.initial方法
在这里插入图片描述
  可以看到,加载词汇的过程再Dictionary.initial 方法中,在该方法中,加载了各文件的词汇还有通过定时任务来获取接口词汇进行更新.
(2)接下来我们进入到singleton.loadMainDict -> loadExtDict -> loadDictFile方法中
在这里插入图片描述
  可以看到dict.fillSegment就是添加主词汇
(3)同理的,如下_stopWords.fillSegment就是对停用词的加载
在这里插入图片描述
  所以我们要做的就是拿到词汇,调用对应的fillSegment来加载词汇就可以了

准备工作

(1)表设计
 主词汇表:

CREATE TABLE `es_dic_main` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `word` varchar(100) NOT NULL COMMENT '词汇',
  `moditime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `ifdel` char(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='主词汇'

 通用词表:

CREATE TABLE `es_dic_stop` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `word` varchar(100) NOT NULL COMMENT '停用词',
  `moditime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `ifdel` char(1) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='停用词'

(2)在/config目录下创建jdbc配置文件jdbc.properties:

jdbc.url=jdbc:mysql://cckg.liulingjie.cn:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghai
jdbc.username=账号
jdbc.password=密码
# 主词汇增量查询sql
main.word.sql=SELECT * FROM es_dic_main WHERE moditime >= ?
# 通用词增量查询sql
stop.word.sql=SELECT * FROM es_dic_stop WHERE moditime >= ?
# 执行间隔(秒)
interval=10

(3)pom.xml添加jdbc依赖:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.21</version>
</dependency>

(4)src/main/assemblies/plugin.xml下添加以下内容打包时包含mysql驱动jar包:

        <dependencySet>
            <outputDirectory/>
            <useProjectArtifact>true</useProjectArtifact>
            <useTransitiveFiltering>true</useTransitiveFiltering>
            <includes>
                <include>mysql:mysql-connector-java</include>
            </includes>
        </dependencySet>

在这里插入图片描述

过程

 大致流程:
在这里插入图片描述
 主要涉及有两个类,一个是Dictionary,一个是自己创建的类JdbcMonitor。
  Dictionary:提供读取配置,加载词汇和启动词汇更新任务。
  JdbcMonitor功能:是一个实现了Runner接口的类,通过jdbc读取数据库词汇并调用Dictionary的方法加载词汇

(1)在Dictionary类中添加以下方法提供对词汇的api
在这里插入图片描述
 代码:

    protected void fillSegmentMain(String word) {
        _MainDict.fillSegment(word.trim().toCharArray());
    }

    protected void disableSegmentMain(String word) {
        _MainDict.disableSegment(word.trim().toCharArray());
    }

    protected void fillSegmentStop(String word) {
        _StopWords.fillSegment(word.trim().toCharArray());
    }

    protected void disableSegmentStop(String word) {
        _StopWords.disableSegment(word.trim().toCharArray());
    }

(2)在Dictionary构造方法中读取配置jdbc.properties
在这里插入图片描述
 代码:

public class JdbcConfig {

    private String url;

    private String username;

    private String password;

    private String mainWordSql;

    private String stopWordSql;

    private Integer interval;
	// geter,setter省略
}
    private Dictionary(Configuration cfg) {
   		//......省略

        // 读取jdbc配置
        setJdbcConfig();
    }

    private void setJdbcConfig() {
        Path file = PathUtils.get(getDictRoot(), Dictionary.PATH_JDBC_CONFIG);
        Properties properties = null;
        try {
            properties = new Properties();
            properties.load(new FileInputStream(file.toFile()));
        } catch (Exception e) {
            logger.error("load jdbc.properties failed");
            logger.error(e.getMessage());
        }
        jdbcConfig = new JdbcConfig(
                properties.getProperty("jdbc.url"),
                properties.getProperty("jdbc.username"),
                properties.getProperty("jdbc.password"),
                properties.getProperty("main.word.sql"),
                properties.getProperty("stop.word.sql"),
                Integer.valueOf(properties.getProperty("interval"))
        );
    }

(3)声明JdbcMinitor类定时连接数据库读取并更新词汇

package org.wltea.analyzer.dic;

import org.apache.logging.log4j.Logger;
import org.elasticsearch.SpecialPermission;
import org.wltea.analyzer.cfg.JdbcConfig;
import org.wltea.analyzer.help.ESPluginLoggerFactory;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author liulingjie
 * @date 2022/11/29 20:36
 */
public class JdbcMonitor implements Runnable {

    static {
        try {
            Class.forName("com.mysql.cj.jdbc.Driver");
        } catch (Exception e) {
            e.getStackTrace();
        }
    }

    /**
     * jdbc配置
     */
    private JdbcConfig jdbcConfig;
    /**
     * 主词汇上次更新时间
     */
    private Timestamp mainLastModitime = Timestamp.valueOf("2022-01-01 00:00:00");
    /**
     * 停用词上次更新时间
     */
    private Timestamp stopLastModitime = Timestamp.valueOf("2022-01-01 00:00:00");

    private static final Logger logger = ESPluginLoggerFactory.getLogger(JdbcMonitor.class.getName());

    public JdbcMonitor(JdbcConfig jdbcConfig) {
        this.jdbcConfig = jdbcConfig;
    }

    @Override
    public void run() {
        SpecialPermission.check();
        AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
            this.runUnprivileged();
            return null;
        });
    }

    /**
     * 加载词汇和停用词
     */
    public void runUnprivileged() {
        //Dictionary.getSingleton().reLoadMainDict();
        loadWords();
    }

    private void loadWords() {
        List<String> mainWords = new ArrayList<>();
        List<String> delMainWords = new ArrayList<>();
        List<String> stopWords = new ArrayList<>();
        List<String> delStopWords = new ArrayList<>();

        setAllWordList(mainWords, delMainWords, stopWords, delStopWords);

        mainWords.forEach(w -> Dictionary.getSingleton().fillSegmentMain(w));
        delMainWords.forEach(w -> Dictionary.getSingleton().disableSegmentMain(w));
        stopWords.forEach(w -> Dictionary.getSingleton().fillSegmentStop(w));
        delStopWords.forEach(w -> Dictionary.getSingleton().disableSegmentStop(w));


        logger.info("ik dic refresh from db. mainLastModitime: {} stopLastModitime: {}", mainLastModitime, stopLastModitime);
    }

    /**
     * 获取主词汇和停用词
     *
     * @param mainWords
     * @param delMainWords
     * @param stopWords
     * @param delStopWords
     */
    private void setAllWordList(List<String> mainWords, List<String> delMainWords, List<String> stopWords, List<String> delStopWords) {
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(jdbcConfig.getUrl(), jdbcConfig.getUsername(), jdbcConfig.getPassword());
            setWordList(connection, jdbcConfig.getMainWordSql(), mainLastModitime, mainWords, delMainWords);
            setWordList(connection, jdbcConfig.getStopWordSql(), stopLastModitime, stopWords, delStopWords);
        } catch (SQLException throwables) {
            logger.error("jdbc load words failed: mainLastModitime-{} stopLostMOditime-{}", mainLastModitime, stopLastModitime);
            logger.error(throwables.getStackTrace());
        } finally {

            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException throwables) {
                    logger.error("failed to close connection");
                    logger.error(throwables.getMessage());
                }
            }
        }
    }

    /**
     * 连接数据库获取词汇
     *
     * @param connection
     * @param sql
     * @param lastModitime
     * @param words
     * @param delWords
     */
    private void setWordList(Connection connection, String sql, Timestamp lastModitime, List<String> words, List<String> delWords) {
        PreparedStatement prepareStatement = null;
        ResultSet result = null;

        try {
            prepareStatement = connection.prepareStatement(sql);
            prepareStatement.setTimestamp(1, lastModitime);
            result = prepareStatement.executeQuery();

            while (result.next()) {
                String word = result.getString("word");
                Timestamp moditime = result.getTimestamp("moditime");
                String ifdel = result.getString("ifdel");

                if ("1".equals(ifdel)) {
                    delWords.add(word);
                } else {
                    words.add(word);
                }

                // 取最大的时间
                if (moditime.after(lastModitime)) {
                    lastModitime.setTime(moditime.getTime());
                }
            }
        } catch (SQLException throwables) {
            logger.error("jdbc load words failed: {}", lastModitime);
            logger.error(throwables.getMessage());
        } finally {
            if (result != null) {
                try {
                    result.close();
                } catch (SQLException throwables) {
                    logger.error("failed to close prepareStatement");
                    logger.error(throwables.getMessage());
                }
            }

            if (prepareStatement != null) {
                try {
                    prepareStatement.close();
                } catch (SQLException throwables) {
                    logger.error("failed to close prepareStatement");
                    logger.error(throwables.getMessage());
                }
            }
        }
    }
}

(4)最后在Dictionary.initial方法中启用该定时任务
在这里插入图片描述
 代码:

public static synchronized void initial(Configuration cfg) {
    if (singleton== null) {
        synchronized (Dictionary.class) {
            if (singleton == null) {
                singleton = new Dictionary(cfg);
                ......
                // 开启数据库增量更新
                pool.scheduleAtFixedRate(new JdbcMonitor(singleton.jdbcConfig), 10, singleton.jdbcConfig.getInterval(), TimeUnit.SECONDS);
            }
        }
    }
}

(5)最后mvn cliean package打包,在~\target\releases下会生成如下包在这里插入图片描述
(6)解压放入到 es安装路径/plugins/ik 重启es就行了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/90675.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

大数据课设

----------------------------------------------------------------------------------------------------------------------------- 由于本人主修嵌入式方向最多使用的就是C语言&#xff0c;由于物联网这个专业的特殊性&#xff0c;javaweb没少 写&#xff0c;所以java也用…

Nginx动静分离

&#x1f341;博客主页&#xff1a;&#x1f449;不会压弯的小飞侠 ✨欢迎关注&#xff1a;&#x1f449;点赞&#x1f44d;收藏⭐留言✒ ✨系列专栏&#xff1a;&#x1f449;Linux专栏 &#x1f525;欢迎大佬指正&#xff0c;一起学习&#xff01;一起加油&#xff01; 目录&…

[附源码]Python计算机毕业设计SSM基于Java的租房系统(程序+LW)

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

论文笔记:CycleMorph: Cycle Consistent UnsupervisedDeformable Image Registration

CycleMorph: Cycle Consistent Unsupervised Deformable Image Registration 针对本篇论文个人总结&#xff1a; 文章最重要的点在于施加循环一致性&#xff0c;模型有两个网络&#xff0c;移动图像与固定图像互相配准过程中施加约束优化网络提高配准精度&#xff0c;仔细看图…

如何求解欧拉路径?

求解欧拉路径前言一、案例二、回溯三、源码四、复杂度分析五、欧拉科普总结参考文献前言 欧拉路径 从图的一个节点出发&#xff0c;每条边只访问一次&#xff0c;遍历完了所有图节点&#xff0c;这条路径为欧拉路径。 一、案例 二、回溯 按照上面的例子&#xff0c;很容易理…

【LeetCode】1945. 字符串转化后的各位数字之和

题目描述 给你一个由小写字母组成的字符串 s &#xff0c;以及一个整数 k 。 首先&#xff0c;用字母在字母表中的位置替换该字母&#xff0c;将 s 转化 为一个整数&#xff08;也就是&#xff0c;‘a’ 用 1 替换&#xff0c;‘b’ 用 2 替换&#xff0c;… ‘z’ 用 26 替换&…

计算机网络原理第1章 概述

1.1 计算机网络在信息时代中的作用 1.2 互联网概述 1.2.1 网络的网络 互联网 特指Internet&#xff0c;起源于美国&#xff0c;现已发展成为世界上最大的、覆盖全球的计算机网络。 计算机网络 (简称为网络) 由若干结点(node)和连接这些结点的链路(link)组…

玩转MySQL:一站式解决分库分表后患问题方案

引言 上篇有关分分库分表一文中已经将分库分表的方法论全面阐述清楚了&#xff0c;总体看下来用一个字形容&#xff0c;那就是爽&#xff01;&#xff08;手动狗头&#xff09;尤其是分库分表技术能够让数据存储层真正成为三高架构&#xff0c;但前面爽是爽了&#xff0c;接着…

面试官:你觉得HTTPS能防止重放攻击吗?

引言 先来一段面试情景再现~~ ​ 编辑切换为居中 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 最后的结局自然就是 ​ 编辑切换为居中 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; OK&#xff0c;带着上面的疑问&#xf…

【SpringCloud负载均衡】【源码+图解】【四】负载均衡的实现

【SpringCloud负载均衡】【源码图解】【三】LoadBalancer的工作原理 目录4. 负载均衡4.1 提供者DiscoveryClient4.1.1 CompositeDiscoveryClient4.1.2 EurekaDiscoveryClient4.1.3 SimpleDiscoveryClient4.1.4 自定义DiscoveryClient4.2 过滤器Supplier4.2.1 CachingServiceIns…

Linux文本三剑客之grep命令

Linux文本三剑客之grep命令 1. grep 命令 介绍 grep 命令的基本语法格式和参数列表&#xff01; 文本搜索工具&#xff0c;根据用户指定的”模式”对目标文本逐行进行匹配检查&#xff0c;打印匹配到的行。 模式&#xff1a;由正则表达式字符及文本字符所编写的过滤条件&am…

MySQL 中截取字符串的方法

LEFT(str, len) 从左边开始截取&#xff0c;如果字符串为 null 则返回null。 str&#xff1a;被截取字符串&#xff1b;len&#xff1a;截取长度 SELECT LEFT(ABCDEFT, 2) FROM sub_str;RIGHT(str, len) 从右边开始截取&#xff0c;如果字符串为 null 则返回null。 str&…

0125 搜索与回溯算法 Day14

剑指 Offer 12. 矩阵中的路径 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 单词必须按照字母顺序&#xff0c;通过相邻的单元格内的字母构成&#xff0c;其中“相邻…

GLAD:带有反射壁的空心波导

概述 离散傅里叶变换的混叠效应为带有反射壁的空心波导的建模提供了一个便捷的方法。反射壁可以将光返回到光路中而混叠效应将使溢出光场从反方向折回到采样光场中。如果光场分布是一个偶函数&#xff0c;那么折回的作用就如同反射效果。我们可以将任意形状的光场分布转化成…

推荐一款免费的AI绘图软件,可生成二次元画作和3D模型

随着AI绘画的火热&#xff0c;市面上关于AI绘画的话题居高不小&#xff0c;各种教程、软件、小程序也是满天飞&#xff0c;在这些眼花缭乱的推荐中&#xff0c;究竟哪一款ai绘图软件才是真正适合自己的&#xff0c;不但免费&#xff0c;生成出来的二次元画作还很精美&#xff1…

早教资源网站

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 网站前台&#xff1a;关于我们、联系我们、公告信息、二手物品、资源信息 管理员功能&#xff1a; 1、管理关于我们、联…

Django 第三天学习笔记

1.模板层-变量和标签 能够传递到Django模板中的数据类型&#xff1a; 1.str 字符串 2.Int 整形 3.List 数组 4.Tuple 元组 5.Dict 字典 6.Func 方法 7.Obj 类的实例化对象。 在模板中使用的变量的语法&#xff1a; {{变量名}}{{变量名.index}} #索引{{变量名.key}} #获取字典对…

数据结构顺序栈

栈 这是大话数据结构种对于栈的描述 可以看到 栈是一种特殊的线性表 它只能在尾部进行元素的插入和删除 但是在栈种 这叫做 入栈 和 出栈 而且它遵循 先进入的元素后出 后进入的元素先出 即就是我们常听说的 先进后出 和后进先出 这里就有一个简单的例子 先进后出 后进先出…

【Node.js】实现微信小程序在线支付功能

实战项目名称&#xff1a;微信小程序实现在线支付功能 - 文章结尾附上微信小程序码&#xff0c;扫码登录后即可体验&#xff01;&#xff01; 文章目录一、实战步骤1. 前期准备2. 添加wechatpay-node-v3和fs插件3. 预设微信下单的数据4. 将上一步骤的下单信息返回给前端5. 小程…

在抖音全程看世界杯,超高清直播背后的硬实力

导语&#xff1a;IT技术赛场开赛&#xff01;作者 | 宋慧 出品 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09;当前&#xff0c;2022 卡塔尔世界杯比赛正在如火如荼进行中&#xff0c;处在更加激烈关键的半决赛阶段。作为足球运动的全球顶级赛事&#xff0c;世界杯…