若依微服务Cloud中Quartz-Job模块适配OpenGauss数据库

项目背景

在将若依(Ruoyi)框架的任务调度模块从其他数据库迁移到OpenGauss时,遇到了Quartz调度器与数据库兼容性问题。OpenGauss作为华为推出的开源关系型数据库,虽然兼容PostgreSQL协议,但在实际使用Quartz时仍遇到了一些特殊问题。

适配步骤

  1. 初始化SQL语句注:提供的初始化SQL语句已经将下方提到的长度报错信息解决
mysql 复制代码
## Quartz
DROP TABLE IF EXISTS QRTZ_FIRED_TRIGGERS CASCADE;
DROP TABLE IF EXISTS QRTZ_PAUSED_TRIGGER_GRPS CASCADE;
DROP TABLE IF EXISTS QRTZ_SCHEDULER_STATE CASCADE;
DROP TABLE IF EXISTS QRTZ_LOCKS CASCADE;
DROP TABLE IF EXISTS QRTZ_SIMPLE_TRIGGERS CASCADE;
DROP TABLE IF EXISTS QRTZ_SIMPROP_TRIGGERS CASCADE;
DROP TABLE IF EXISTS QRTZ_CRON_TRIGGERS CASCADE;
DROP TABLE IF EXISTS QRTZ_BLOB_TRIGGERS CASCADE;
DROP TABLE IF EXISTS QRTZ_TRIGGERS CASCADE;
DROP TABLE IF EXISTS QRTZ_JOB_DETAILS CASCADE;
DROP TABLE IF EXISTS QRTZ_CALENDARS CASCADE;

-- 1、存储每一个已配置的 jobDetail 的详细信息
CREATE TABLE QRTZ_JOB_DETAILS (
    sched_name           varchar(120)    NOT NULL,
    job_name             varchar(200)    NOT NULL,
    job_group            varchar(200)    NOT NULL,
    description          varchar(250)    NULL,
    job_class_name       varchar(250)    NOT NULL,
    is_durable           varchar(5)      NOT NULL,
    is_nonconcurrent     varchar(5)      NOT NULL,
    is_update_data       varchar(5)      NOT NULL,
    requests_recovery    varchar(5)      NOT NULL,
    job_data             bytea           NULL,
    PRIMARY KEY (sched_name, job_name, job_group)
);

-- 2、 存储已配置的 Trigger 的信息
CREATE TABLE QRTZ_TRIGGERS (
    sched_name           varchar(120)    NOT NULL,
    trigger_name         varchar(200)    NOT NULL,
    trigger_group        varchar(200)    NOT NULL,
    job_name             varchar(200)    NOT NULL,
    job_group            varchar(200)    NOT NULL,
    description          varchar(250)    NULL,
    next_fire_time       bigint          NULL,
    prev_fire_time       bigint          NULL,
    priority             integer         NULL,
    trigger_state        varchar(16)     NOT NULL,
    trigger_type         varchar(8)      NOT NULL,
    start_time           bigint          NOT NULL,
    end_time             bigint          NULL,
    calendar_name        varchar(200)    NULL,
    misfire_instr        smallint        NULL,
    job_data             bytea           NULL,
    PRIMARY KEY (sched_name, trigger_name, trigger_group),
    FOREIGN KEY (sched_name, job_name, job_group) REFERENCES QRTZ_JOB_DETAILS(sched_name, job_name, job_group)
);

-- 3、 存储简单的 Trigger,包括重复次数,间隔,以及已触发的次数
CREATE TABLE QRTZ_SIMPLE_TRIGGERS (
    sched_name           varchar(120)    NOT NULL,
    trigger_name         varchar(200)    NOT NULL,
    trigger_group        varchar(200)    NOT NULL,
    repeat_count         bigint          NOT NULL,
    repeat_interval      bigint          NOT NULL,
    times_triggered      bigint          NOT NULL,
    PRIMARY KEY (sched_name, trigger_name, trigger_group),
    FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group)
);

