[求助] 如何在 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") (这个没看版本,因为试了一下也存在前面其他方法同样的问题)

相关推荐
Albert Edison4 小时前
【最新版】IntelliJ IDEA 2025 创建 SpringBoot 项目
java·spring boot·intellij-idea
Piper蛋窝5 小时前
深入 Go 语言垃圾回收:从原理到内建类型 Slice、Map 的陷阱以及为何需要 strings.Builder
后端·go
六毛的毛7 小时前
Springboot开发常见注解一览
java·spring boot·后端
AntBlack7 小时前
拖了五个月 ,不当韭菜体验版算是正式发布了
前端·后端·python
31535669137 小时前
一个简单的脚本,让pdf开启夜间模式
前端·后端
uzong8 小时前
curl案例讲解
后端
开开心心就好9 小时前
免费PDF处理软件,支持多种操作
运维·服务器·前端·spring boot·智能手机·pdf·电脑
一只叫煤球的猫9 小时前
真实事故复盘:Redis分布式锁居然失效了?公司十年老程序员踩的坑
java·redis·后端
猴哥源码9 小时前
基于Java+SpringBoot的农事管理系统
java·spring boot
大鸡腿同学9 小时前
身弱武修法:玄之又玄,奇妙之门
后端