Mybatis学习笔记(九)

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

常见问题与解决方案

简要描述:总结MyBatis-Plus开发过程中常见的问题、错误及其解决方案,帮助开发者快速定位和解决问题。

核心概念

  • 常见错误:开发中经常遇到的错误类型
  • 性能问题:性能相关问题的排查和解决
  • 配置问题:配置相关的常见问题
  • 最佳实践:推荐的开发实践和规范

常见错误及解决

简要描述:MyBatis-Plus开发中常见的错误类型及其解决方法。

核心概念

  • SQL错误:SQL语法和执行错误
  • 映射错误:实体类和数据库字段映射错误
  • 配置错误:配置文件相关错误
  • 注解错误:注解使用不当导致的错误

SQL相关错误

// 1. 字段不存在错误
// 错误示例:
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("user_name", "张三"); // 数据库字段是username,不是user_name

// 解决方案:
@TableField("username") // 明确指定数据库字段名
private String userName;

// 或者在QueryWrapper中使用正确的字段名
wrapper.eq("username", "张三");

// 2. 表名不存在错误
// 错误示例:
@TableName("sys_user") // 数据库表名是user,不是sys_user
public class User {
    // ...
}

// 解决方案:
@TableName("user") // 使用正确的表名
public class User {
    // ...
}

// 3. 主键策略错误
// 错误示例:
@TableId(type = IdType.AUTO) // 数据库主键不是自增的
private Long id;

// 解决方案:
@TableId(type = IdType.ASSIGN_ID) // 使用雪花算法生成ID
private Long id;

// 4. 逻辑删除字段错误
// 错误示例:
@TableLogic
private Integer deleted; // 数据库字段类型是TINYINT,但使用了Integer

// 解决方案:
@TableLogic(value = "0", delval = "1")
private Boolean deleted; // 使用Boolean类型

分页查询错误

// 1. 分页插件未配置
// 错误现象:分页查询返回全部数据
// 解决方案:正确配置分页插件
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

// 2. 分页参数错误
// 错误示例:
Page<User> page = new Page<>(0, 10); // 页码从0开始

// 解决方案:
Page<User> page = new Page<>(1, 10); // 页码从1开始

// 3. 分页查询count错误
// 错误示例:
Page<User> page = new Page<>(1, 10);
page.setSearchCount(false); // 禁用count查询但又需要总数

// 解决方案:
Page<User> page = new Page<>(1, 10);
page.setSearchCount(true); // 启用count查询

事务相关错误

// 1. 事务不生效
// 错误示例:
@Service
public class UserService {
    @Transactional
    private void updateUser(User user) { // private方法,事务不生效
        // ...
    }
}

// 解决方案:
@Service
public class UserService {
    @Transactional
    public void updateUser(User user) { // public方法
        // ...
    }
}

// 2. 异常被捕获导致事务不回滚
// 错误示例:
@Transactional
public void updateUser(User user) {
    try {
        userMapper.updateById(user);
        int i = 1 / 0; // 抛出异常
    } catch (Exception e) {
        log.error("更新用户失败", e); // 异常被捕获,事务不回滚
    }
}

// 解决方案:
@Transactional(rollbackFor = Exception.class)
public void updateUser(User user) {
    try {
        userMapper.updateById(user);
        int i = 1 / 0;
    } catch (Exception e) {
        log.error("更新用户失败", e);
        throw e; // 重新抛出异常,触发事务回滚
    }
}

性能问题排查

简要描述:识别和解决MyBatis-Plus应用中的性能问题。

核心概念

  • 慢查询识别:识别执行缓慢的SQL
  • 内存泄漏:排查内存泄漏问题
  • 连接池问题:数据库连接池相关问题
  • 缓存问题:缓存使用不当导致的性能问题

慢查询排查

// 1. 开启SQL日志
// application.yml
logging:
  level:
    com.example.mapper: debug
    org.springframework.jdbc: debug

// 2. 使用性能分析插件
@Configuration
public class PerformanceConfig {
    @Bean
    @Profile("dev")
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor interceptor = new PerformanceInterceptor();
        interceptor.setMaxTime(1000); // 超过1秒的SQL会被记录
        interceptor.setFormat(true);
        return interceptor;
    }
}

// 3. 自定义慢查询监控
@Component
public class SlowQueryMonitor {
    
    private static final Logger logger = LoggerFactory.getLogger(SlowQueryMonitor.class);
    
    @EventListener
    public void handleSlowQuery(SlowQueryEvent event) {
        if (event.getExecutionTime() > 1000) {
            logger.warn("慢查询检测: SQL={}, 执行时间={}ms, 参数={}", 
                event.getSql(), event.getExecutionTime(), event.getParameters());
            
            // 可以发送告警通知
            sendSlowQueryAlert(event);
        }
    }
    
    private void sendSlowQueryAlert(SlowQueryEvent event) {
        // 发送告警逻辑
    }
}

内存泄漏排查

// 1. SqlSession未关闭导致内存泄漏
// 错误示例:
public List<User> findUsers() {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    List<User> users = sqlSession.selectList("findUsers");
    // 未关闭SqlSession,导致内存泄漏
    return users;
}

// 解决方案:
public List<User> findUsers() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
        return sqlSession.selectList("findUsers");
    } // 自动关闭SqlSession
}

// 2. 大结果集查询导致内存溢出
// 错误示例:
public List<User> findAllUsers() {
    return userMapper.selectList(null); // 查询所有数据,可能导致内存溢出
}

