liquibase执行创建存储过程sql脚本

488 阅读3分钟

需求背景:楼层需要设置楼层负责人,上一版是多对一,一个楼层只有一个管理员,多个楼层可以是同一个管理员,随着迭代的更新,现在要做的是,一个楼层支持多个管理员,于是乎,需要实现这个功能就有2个方案:

1. 保持现有db结构不变

  • 但是要将多个管理员的用户id,使用逗号(",")拼接来实现 。
  • 这样带来的查询问题,比如说查看当前用户管理了哪些楼层 ,那就要去managerId字段 去使用mysql的find_in_set函数了,>0代表该条数据的管理员里包含了当前用户,但是这样其实不太符合关联关系的标准维护方式。

2. 增加管理表,维护关联关系

增加一个manager_link表,把manager_id抽到关联表里,这样好像更标准化一点 ,但是这也有对应的问题

  • 当要按照管理员过滤楼层列表,或者填充楼层的管理员信息的时候 ,都多了一步额外的查询过程
  • 增加了一个表,且要做数据修复,尤其是数据修复需要准备脚本

但是最后还是选择了加表的方式,原因有2个:

  1. 数据暂时不大 几十条而已 脚本风险不太大
  2. 对于修数据脚本,工程集成了liquibase,这样可以在更新代码的时候把脚本维护到工程里,只要后端升级,即可实现代码端,db端同步升级,避免了额外的执行db脚本过程,一步搞定,所以野蛮省心的

3. 过程

于是我准备了一个ddl语句建个表,同时我想到了通过存储过程来在db层面 实现业务逻辑从而达到修理数据的效果

sql内容如下:

create table if not exists dormitory_floor_manager_link
(
  id        varchar(64) not null comment '主键Id' primary key,
  floor_id  varchar(64)      not null comment '楼层id',
  manager_id varchar(64) not null comment '宿管用户id'

)
    comment '关联表';

-- 搞一个存储过程处理
DELIMITER $$
CREATE PROCEDURE MoveDataToNewTable()
BEGIN
    -- 声明变量
    DECLARE done INT DEFAULT FALSE;
    DECLARE floor_id VARCHAR(64);
    DECLARE new_manager_id VARCHAR(64);
    DECLARE current_id BIGINT DEFAULT 1793837134670712834;
    -- 起始ID
    -- 定义游标
    DECLARE cur CURSOR FOR SELECT id, manager_id FROM dormitory_floor WHERE deleted = 0 AND manager_id IS NOT NULL;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    -- 打开游标
    OPEN cur;
    -- 循环读取并插入数据
    read_loop:
    LOOP
        FETCH cur INTO floor_id, new_manager_id;
        IF done THEN
            LEAVE read_loop;
        END IF;
        -- 插入数据到新表
        INSERT INTO dormitory_floor_manager_link (id, floor_id, manager_id, creator, create_at, updater, update_at)
        VALUES (current_id, floor_id, new_manager_id, 1, NOW(), 1, NOW());
        -- ID递增
        SET current_id = current_id + 1;
    END LOOP;
    -- 关闭游标
    CLOSE cur;
END$$
DELIMITER ;
-- 执行存储过程脚本
call MoveDataToNewTable();
drop PROCEDURE MoveDataToNewTable;


于是我打开datagrip 信息满满的选中内容执行 nice!! 效果就是我想要的 所以我就开始了代码的开发,等到开发完成升级的的时候我恢复db初始态 ,重新执行脚本即可。

没想到在我完成了开发,准备整理好liquibase脚本文件,升级后端的时候遇到了问题,一直提示DELIMITER $$处语法不对 经过一顿调查之后 发现 如果要通过liquibase来执行创建存储过程的脚本,是和平时不一样的

平时sql脚本都是这样的,准备好sql文件 写好一个changeSet即可

- changeSet:
    id: 20240603_add_column_bed_use
    author: xxx
    runOnChange: true
    changes:
      - sqlFile:
          path: db/changelog/sql/xxx.sql

但是存储过程的changeSet和平时的有点不一样

- changeSet:
    id: 20240812_create_procedure_move_data
    author: xxx
    runOnChange: true
    changes:
      - createProcedure:
          path: db/changelog/sql/xxx.sql

需要使用createProcedure标签,path属性指定存储过程的sql脚本

于是我将上面的sql拆成三部分

  1. 建表
  2. 建存储过程
  3. 调用存储过程,删除存储过程,删表字段

直接上最终结果

- changeSet:
    id: create_table
    author: circle
    runOnChange: true
    changes:
      - sqlFile:
          path: db/changelog/sql/create_table.sql
- changeSet:
    id: create_procedure_move_data
    author: circle
    runOnChange: true
    changes:
      - createProcedure:
          path: db/changelog/sql/create_procedure_move_data.sql
- changeSet:
    id: call_procedure_and_drop
    author: circle
    runOnChange: true
    changes:
      - sqlFile:
          path: db/changelog/sql/call_procedure_and_drop.sql

4. 提一点

ps: 注意 创建存储过程的语句 不需要DELIMITER$$了, sql脚本内容如下即可

CREATE PROCEDURE MoveDataToNewTable()
BEGIN
    -- 声明变量
    DECLARE done INT DEFAULT FALSE;
    DECLARE floor_id VARCHAR(64);
    DECLARE new_manager_id VARCHAR(64);
    DECLARE current_id BIGINT DEFAULT 123456789;
    -- 起始ID
    -- 定义游标
    DECLARE cur CURSOR FOR SELECT id, manager_id FROM dormitory_floor WHERE deleted = 0 AND manager_id IS NOT NULL;
DECLARE
CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    -- 打开游标
OPEN cur;
-- 循环读取并插入数据
read_loop
:
    LOOP
        FETCH cur INTO floor_id, new_manager_id;
        IF
done THEN
            LEAVE read_loop;
END IF;
        -- 插入数据到新表
INSERT INTO dormitory_floor_manager_link (id, floor_id, manager_id) VALUES (current_id, floor_id, new_manager_id);
-- ID递增
SET
current_id = current_id + 1;
END LOOP;
    -- 关闭游标
CLOSE cur;
END
 ;