-- 4、 存储 Cron Trigger,包括 Cron 表达式和时区信息
CREATE TABLE QRTZ_CRON_TRIGGERS (
    sched_name           varchar(120)    NOT NULL,
    trigger_name         varchar(200)    NOT NULL,
    trigger_group        varchar(200)    NOT NULL,
    cron_expression      varchar(200)    NOT NULL,
    time_zone_id         varchar(80),
    PRIMARY KEY (sched_name, trigger_name, trigger_group),
    FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group)
);

-- 5、 Trigger 作为 Blob 类型存储
CREATE TABLE QRTZ_BLOB_TRIGGERS (
    sched_name           varchar(120)    NOT NULL,
    trigger_name         varchar(200)    NOT NULL,
    trigger_group        varchar(200)    NOT NULL,
    blob_data            bytea           NULL,
    PRIMARY KEY (sched_name, trigger_name, trigger_group),
    FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group)
);

-- 6、 以 Blob 类型存储存放日历信息
CREATE TABLE QRTZ_CALENDARS (
    sched_name           varchar(120)    NOT NULL,
    calendar_name        varchar(200)    NOT NULL,
    calendar             bytea           NOT NULL,
    PRIMARY KEY (sched_name, calendar_name)
);

-- 7、 存储已暂停的 Trigger 组的信息
CREATE TABLE QRTZ_PAUSED_TRIGGER_GRPS (
    sched_name           varchar(120)    NOT NULL,
    trigger_group        varchar(200)    NOT NULL,
    PRIMARY KEY (sched_name, trigger_group)
);

-- 8、 存储与已触发的 Trigger 相关的状态信息
CREATE TABLE QRTZ_FIRED_TRIGGERS (
    sched_name           varchar(120)    NOT NULL,
    entry_id             varchar(95)     NOT NULL,
    trigger_name         varchar(200)    NOT NULL,
    trigger_group        varchar(200)    NOT NULL,
    instance_name        varchar(200)    NOT NULL,
    fired_time           bigint          NOT NULL,
    sched_time           bigint          NOT NULL,
    priority             integer         NOT NULL,
    state                varchar(16)     NOT NULL,
    job_name             varchar(200)    NULL,
    job_group            varchar(200)    NULL,
    is_nonconcurrent     varchar(5)      NULL,
    requests_recovery    varchar(5)      NULL,
    PRIMARY KEY (sched_name, entry_id)
);

-- 9、 存储少量的有关 Scheduler 的状态信息
CREATE TABLE QRTZ_SCHEDULER_STATE (
    sched_name           varchar(120)    NOT NULL,
    instance_name        varchar(200)    NOT NULL,
    last_checkin_time    bigint          NOT NULL,
    checkin_interval     bigint          NOT NULL,
    PRIMARY KEY (sched_name, instance_name)
);

-- 10、 存储程序的悲观锁的信息
CREATE TABLE QRTZ_LOCKS (
    sched_name           varchar(120)    NOT NULL,
    lock_name            varchar(40)     NOT NULL,
    PRIMARY KEY (sched_name, lock_name)
);

-- 11、 Quartz集群实现同步机制的行锁表
CREATE TABLE QRTZ_SIMPROP_TRIGGERS (
    sched_name           varchar(120)    NOT NULL,
    trigger_name         varchar(200)    NOT NULL,
    trigger_group        varchar(200)    NOT NULL,
    str_prop_1           varchar(512)    NULL,
    str_prop_2           varchar(512)    NULL,
    str_prop_3           varchar(512)    NULL,
    int_prop_1           int             NULL,
    int_prop_2           int             NULL,
    long_prop_1          bigint          NULL,
    long_prop_2          bigint          NULL,
    dec_prop_1           numeric(13,4)   NULL,
    dec_prop_2           numeric(13,4)   NULL,
    bool_prop_1          varchar(1)      NULL,
    bool_prop_2          varchar(1)      NULL,
    PRIMARY KEY (sched_name, trigger_name, trigger_group),
    FOREIGN KEY (sched_name, trigger_name, trigger_group) REFERENCES QRTZ_TRIGGERS(sched_name, trigger_name, trigger_group)
);

COMMIT;
  1. 引入依赖

    <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> </dependency>

问题一:数据库字段长度不匹配

错误现象

Quartz调度器在尝试保存任务时出现"value too long for type character varying(1)"错误

问题分析

