常见问题与解决方案
简要描述:总结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;
}
}