在SpringBoot+MyBatis中优雅处理多表数据清洗的实现步骤

 更新时间:2025年03月06日 11:31:13   作者:好奇的菜鸟  
数据清洗是指对数据进行处理和纠错,以去除或修复数据集中存在的错误、不致、不完整和冗余的数据,从而使数据更加准确、可靠和有用,本文给大家介绍了在SpringBoot和MyBatis中优雅处理多表数据清洗的实现步骤,需要的朋友可以参考下

问题背景

在实际业务中,我们常会遇到数据冗余问题。例如,一个公司表(sys_company)中存在多条相同公司名的记录,但只有一条有效(del_flag=0),其余需要删除。删除前需将关联表(如合同草稿表、发票表等)的外键字段(如purchaser_id)替换为保留记录的ID。这类问题通常涉及多表、多字段的动态更新,如何高效且安全地实现?

解决方案

我们将通过以下步骤实现:

  1. 配置化驱动:用配置类声明需要处理的表和字段,避免硬编码。
  2. 动态SQL更新:通过MyBatis XML实现批量更新和删除。
  3. 事务一致性:确保所有操作原子化执行。

实现步骤

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);
            }
        }
    }
}

关键设计说明

  1. 实体类与数据映射
    • CompanyRetainedInfo 通过别名(AS retainedId)直接映射查询结果,避免额外转换。
    • companyName 和 retainedName 字段值相同,但保留后者以明确语义。

  2. XML动态SQL优势
    • <choose>:根据配置动态决定是否更新名称字段。
    • <foreach>:自动展开ID列表为IN (id1, id2...),支持批量操作。
    • ${}占位符:安全引用配置的表名和字段名(非用户输入,无注入风险)。

  3. 事务与性能优化
    • @Transactional:保证“更新外键”和“删除公司”操作的原子性。
    • 索引建议:对sys_company.company_name和关联表的外键字段添加索引。

总结

通过 实体类封装配置化表关系 和 MyBatis动态SQL,我们实现了一套可扩展的多表数据清洗方案。这种模式的核心在于:

  1. 抽象变化部分:将表和字段的差异收敛到配置类中。
  2. 复用不变逻辑:批量更新和删除操作由统一服务驱动。
  3. 最小化侵入性:新增表只需修改配置,无需改动核心逻辑。

该方案适用于用户中心、商品系统等存在外键关联的冗余数据处理场景,读者可结合实际需求调整配置和SQL逻辑。

以上就是在SpringBoot+MyBatis中优雅处理多表数据清洗的实现步骤的详细内容,更多关于SpringBoot MyBatis多表数据清洗的资料请关注脚本之家其它相关文章!

相关文章

  • Java服务限流算法的6种实现

    Java服务限流算法的6种实现

    服务限流是指通过控制请求的速率或次数来达到保护服务的目的,本文主要介绍了Java服务限流算法的6种实现,具有一定的参考价值,感兴趣的可以了解一下
    2023-05-05
  • Open Feign之非SpringCloud方式使用示例

    Open Feign之非SpringCloud方式使用示例

    这篇文章主要为大家介绍了Open Feign之非SpringCloud方式使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • spring boot 本地图片不能加载(图片路径)的问题及解决方法

    spring boot 本地图片不能加载(图片路径)的问题及解决方法

    这篇文章主要介绍了spring boot 本地图片不能加载(图片路径)的问题,解决的办法其实很简单,只要写一个配置文件,也就是图片位置的转化器,原理是虚拟一个在服务器上的文件夹,与本地图片的位置进行匹配。需要的朋友可以参考下
    2018-04-04
  • MyBatis的缓存解析

    MyBatis的缓存解析

    这篇文章主要介绍了MyBatis的缓存解析,一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会缓存,下次查询相同的数据就会从缓存中直接获取,不会从数据重新访问,前提必须是同一个SqlSession对象,并且查询的数据相同,需要的朋友可以参考下
    2023-09-09
  • Java获取本机IP地址的方法代码示例(内网、公网)

    Java获取本机IP地址的方法代码示例(内网、公网)

    在IT领域获取本机IP地址是一项基础但重要的任务,特别是在网络编程、远程协作和设备通信中,这篇文章主要给大家介绍了关于Java获取本机IP地址的方法(内网、公网),需要的朋友可以参考下
    2024-07-07
  • Java 类在 Tomcat 中是如何加载的(过程分析)

    Java 类在 Tomcat 中是如何加载的(过程分析)

    这篇文章主要介绍了Java 类在 Tomcat 中是如何加载的过程分析,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • SpringBoot浅析安全管理之基于数据库认证

    SpringBoot浅析安全管理之基于数据库认证

    在真实的项目中,用户的基本信息以及角色等都存储在数据库中,因此需要从数据库中获取数据进行认证和授权
    2022-08-08
  • Java8 接口默认方法和静态方法

    Java8 接口默认方法和静态方法

    这篇文章主要介绍了Java8 接口默认方法和静态方法,在默认接口中使用关键字default声明并提供具体实现,而且该方法不需要添加public关键字就可以公开调用,甚至你可以在其实现类中覆写,带着对默认接口的方法和小编一起探索下面文章内容的静态方法吧
    2021-10-10
  • springboot @Controller和@RestController的区别及应用详解

    springboot @Controller和@RestController的区别及应用详解

    这篇文章主要介绍了springboot @Controller和@RestController的区别及应用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • springMVC+jersey实现跨服务器文件上传

    springMVC+jersey实现跨服务器文件上传

    这篇文章主要介绍了springMVC+jersey实现跨服务器文件上传,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-08-08

最新评论