Quartz的QRTZ_JOB_DETAILS表中布尔类型字段原本定义为varchar(1),但Quartz内部会存储"true"或"false"这样的字符串值,长度超过1个字符,导致插入失败。

解决方案

修改Quartz初始化SQL脚本,将所有布尔字段的长度从varchar(1)扩展为varchar(5)

mysql 复制代码
-- 修改前的字段定义
is_durable        varchar(1)  NOT NULL,
is_nonconcurrent  varchar(1)  NOT NULL,
is_update_data    varchar(1)  NOT NULL,
requests_recovery varchar(1)  NOT NULL,

-- 修改后的字段定义
is_durable        varchar(5)  NOT NULL,
is_nonconcurrent  varchar(5)  NOT NULL,
is_update_data    varchar(5)  NOT NULL,
requests_recovery varchar(5)  NOT NULL,

修改涉及的表包括:

  • QRTZ_JOB_DETAILS
  • QRTZ_FIRED_TRIGGERS
  • QRTZ_SIMPROP_TRIGGERS

问题二:应用启动时的初始化问题

问题描述

应用启动时,@PostConstruct注解的init()方法会尝试从数据库加载所有定时任务并重新创建调度任务。如果数据库中已存在损坏或不兼容的数据,会导致应用启动失败。

解决方案

临时注释掉初始化代码,先确保应用能够正常启动:

复制代码
// 临时注释掉,防止应用启动失败
//@PostConstruct
public void init() throws SchedulerException, TaskException
{
    scheduler.clear();
    List<SysJob> jobList = jobMapper.selectJobAll();
    for (SysJob job : jobList)
    {
        ScheduleUtils.createScheduleJob(scheduler, job);
    }
}

问题三:定时任务工具类优化

问题分析

在创建定时任务时,没有明确设置作业的持久化属性,可能导致Quartz使用默认值,与数据库表结构不兼容。

解决方案

ScheduleUtils.createScheduleJob()方法中明确设置作业持久化属性:

复制代码
public static void createScheduleJob(Scheduler scheduler, SysJob job) 
    throws SchedulerException, TaskException
{
    Class<? extends Job> jobClass = getQuartzJobClass(job);
    // 构建job信息
    Long jobId = job.getJobId();
    String jobGroup = job.getJobGroup();
    JobDetail jobDetail = JobBuilder.newJob(jobClass)
            .withIdentity(getJobKey(jobId, jobGroup))
            .storeDurably(false)  // 明确设置持久化属性
            .build();
    // ... 其余代码
}

关键改进

  • 明确设置storeDurably(false),确保作业持久化属性有确定值
  • 避免Quartz内部使用默认的长字符串值
  • 确保数据库中存储的是明确的值(如"false")

问题四:数据类型转换错误

错误信息

org.quartz.JobPersistenceException: Couldn't obtain triggers for job: 不良的类型值 long : \x

错误分析

这个错误表明Quartz在从OpenGauss数据库读取触发器信息时,遇到了long类型数据转换问题。\x表示二进制数据,说明数据库返回的数据格式与Quartz期望的long类型不匹配。

可能原因

  • 数据库方言不匹配:Quartz默认可能不支持OpenGauss数据库的特定数据类型处理
  • bigint类型处理问题:OpenGauss的bigint类型与PostgreSQL的bigint处理方式可能存在差异
  • bytea字段处理问题:job_data字段为bytea类型,可能在某些情况下被错误解析
  • 数据损坏:数据库中已存在损坏的触发器数据

完整解决方案

  1. 修改Quartz配置类
    ScheduleConfig.java中添加OpenGauss/PostgreSQL特定的配置:
