问题背景
在实际业务中,我们常会遇到数据冗余问题。例如,一个公司表(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多表数据清洗的资料请关注代码网其它相关文章!
发表评论