「联查直更」项目前端
后端地址:https://github.com/Huaguang-XinZhe/JointQueryUpdaterBackend?tab=readme-ov-file
这是一个非常简单的 “项目”(或许算不上项目),前端用 Vue2 + 原始的前端三件套构建(用了少许的 Element UI),后端用 Spring Boot + MyBatis 构建。目前已经部署上线,可通过此域名❓访问。
为什么我要做这个 “项目” ?
我有一个记录的习惯,平时会不自觉的收集一些想法、疑问、待办、需求等等,这些东西都在同一个入口(Typora)进行记录,日渐累积。
等到终于有空了,我对这些记录进行整理,集中打标,然后通过程序扫描,分列,入库。这样方便我通过 SQL 语句进行查询分析,也能快速、批量修改一些数据。
根据记录数据的结构,我创建了以下两张表:
-- 创建一张输入表(主表)
create table myinput(
id int primary key auto_increment,
content text not null -- TEXT 类型理论上可以存储 65535 个字符的数据(实际会小于这个数),够用了
);
-- 创建一张标签表
create table mytag(
id int primary key auto_increment,
input_id int not null,
c1 varchar(20) not null,
c2 varchar(50),
c3 varchar(50),
foreign key (input_id) references myinput(id) -- 外键在多的一方
);
呈现如下结构:
很好,现在就可以自由分析、修改了。
我执行了这条联查 SQL:
select i.content, t.c1, t.c2
from myinput i
left join mytag t on i.id = t.input_id
where t.c1 = '待办';
但当我试图修改时,却提示说结果是只读的:
在 GPT 的帮助下,我在以上 SQL 的上方加了一句,创建了一个视图:
create view v_c1 as
这次允许修改了,但提交后还是报错了:
仔细查看其更新 SQL:
ling_guang> UPDATE ling_guang.v_c1 t SET t.c1 = 'Typora' WHERE t.content LIKE '在暗黑模式下需要修改一下滚动条的颜色,增强对比度。' ESCAPE '#' AND t.c1 LIKE '待办' ESCAPE '#' AND t.c2 LIKE 'Typora 样式' ESCAPE '#'
t 表(mytag 表)中是没有 content 字段的,t.content
不管怎样模糊匹配,都找不到对应的可更新条目。
我又反复测试了几次,也试了试单表查询,发现单表查询的结果是可以修改的,对单表查询创建的视图也是,而多表联查就不行!
那怎么办呢?我只能通过联查才能在看到内容的同时看到其标签呀,而且我是有可能对数据进行修改的。
如果我能自己掌控更新语句就好了,这样我就能根据修改的位置判断它要更新那张表的哪个字段、哪一行了。
“哪个字段” 好办,“哪一行” 的话,就还需要一点数据。
我得把两张表的 id 在联查的时候暴露出来,这样就能知道是哪一行了。
于是,我创建了这样的视图:
create view v_c2 as
select i.id as myinput_id, t.id as mytag_id, i.content, t.c1, t.c2
from myinput i
left join mytag t on i.id = t.input_id
where t.c1 = '待办';
但结果依然不理想:
DataGrip 的更新语句让我觉得有点 “傻”,我都给了更新位置所在行的 id 了,它判断一下更新的是哪张表,然后选择那张表的 id 去定点更新不就行了,为啥老是要用模糊匹配呢?
随后,我让 GPT 帮我找找有没有能满足我需求的插件或工具啥的,但没找到,这符合我的预期,毕竟这个需求太小、太细了。
看来,我只能自己造了。
彼时我正在学习 Vue2,也正好有这个能力,那就干吧。
我管这个 “应用” 叫「联查直更」,取 “能在多表联查的结果上直接修改更新” 之意。目前该功能确已实现。
用的时候,只需要将联查 SQL 复制粘贴到输入框中,然后点击确定或按下 Ctrl + Enter
,即可将该 SQL 语句发至服务器远程执行,并返回数据,过滤无关列(默认)后展示。
然后便可在查询结果上直接修改了,修改过的单元格会高亮显示,全部修改完成后可点击 “全部提交”(仿 DataGrip 设计),而后程序会根据所有修改的位置自动生成相应的更新语句,完成更新并返回结果。
每一个修改单元格都是一个更新对象,多个更新对象形成一个列表,一同发送到服务器。
@Data
public class UpdateObj {
// String originalValue;
@JsonProperty("changedValue")
String value;
@JsonProperty("fieldName")
String column;
@JsonProperty("rowId")
int id;
@JsonProperty("tableName")
String table;
}
为优化性能,同一张表同一行的不同列将会放在一起更新:
// 对 updateObjs 进行分组
Map<UpdateKey, Map<String, Object>> groupedUpdates = new HashMap<>();
for (UpdateObj updateObj : updateObjs) {
UpdateKey key = new UpdateKey(updateObj.getTable(), updateObj.getId());
// computeIfAbsent() 方法的作用是:如果 key 不存在,则创建一个新的 value,否则返回已存在的 value
groupedUpdates.computeIfAbsent(key, k -> new HashMap<>())
.put(updateObj.getColumn(), updateObj.getValue());
}
System.out.println("groupedUpdates = " + groupedUpdates);
而 MyBatis 负责动态生成更新语句:
<!-- 这里的 column 代表 updates(Map) 中的键,而 value 代表其中的值 -->
<update id="updateTable">
UPDATE ${table}
SET
<foreach item="value" index="column" collection="updates" separator=",">
${column} = #{value}
</foreach>
WHERE id = #{id}
</update>
这个 “项目” 我花了近一周时间,核心功能已经实现,但还有一些已测试的交互没能应用。
因为我突然发现自己的需求有点简单,稍微忍一忍(交互),把自己的核心诉求解决后好像就没它什么事儿了,感觉再做下去意义不大。
也确实,它并不是必须的,把两个表合为一个表,这不就避免了 “在多表联查结果上直接修改” 的问题了吗?当时怎么没想到呢?还是前期准备工作做的不够,轻易投入了!
颇有点后悔,毕竟又是一个星期。不过,一想到这是对 Vue2 的一次顶好的实践,心情就好多了,我并没有失去太多。
这个 “项目” 还可以继续发展,至少可以把目前写死的部分(表数据)盘活来,可以加上一点 SQL 注入的安全性检测。
这样下次遇到类似情境的时候就可以快速上手,而无需手动修改数据源和表头相关代码了。
有时间的时候再做吧。