问题背景
在实际业务中,我们常会遇到数据冗余问题。例如,一个公司表(sys_company)中存在多条相同公司名的记录,但只有一条有效(del_flag=0),其余需要删除。删除前需将关联表(如合同草稿表、发票表等)的外键字段(如purchaser_id)替换为保留记录的id。这类问题通常涉及多表、多字段的动态更新,如何高效且安全地实现?
解决方案
我们将通过以下步骤实现:
- 配置化驱动:用配置类声明需要处理的表和字段,避免硬编码。
- 动态sql更新:通过mybatis xml实现批量更新和删除。
- 事务一致性:确保所有操作原子化执行。
实现步骤
1. 定义实体类
companyretainedinfo:封装需保留的公司信息
import lombok.data;
@data
public class companyretainedinfo {
private string companyname; // 公司名称
private long retainedid; // 需保留的公司id(del_flag=0的记录)
private string retainedname; // 需保留的公司名称(与companyname一致)
}
• 作用:映射查询结果,传递保留记录的id和名称。
• lombok:@data 自动生成getter/setter和tostring()方法。
2. 定义配置类
tableconfig:声明需处理的表和外键关系
public class tableconfig {
private string tablename; // 表名(如contract_draft)
private string idcolumn; // 外键id字段(如purchaser_id)
private string namecolumn; // 名称字段(如purchaser_name,可能为null)
// 构造器 + getter/setter
public tableconfig(string tablename, string idcolumn, string namecolumn) {
this.tablename = tablename;
this.idcolumn = idcolumn;
this.namecolumn = namecolumn;
}
}
3. 编写mybatis mapper接口
companycleanmapper:定义数据操作接口(无注解,纯xml映射)
@mapper
public interface companycleanmapper {
// 查询需保留的公司信息(del_flag=0)
list<companyretainedinfo> selectretainedcompanies();
// 根据公司名查询待删除的id列表(del_flag!=0)
list<long> selectidstodelete(string companyname);
// 更新关联表的外键引用
void updateforeignkeys(
@param("config") tableconfig config,
@param("retainedid") long retainedid,
@param("retainedname") string retainedname,
@param("ids") list<long> ids
);
// 删除冗余公司记录
void deletecompanies(@param("ids") list<long> ids);
}
4. 实现xml映射文件
companycleanmapper.xml:定义动态sql逻辑
<?xml version="1.0" encoding="utf-8"?>
<!doctype mapper public "-//mybatis.org//dtd mapper 3.0//en"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.companycleanmapper">
<!-- 查询需保留的公司 -->
<select id="selectretainedcompanies" resulttype="companyretainedinfo">
select
company_name as companyname,
id as retainedid,
company_name as retainedname
from sys_company
where del_flag = 0
and company_name in (
select company_name
from sys_company
group by company_name
having count(*) > 1 and sum(del_flag = 0) = 1
)
</select>
<!-- 查询待删除的id列表 -->
<select id="selectidstodelete" resulttype="long">
select id
from sys_company
where company_name = #{companyname}
and del_flag != 0
</select>
<!-- 动态更新外键引用 -->
<update id="updateforeignkeys">
update ${config.tablename}
set
<choose>
<when test="config.namecolumn != null">
<!-- 同时更新id和名称字段 -->
${config.idcolumn} = #{retainedid},
${config.namecolumn} = #{retainedname}
</when>
<otherwise>
<!-- 仅更新id字段 -->
${config.idcolumn} = #{retainedid}
</otherwise>
</choose>
where
${config.idcolumn} in
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 批量删除公司记录 -->
<delete id="deletecompanies">
delete from sys_company
where id in
<foreach item="id" collection="ids" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>
5. 服务层实现
companycleanservice:配置化驱动批量处理
@service
@requiredargsconstructor
public class companycleanservice {
private final companycleanmapper companycleanmapper;
// 配置需要处理的表和字段
private static final list<tableconfig> table_configs = arrays.aslist(
new tableconfig("contract_draft", "purchaser_id", "purchaser_name"),
new tableconfig("invoice", "company_id", "company_name")
// 按需添加其他表...
);
@transactional
public void cleanduplicatecompanies() {
// 1. 查询所有需保留的公司
list<companyretainedinfo> retainedcompanies = companycleanmapper.selectretainedcompanies();
for (companyretainedinfo info : retainedcompanies) {
// 2. 查询待删除的id列表
list<long> idstodelete = companycleanmapper.selectidstodelete(info.getcompanyname());
if (!idstodelete.isempty()) {
// 3. 更新所有关联表的外键引用
table_configs.foreach(config ->
companycleanmapper.updateforeignkeys(
config,
info.getretainedid(),
info.getretainedname(),
idstodelete
)
);
// 4. 删除冗余公司记录
companycleanmapper.deletecompanies(idstodelete);
}
}
}
}
关键设计说明
实体类与数据映射
•companyretainedinfo通过别名(as retainedid)直接映射查询结果,避免额外转换。
•companyname和retainedname字段值相同,但保留后者以明确语义。xml动态sql优势
•<choose>:根据配置动态决定是否更新名称字段。
•<foreach>:自动展开id列表为in (id1, id2...),支持批量操作。
•${}占位符:安全引用配置的表名和字段名(非用户输入,无注入风险)。事务与性能优化
•@transactional:保证“更新外键”和“删除公司”操作的原子性。
• 索引建议:对sys_company.company_name和关联表的外键字段添加索引。
总结
通过 实体类封装、配置化表关系 和 mybatis动态sql,我们实现了一套可扩展的多表数据清洗方案。这种模式的核心在于:
- 抽象变化部分:将表和字段的差异收敛到配置类中。
- 复用不变逻辑:批量更新和删除操作由统一服务驱动。
- 最小化侵入性:新增表只需修改配置,无需改动核心逻辑。
该方案适用于用户中心、商品系统等存在外键关联的冗余数据处理场景,读者可结合实际需求调整配置和sql逻辑。
以上就是在springboot+mybatis中优雅处理多表数据清洗的实现步骤的详细内容,更多关于springboot mybatis多表数据清洗的资料请关注代码网其它相关文章!
发表评论