Spring Boot 菜单删除功能的实现与事务管理

发布于:2025-08-12 ⋅ 阅读:(20) ⋅ 点赞:(0)

在后台管理系统开发中,菜单管理是一个核心功能,而删除菜单作为其中的重要操作,需要考虑数据完整性、关联关系处理和操作安全性。本文将详细介绍 Spring Boot 环境下菜单删除功能的实现逻辑,包括关联数据处理、事务控制和异常处理等关键环节。

菜单删除的业务逻辑分析

菜单删除并非简单地删除一条记录,需要考虑以下几点:

  • 菜单可能存在多级嵌套关系(父菜单与子菜单)
  • 菜单与角色存在多对多关联(通过中间表 role_menu)
  • 操作需要具备原子性(要么全部成功,要么全部失败)

因此,完整的菜单删除逻辑应该是:先删除关联数据,再删除子菜单,最后删除父菜单本身,并通过事务保证整个过程的完整性。

实现代码与步骤解析

1. 控制器层(MenuController)

首先定义删除接口,接收菜单 ID 并添加事务注解:

@RestController
@RequestMapping("/menu")
public class MenuController {

    @Autowired
    private MenuService menuService;
    
    @Autowired
    private RoleMenuMapper roleMenuMapper;

    /**
     * 删除菜单
     * @param id 菜单ID
     * @return 操作结果
     */
    @DeleteMapping("/del/{id}")
    @Transactional  // 事务注解,确保操作原子性
    public Result deleteMenu(@PathVariable Integer id) {
        try {
            // 1. 查询所有子菜单
            QueryWrapper<MenuEntity> subMenuQuery = new QueryWrapper<>();
            subMenuQuery.eq("parent_id", id);
            List<MenuEntity> subMenus = menuService.list(subMenuQuery);
            
            // 2. 先删除子菜单与角色的关联关系
            if (!subMenus.isEmpty()) {
                for (MenuEntity subMenu : subMenus) {
                    QueryWrapper<RoleMenuEntity> roleMenuQuery = new QueryWrapper<>();
                    roleMenuQuery.eq("menu_id", subMenu.getMenuId());
                    roleMenuMapper.delete(roleMenuQuery);
                }
                
                // 3. 删除子菜单
                menuService.remove(subMenuQuery);
            }
            
            // 4. 删除当前菜单与角色的关联关系
            QueryWrapper<RoleMenuEntity> parentRoleMenuQuery = new QueryWrapper<>();
            parentRoleMenuQuery.eq("menu_id", id);
            roleMenuMapper.delete(parentRoleMenuQuery);
            
            // 5. 删除当前菜单
            boolean isDeleted = menuService.removeById(id);
            
            if (isDeleted) {
                return Result.success("菜单删除成功");
            } else {
                return Result.error("菜单不存在或已被删除");
            }
        } catch (Exception e) {
            // 抛出运行时异常,触发事务回滚
            throw new RuntimeException("删除菜单失败:" + e.getMessage());
        }
    }
}

2. 关键步骤解析

步骤 1:查询子菜单

通过 parent_id 查询当前菜单的所有子菜单,为后续级联删除做准备:

QueryWrapper<MenuEntity> subMenuQuery = new QueryWrapper<>();
subMenuQuery.eq("parent_id", id);
List<MenuEntity> subMenus = menuService.list(subMenuQuery);
步骤 2:删除子菜单与角色的关联关系

由于菜单与角色通过中间表 role_menu 关联,需要先删除这些关联数据,避免外键约束错误:

for (MenuEntity subMenu : subMenus) {
    QueryWrapper<RoleMenuEntity> roleMenuQuery = new QueryWrapper<>();
    roleMenuQuery.eq("menu_id", subMenu.getMenuId());
    roleMenuMapper.delete(roleMenuQuery);
}
步骤 3:删除子菜单

在删除关联数据后,批量删除所有子菜单:

menuService.remove(subMenuQuery);
步骤 4:删除当前菜单与角色的关联关系

同样需要解除当前菜单与角色的关联:

QueryWrapper<RoleMenuEntity> parentRoleMenuQuery = new QueryWrapper<>();
parentRoleMenuQuery.eq("menu_id", id);
roleMenuMapper.delete(parentRoleMenuQuery);
步骤 5:删除当前菜单

最后删除主菜单记录:

menuService.removeById(id);

事务管理与异常处理

  1. 事务保证:通过@Transactional注解,确保整个删除过程的原子性。如果任何步骤失败,所有操作都会回滚,避免数据不一致。

  2. 异常处理

    • 使用 try-catch 块捕获可能的异常
    • 抛出RuntimeException触发事务回滚(符合 Spring 事务默认回滚规则)
    • 提供清晰的错误信息,便于问题排查
  3. 删除顺序原则

    • 先删关联数据,再删主数据
    • 先删子菜单,再删父菜单
    • 遵循 "从外向内、从下到上" 的删除顺序,避免外键约束冲突