// 解决方案:使用分页查询
public List<User> findAllUsers() {
    List<User> allUsers = new ArrayList<>();
    int pageSize = 1000;
    int current = 1;
    
    while (true) {
        Page<User> page = new Page<>(current, pageSize);
        IPage<User> result = userMapper.selectPage(page, null);
        allUsers.addAll(result.getRecords());
        
        if (result.getRecords().size() < pageSize) {
            break;
        }
        current++;
    }
    
    return allUsers;
}

版本升级注意事项

简要描述:MyBatis-Plus版本升级过程中需要注意的重要事项和兼容性问题。

核心概念

  • 版本兼容性:不同版本间的兼容性问题
  • API变更:API接口的变更和废弃
  • 配置变更:配置方式的变化
  • 依赖升级:相关依赖的升级要求

3.x升级到4.x注意事项

# 1. 依赖变更
# 旧版本(3.x)
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3</version>
</dependency>

# 新版本(4.x)
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>4.0.0</version>
</dependency>

# 2. 配置变更
# 旧配置方式
mybatis-plus:
  global-config:
    db-config:
      id-type: auto
      logic-delete-value: 1
      logic-not-delete-value: 0

# 新配置方式
mybatis-plus:
  global-config:
    db-config:
      id-type: auto
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0

API变更处理

// 1. 分页插件配置变更
// 旧版本配置
@Configuration
public class MybatisPlusConfig {
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
        return paginationInterceptor;
    }
}

// 新版本配置
@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        return interceptor;
    }
}

// 2. 性能分析插件变更
// 旧版本
@Bean
public PerformanceInterceptor performanceInterceptor() {
    return new PerformanceInterceptor();
}

// 新版本(已废弃,建议使用第三方工具)
// 使用p6spy或其他性能监控工具

升级检查清单

// 升级前检查清单
public class UpgradeChecklist {
    
    /**
     * 1. 检查依赖兼容性
     * - Spring Boot版本兼容性
     * - JDK版本要求
     * - 其他依赖库版本
     */
    
    /**
     * 2. 检查配置文件
     * - application.yml配置项变更
     * - 插件配置方式变更
     * - 全局配置项变更
     */
    
    /**
     * 3. 检查代码兼容性
     * - 废弃API的替换
     * - 新增注解的使用
     * - 方法签名变更
     */
    
    /**
     * 4. 测试验证
     * - 单元测试通过
     * - 集成测试通过
     * - 性能测试对比
     */
}

最佳实践总结

简要描述:MyBatis-Plus开发的最佳实践和推荐规范总结。

核心概念

  • 代码规范:代码编写规范
  • 性能优化:性能优化建议
  • 安全实践:安全相关的最佳实践
  • 维护性:提高代码可维护性的建议

开发规范总结

// 1. 实体类设计规范
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("sys_user")
public class User implements Serializable {
    
    private static final long serialVersionUID = 1L;
    
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private Long id;
    
    @TableField("username")
    @NotBlank(message = "用户名不能为空")
    private String username;
    
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableLogic(value = "0", delval = "1")
    private Boolean deleted;
    
    @Version
    private Integer version;
}

// 2. Mapper接口规范
@Mapper
public interface UserMapper extends BaseMapper<User> {
    
    /**
     * 自定义查询方法要有明确的注释
     */
    List<User> selectByCustomCondition(@Param("condition") UserQueryCondition condition);
}

// 3. Service层规范
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    
    @Override
    public IPage<User> findUserPage(UserPageRequest request) {
        // 参数校验
        validatePageRequest(request);
        
        // 构建查询条件
        LambdaQueryWrapper<User> wrapper = buildQueryWrapper(request);
        
        // 执行分页查询
        Page<User> page = new Page<>(request.getCurrent(), request.getSize());
        return baseMapper.selectPage(page, wrapper);
    }
    
    private void validatePageRequest(UserPageRequest request) {
        if (request.getCurrent() < 1) {
            throw new IllegalArgumentException("页码不能小于1");
        }
        if (request.getSize() > 100) {
            throw new IllegalArgumentException("每页大小不能超过100");
        }
    }
}

性能优化总结

// 1. 查询优化
public class QueryOptimization {
    
    // 使用字段选择,避免查询不必要的字段
    public List<UserSimpleVO> findUserSimpleList() {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.select(User::getId, User::getUsername, User::getEmail);
        return baseMapper.selectList(wrapper);
    }
    
    // 使用批量操作
    public boolean batchUpdateStatus(List<Long> userIds, Integer status) {
        if (CollectionUtils.isEmpty(userIds)) {
            return true;
        }
        
        List<User> users = userIds.stream()
                .map(id -> {
                    User user = new User();
                    user.setId(id);
                    user.setStatus(status);
                    return user;
                })
                .collect(Collectors.toList());
        
        return updateBatchById(users);
    }
    
    // 使用缓存
    @Cacheable(value = "users", key = "#id")
    public User findById(Long id) {
        return baseMapper.selectById(id);
    }
}

// 2. 连接池优化
spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 30000
      idle-timeout: 600000
      max-lifetime: 1800000

安全实践总结

// 1. 防止SQL注入
public class SecurityPractice {
    
    // 正确:使用参数化查询
    public List<User> findUsersByName(String username) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername, username);
        return baseMapper.selectList(wrapper);
    }
    
    // 错误:直接拼接SQL
    public List<User> findUsersByNameBad(String username) {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.apply("username = '" + username + "'"); // 危险!
        return baseMapper.selectList(wrapper);
    }
    
    // 2. 敏感信息处理
    public UserVO getUserInfo(Long userId) {
        User user = baseMapper.selectById(userId);
        if (user == null) {
            return null;
        }
        
        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        userVO.setPassword(null); // 不返回密码
        return userVO;
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到