增加
原则:先查用户,再查中间表。使用 saveBatch 批量插入中间表高效
@Transactional // 记得加事务
public void addUserWithRoles(User user, List<Long> roleIds) {
// 1. 保存用户
userMapper.insert(user);
// 2. 批量插入中间表
if (CollUtil.isNotEmpty(roleIds)) {
List<UserRole> list = roleIds.stream()
.map(rid -> new UserRole(user.getId(), rid))
.collect(Collectors.toList());
userRoleMapper.insertBatchSomeColumn(list); // 需配置批量插件
}
}
删除
原则:删除中间表关联,可选是否删用户主表
@Transactional
public void removeUserRoles(Long userId, List<Long> roleIds) {
// 构造删除条件:用户ID + 角色ID列表
LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserRole::getUserId, userId)
.in(UserRole::getRoleId, roleIds);
userRoleMapper.delete(wrapper);
// 若需删除用户主表:userMapper.deleteById(userId);
}
修改
原则:先删后增,简单可靠,无需比对差异
@Transactional
public void updateUserRoles(Long userId, List<Long> newRoleIds) {
// 1. 删除该用户所有旧关联
LambdaQueryWrapper<UserRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserRole::getUserId, userId);
userRoleMapper.delete(wrapper);
// 2. 插入新关联(复用“增”的批量逻辑)
if (CollUtil.isNotEmpty(newRoleIds)) {
// ...批量插入代码同上
}
}
查询
分步执行,查询2次数据库
- 规避了N+1问题:批量查userIds IN (...),而不是for循环单查
- 内存组装高效:groupingBy是O(n)复杂度,比数据库连表JOIN快
- SQL简洁可控:两条简单SQL,DBA一眼看懂
- 扩展性好:以后加缓存,直接缓存userMapper.selectList(null)结果即可
public List<User> getUsersWithRoles() {
// 1. 查所有用户
List<User> users = userMapper.selectList(null);
if (CollUtil.isEmpty(users)) return users;
// 2. 提取所有userId,批量查中间表和角色
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
// 连表查询:SELECT u.*, r.* FROM user_role ur LEFT JOIN role r ON ur.role_id = r.id WHERE ur.user_id IN (...)
List<UserRoleDTO> mappings = userRoleMapper.selectRoleListByUserIds(userIds);
// 3. 组装到User对象(按userId分组)
Map<Long, List<Role>> map = mappings.stream()
.collect(Collectors.groupingBy(UserRoleDTO::getUserId,
Collectors.mapping(dto -> new Role(dto.getRoleId(), dto.getRoleName()), toList())));
users.forEach(u -> u.setRoles(map.getOrDefault(u.getId(), emptyList())));
return users;
}