SQL 的艺术(续):用 MyBatis-Plus 精雕细琢“外科手术”级更新接口 ✨

我们再次切换到“SQL工匠”模式,用 MyBatis-Plus 来实现这个同样复杂的 updateSolutionBrand 接口。

使用 MyBatis-Plus 实现这个接口,将再次凸显它与 JPA 在处理事务部分更新关联更新方面的巨大差异。这篇博客将重点展示如何通过手写 SQL 和精巧的逻辑编排,来完成这次“外科手术”。


⚔️ SQL 的艺术(续):用 MyBatis-Plus 精雕细琢“外科手术”级更新接口

你好,我是坚持哥!在之前的文章中,我们已经领略了 MyBatis-Plus 在查询和简单创建方面的强大。今天,挑战再次升级!我们将面对一个后端开发中最复杂、也最考验基本功的场景之一——带有“部分更新”和“关联更新”的复杂修改接口

我们将以 updateSolutionBrand (修改品牌并更新其关联分类) 为例,深入探索:

  1. 如何在一个事务中,协调多个 Mapper,手动完成主表更新中间表删除中间表批量插入这三大操作?
  2. 如何利用 MyBatis 的动态 SQL,构建一个只更新必要字段的、精准的 UPDATE 语句?
  3. 如何通过逻辑编排,清晰地实现多对多关系的“覆盖式”更新?

如果你想看看如何用 MyBatis-Plus 像一位钟表匠一样,精密地组装出一个复杂的更新接口,那么,请准备好你的“放大镜”和“镊子”,我们的 SQL 精密工程,现在开始!️

接口需求与实现方案总结 (MyBatis-Plus 版)

环节 需求描述 / 实现方案
核心需求 修改一个“方案品牌” (SolutionBrand) 的基本信息,并能覆盖式地更新它所关联的“品牌分类” (SolutionBrandCategory)。
⚙️ 技术栈 Spring Boot, MyBatis-Plus, MySQL
关键逻辑 手动三步走:在一个 @Transactional 方法中,按顺序执行:
1. UPDATE 主表:调用自定义的动态 UPDATE 方法,实现部分更新。
2. DELETE 中间表:删除该品牌在中间表的所有旧关联。
3. INSERT 中间表:批量插入该品牌在中间表的新关联。
✍️ 部分更新 XML Mapper 中编写自定义 UPDATE 语句,使用 标签,只更新 Payload 中非 null 的字段。
关联更新 先删后增。先调用 relationMapper.delete() 删除所有旧关联,再调用 relationMapper.batchInsert() 插入新关联。
️ 架构模式 Controller -> Service -> Mapper 分层架构 + Payload/VO 数据传输模式。
️ 数据校验 在 Service 层进行权限校验(确保只能修改自己的数据)和业务校验。

️ 实现流程:MyBatis-Plus 的三步“手术”

使用 MyBatis-Plus,我们需要像外科医生一样,清晰地规划每一步操作。

Service 层核心事务逻辑 (@Transactional)
如果 payload 中有新的分类 ID
如果 payload 中分类 ID 为空
1. 权限和业务校验
调用 Service.updateSolutionBrand(adminId, id, payload)
步骤一:更新主表
调用 brandMapper.updatePartially(id, payload)
步骤二:删除旧关联
调用 relationMapper.delete(wrapper)
删除中间表所有相关记录
步骤三:插入新关联
构建 Relation 列表
调用 relationMapper.batchInsert(relations)
4. 查询更新后的完整数据并转换为 VO
前端发起 PUT 请求
/api/solution/brands/{id}
(Body 中包含要修改的字段)
Controller 接收请求
(包含 adminId, id, Payload)
Controller 获得 SolutionBrandVO
将 VO 包装进 BaseResult
返回 200 OK 和成功的 JSON 响应

交互时序:Service 如何“指挥”三场数据库战役

在这个场景下,Service 层就像一位运筹帷幄的将军,指挥着不同的 Mapper 在一个事务中协同作战。

