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

相关推荐
搬码后生仔1 小时前
将 ASP.NET Core 应用程序的日志保存到 D 盘的文件中 (如 Serilog)
后端·asp.net
Suwg2091 小时前
《手写Mybatis渐进式源码实践》实践笔记(第七章 SQL执行器的创建和使用)
java·数据库·笔记·后端·sql·mybatis·模板方法模式
凡人的AI工具箱2 小时前
每天40分玩转Django:Django文件上传
开发语言·数据库·后端·python·django
spcodhu2 小时前
在 Ubuntu 上搭建 MinIO 服务器
linux·后端·minio
小码编匠2 小时前
2024 年各编程语言运行百万并发任务需多少内存?
java·后端·python
罗政3 小时前
PDF书籍《手写调用链监控APM系统-Java版》第10章 插件与链路的结合:SpringBoot环境插件获取应用名
java·spring boot·pdf
sin22013 小时前
springboot测试类里注入不成功且运行报错
spring boot·后端·sqlserver
努力的小雨3 小时前
灵感上线,云开发实现抽奖转盘是多么简单的一件事
后端
kirito学长-Java4 小时前
springboot/ssm网上宠物店系统Java代码编写web宠物用品商城项目
java·spring boot·后端
海绵波波1074 小时前
flask后端开发(9):ORM模型外键+迁移ORM模型
后端·python·flask