需求背景:楼层需要设置楼层负责人,上一版是多对一,一个楼层只有一个管理员,多个楼层可以是同一个管理员,随着迭代的更新,现在要做的是,一个楼层支持多个管理员,于是乎,需要实现这个功能就有2个方案:
1. 保持现有db结构不变
- 但是要将多个管理员的用户id,使用
逗号(",")拼接来实现 。 - 这样带来的查询问题,比如说查看当前用户管理了哪些楼层 ,那就要去managerId字段 去使用mysql的find_in_set函数了,>0代表该条数据的管理员里包含了当前用户,但是这样其实不太符合关联关系的标准维护方式。
2. 增加管理表,维护关联关系
增加一个manager_link表,把manager_id抽到关联表里,这样好像更标准化一点 ,但是这也有对应的问题
- 当要按照管理员过滤楼层列表,或者填充楼层的管理员信息的时候 ,都多了一步额外的查询过程
- 增加了一个表,且要做数据修复,尤其是数据修复需要准备脚本
但是最后还是选择了加表的方式,原因有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拆成三部分
- 建表
- 建存储过程
- 调用存储过程,删除存储过程,删表字段
直接上最终结果
- 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
;