Controller Service BrandMapper RelationMapper 数据库 updateSolutionBrand(adminId, id, payload) @Transactional 开始事务 执行权限和业务校验... updatePartially(id, payload) 执行 XML 中的动态 UPDATE SQL UPDATE solution_brand SET ... WHERE id = ? delete(wrapper with solutionBrandId) 执行 DELETE SQL DELETE FROM ..._relation WHERE solution_brand_id = ? 构建新的关联关系列表 batchInsert(relationList) 执行 XML 中的批量 INSERT SQL INSERT INTO ..._relation (...) VALUES (...), (...) ...所有操作成功,提交事务。 查询并转换为 VO 返回 SolutionBrandVO Controller Service BrandMapper RelationMapper 数据库

️ 架构设计:MyBatis-Plus 版的组件

Mapper 接口与 XML

我们需要为 SolutionBrand 编写一个自定义的动态 UPDATE 方法。

SolutionBrandMapper.java

@Mapper
public interface SolutionBrandMapper extends BaseMapper<SolutionBrand> {
    int updatePartially(
        @Param("id") Integer id,
        @Param("payload") SolutionBrandUpdatePayload payload
    );
}

SolutionBrandMapper.xml

<mapper namespace="...SolutionBrandMapper">
    <update id="updatePartially">
        UPDATE solution_brand
        <set>
            last_modified_date = NOW(),
            <if test="payload.solutionIntroduction != null">
                solution_introduction = #{payload.solutionIntroduction},
            if>
            <if test="payload.ranks != null">
                ranks = #{payload.ranks},
            if>
            <if test="payload.status != null">
                status = #{payload.status},
            if>
        set>
        WHERE id = #{id}
    update>
mapper>
Service 层

Service 层是所有逻辑的编排者,它需要精确地控制操作的顺序。

@Service
public class SolutionBrandService {
    @Autowired
    private SolutionBrandMapper solutionBrandMapper;
    @Autowired
    private SolutionBrandCategoryRelationMapper relationMapper;
    // ...

    @Transactional
    public SolutionBrandVO updateSolutionBrand(Integer adminId, Integer solutionBrandId, SolutionBrandUpdatePayload payload) {
        // 1. 权限和业务校验...
        
        // 2. 步骤一:部分更新主表
        solutionBrandMapper.updatePartially(solutionBrandId, payload);

        // 3. 步骤二:删除所有旧的关联
        LambdaUpdateWrapper<SolutionBrandCategoryRelation> deleteWrapper = new LambdaUpdateWrapper<>();
        deleteWrapper.eq(SolutionBrandCategoryRelation::getSolutionBrandId, solutionBrandId);
        relationMapper.delete(deleteWrapper);

        // 4. 步骤三:插入新的关联
        if (payload.getCategoryIds() != null && !payload.getCategoryIds().isEmpty()) {
            // ... 校验 categoryIds 的合法性 ...
            List<SolutionBrandCategoryRelation> relations = payload.getCategoryIds().stream()
                    .map(categoryId -> new SolutionBrandCategoryRelation(solutionBrandId, categoryId))
                    .collect(Collectors.toList());
            relationMapper.batchInsert(relations);
        }

        // 5. 查询最新数据并转换为 VO 返回
        return findAndConvertToVO(solutionBrandId);
    }
}

总结思维导图

最后,我们用一张思维导图来总结 MyBatis-Plus 实现复杂更新接口的完整思想。

SQL 的艺术(续):用 MyBatis-Plus 精雕细琢“外科手术”级更新接口 ✨_第1张图片

通过这个案例,我们能深刻体会到,当业务逻辑变得复杂,需要精细控制数据库操作时,MyBatis-Plus 提供的“手术刀”是多么锋利。虽然需要更多的手动编码,但换来的是无与伦比的控制力和性能优化的可能性。希望这次的实战分享,能让你在面对复杂更新时,更加胸有成竹!✨

你可能感兴趣的:(MyBatisPlus,sql,mybatis,数据库)