[求助] 如何在 SpringBoot 中实现如下功能?

问题描述

现有一个实体类,内容如下:

kotlin 复制代码
@Entity(name = "users")
class User: Base() {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(
        name = "id",
        unique = true,
        nullable = false
    )
    @SerializedName("user_id")
    var id: Long = 0

    @Column(
        name = "custom_id",
        unique = true,
        nullable = false
    )
    @SerializedName("custom_id")
    var customId: String = ""

    @Column(
        name = "username",
        unique = true,
        nullable = false
    )
    @SerializedName("username")
    var username: String = ""

   /** 其他字段 **/
}

现有一个业务设计如下:在用户首次注册时, custom_id 字段的内容与 user_id 的数值相同。但是显然, user_id 是一个由数据库顺序生成的字段,在首次插入数据库时其数值对于我们来说是未知的。经查阅资料,我们得知, MySQL 中的触发器功能可以实现这一需求:

MySQL 中的触发器(Trigger)是一种数据库对象,它在特定的数据库事件发生时自动执行。触发器可以在以下事件中触发:

  1. INSERT - 当一条新记录插入表时触发。
  2. UPDATE - 当表中的现有记录被更新时触发。
  3. DELETE - 当表中的记录被删除时触发。

触发器可以在事件发生的之前(BEFORE)或之后(AFTER)执行,具体取决于业务需求。例如,可以使用 BEFORE INSERT 触发器为某个字段设置默认值,或者使用 AFTER UPDATE 触发器进行日志记录。

主要用途包括:

  • 数据完整性:确保在插入、更新或删除时满足特定条件。
  • 自动化操作:如自动填充字段、生成日志、更新相关表等。
  • 审计与日志:记录对数据库的更改历史。

创建触发器时需要注意性能和管理问题,因为复杂的触发器可能对数据库性能产生影响。此外,每张表每个事件类型只能定义一个触发器。

示例语法:

sql 复制代码
CREATE TRIGGER trigger_name
BEFORE INSERT ON table_name
FOR EACH ROW
BEGIN
    -- 触发器逻辑
END;

在开发中,触发器经常用于处理复杂的业务逻辑,保证数据的一致性和完整性。

需要用到的触发器代码如下:

sql 复制代码
DROP TRIGGER IF EXISTS set_custom_id_after_insert;

CREATE TRIGGER set_custom_id_after_insert
    AFTER INSERT ON users
    FOR EACH ROW
BEGIN
    IF NEW.custom_id IS NULL OR NEW.custom_id = '' THEN
        UPDATE users
        SET custom_id = NEW.id
        WHERE id = NEW.id;
    END IF;
END;

我们知道,SpringBoot Application 在启动过程中会扫描 resources 文件夹下的 schema.sqldata.sql 并执行,因此理论上我们可以将这段 SQL 丢到这两个文件的任意一个里面,让 Spring 框架去处理,但是有些事情是超出理论之外的🤣。

我们先了解一下前面提到的几个文件在启动流程中的执行先后顺序(当然这跟本次问题没什么关系(虽然我一开始错误的以为这引发了问题),就当科普了):

schema.sql > data.sql > 初始化 JPA,开始扫描实体类和关联的 @Entity 注解,基于实体类的定义来检查或自动生成数据库表结构

根据这个顺序,我们可以想一下一个问题:如果这个项目是在一个全新的机器上运行(先前没配置过数据库表结构)(我这个项目是依赖 JPA 自动生成数据库表结构的),那就会出现触发器始终注册不上的问题。

但是在我的开发环境中,我是先在我本地数据库中创建过数据库表了的 ,也就是说不应该存在因为先后顺序问题导致触发器无法注册的问题的。但是如你所见,执行查询操作发现触发器没有正确注册:

也就是说,通过使用 Spring 框架提供的自动扫描执行功能不能正常注册触发器(此时我还是认为是先后顺序导致的问题)。