java 复制代码
@Configuration
public class ScheduleConfig
{
    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)
    {
        SchedulerFactoryBean factory = new SchedulerFactoryBean();
        factory.setDataSource(dataSource);

        // quartz参数
        Properties prop = new Properties();
        prop.put("org.quartz.scheduler.instanceName", "RuoyiScheduler");
        prop.put("org.quartz.scheduler.instanceId", "AUTO");
        
        // 线程池配置
        prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
        prop.put("org.quartz.threadPool.threadCount", "20");
        prop.put("org.quartz.threadPool.threadPriority", "5");
        
        // JobStore配置
        prop.put("org.quartz.jobStore.class", "org.springframework.scheduling.quartz.LocalDataSourceJobStore");
        
        // 关键配置:添加PostgreSQL委托类支持OpenGauss
        prop.put("org.quartz.jobStore.driverDelegateClass", "org.quartz.impl.jdbcjobstore.PostgreSQLDelegate");
        
        // 集群配置
        prop.put("org.quartz.jobStore.isClustered", "true");
        prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
        prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "10");
        prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
        prop.put("org.quartz.jobStore.misfireThreshold", "12000");
        prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
        
        factory.setQuartzProperties(prop);
        factory.setSchedulerName("RuoyiScheduler");
        
        // 延时启动
        factory.setStartupDelay(1);
        factory.setApplicationContextSchedulerContextKey("applicationContextKey");
        
        // 启动时更新已存在的Job
        factory.setOverwriteExistingJobs(true);
        
        // 设置自动启动
        factory.setAutoStartup(true);

        return factory;
    }
}
  1. 清理数据库中损坏的数据
    如果数据库中已存在损坏的触发器数据,需要先进行清理
mysql 复制代码
-- 1. 备份现有数据(建议操作前先备份)
CREATE TABLE QRTZ_TRIGGERS_BACKUP AS SELECT * FROM QRTZ_TRIGGERS;
CREATE TABLE QRTZ_SIMPLE_TRIGGERS_BACKUP AS SELECT * FROM QRTZ_SIMPLE_TRIGGERS;
CREATE TABLE QRTZ_CRON_TRIGGERS_BACKUP AS SELECT * FROM QRTZ_CRON_TRIGGERS;
CREATE TABLE QRTZ_JOB_DETAILS_BACKUP AS SELECT * FROM QRTZ_JOB_DETAILS;

-- 2. 查找并删除损坏的触发器数据
-- 注意:以下操作会删除数据,请确保已备份
DELETE FROM QRTZ_CRON_TRIGGERS 
WHERE (sched_name, trigger_name, trigger_group) IN (
    SELECT sched_name, trigger_name, trigger_group 
    FROM QRTZ_TRIGGERS 
    WHERE next_fire_time IS NULL 
    OR CAST(next_fire_time AS TEXT) LIKE '\x%'
);

DELETE FROM QRTZ_SIMPLE_TRIGGERS 
WHERE (sched_name, trigger_name, trigger_group) IN (
    SELECT sched_name, trigger_name, trigger_group 
    FROM QRTZ_TRIGGERS 
    WHERE next_fire_time IS NULL 
    OR CAST(next_fire_time AS TEXT) LIKE '\x%'
);

DELETE FROM QRTZ_TRIGGERS 
WHERE next_fire_time IS NULL 
OR CAST(next_fire_time AS TEXT) LIKE '\x%';

-- 3. 清理孤立的Job详情
DELETE FROM QRTZ_JOB_DETAILS 
WHERE NOT EXISTS (
    SELECT 1 FROM QRTZ_TRIGGERS 
    WHERE QRTZ_TRIGGERS.job_name = QRTZ_JOB_DETAILS.job_name 
    AND QRTZ_TRIGGERS.job_group = QRTZ_JOB_DETAILS.job_group
);

启动


相关推荐
用户3521802454752 小时前
🎉Spring Boot 3 + 多数据源 + Druid:监控页面 + 控制台 SQL 日志,终于搞定啦!
spring boot·微服务
奔跑的小十一2 小时前
ShardingSphere-JDBC 开发手册
java·数据库
lkbhua莱克瓦242 小时前
基础-MySQL概述
java·开发语言·数据库·笔记·mysql
姓蔡小朋友2 小时前
MySQL增删查改、多表查询
数据库·mysql
Knight_AL2 小时前
Maven <dependencyManagement>:如何在多模块项目中集中管理依赖版本
java·数据库·maven
TAEHENGV2 小时前
导入导出模块 Cordova 与 OpenHarmony 混合开发实战
android·javascript·数据库
不剪发的Tony老师3 小时前
sqlectron:一款轻量级的SQL客户端
数据库·sql
酸菜牛肉汤面3 小时前
7、索引设计的原则
数据库
薛不痒3 小时前
使用python操作MySQL
数据库·mysql