随后我考虑了一些数据库迁移工具(如 FlywayLiquibase等)。(具体操作步骤在此处不赘述,用过的应该了解)

直接说结果:

Flyway 不支持 MySQL 8.4 (我一开始用的是 MySQL 9.0,确实挺高的,结果降级了也不支持)

Liquibase 会报一个 SQL Syntax Error

plaintext 复制代码
Caused by: liquibase.exception.DatabaseException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '' at line 9 [Failed SQL: (1064) CREATE TRIGGER set_custom_id_after_insert
                AFTER INSERT ON users
                FOR EACH ROW
            BEGIN
                IF NEW.custom_id IS NULL OR NEW.custom_id = '' THEN
                UPDATE users
                SET custom_id = NEW.id
                WHERE id = NEW.id;
            END IF]
at liquibase.executor.jvm.JdbcExecutor$ExecuteStatementCallback.doInStatement(JdbcExecutor.java:473)
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:80)
at liquibase.executor.jvm.JdbcExecutor.execute(JdbcExecutor.java:182)
at liquibase.executor.AbstractExecutor.execute(AbstractExecutor.java:141)
at liquibase.database.AbstractJdbcDatabase.executeStatements(AbstractJdbcDatabase.java:1176)
at liquibase.changelog.ChangeSet.execute(ChangeSet.java:764)
... 58 common frames omitted

但是实际上我的语法是没问题的(因为我在手动执行 SQL 的时候是没有报错的)

最后,我尝试在 @PostConstruct 方法中执行 SQL 语句,也会报一个和上面 Liquibase 一样的错误。

其执行时机是这样的:

  1. Spring 容器创建 Bean 实例:Spring 容器首先根据配置创建 Bean 实例。
  2. 依赖注入:Spring 执行依赖注入,将通过 @Autowired 或构造函数注入的依赖注入到 Bean 中。
  3. 执行 @PostConstruct 方法:依赖注入完成后,Spring 容器会自动调用被 @PostConstruct 注解的方法,完成一些初始化逻辑。
  4. Bean 准备就绪:在 @PostConstruct 方法执行之后,Bean 已经完全初始化,可以开始处理请求。

在 @PostConstruct 方法中,我先后尝试了 JdbcTemplateEntityManager ,均会触发报错。

也就是说,通过框架代码执行注册触发器的操作行不通,但是这个触发器还必须要在业务开始处理前完成注册。总不可能每次都叫人手动执行吧?求大佬指教,我是否漏掉了任何可行的自动处理的方案。

各依赖项的版本信息

SpringBoot 3.3.2

MySQL 8.4.2 Homebrew

Flyway 10.18.1

Liquibase implementation("org.liquibase:liquibase-core") (这个没看版本,因为试了一下也存在前面其他方法同样的问题)

相关推荐
java—大象16 分钟前
基于Java+Jsp+SpringMVC漫威手办商城系统设计和实现
java·数据库·spring boot·python·课程设计
假装我不帅18 分钟前
asp.net mvc 常用特性
后端·asp.net·mvc
IT学长编程2 小时前
计算机毕业设计 校运会管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·校园会管理系统
无惧代码2 小时前
实现quartz定时任务控制是否启动 (生产环境 和 开发环境)
java·spring boot·spring
小旋风-java2 小时前
springboot整合dwr
java·spring boot·后端·dwr
Pandaconda2 小时前
【计算机网络 - 基础问题】每日 3 题(二十六)
开发语言·经验分享·笔记·后端·计算机网络·面试·职场和发展
虽千万人 吾往矣2 小时前
golang strings api接口
开发语言·后端·golang
景天科技苑2 小时前
【Go语言】深入解读Go语言中的指针,助你拨开迷雾见月明
开发语言·后端·golang·指针·go指针·go语言指针
虽千万人 吾往矣2 小时前
golang格式化输入输出
开发语言·后端·golang
听潮阁3 小时前
【SpringBoot详细教程】-05-整合Druid操作数据库【持续更新】
数据库·spring boot·后端