Spring Boot 3 + MyBatis-Plus 高性能持久层开发实战:从入门到调优

Spring Boot 3 + MyBatis-Plus 高性能持久层开发实战:从入门到调优

在 Java 后端开发中,持久层是连接业务逻辑与数据库的核心纽带,其性能与编码效率直接决定了整个系统的上限。Spring Boot 3 作为主流的微服务开发框架,凭借自动配置、简化部署等特性极大降低了开发成本;而 MyBatis-Plus(简称 MP)则在 MyBatis 的基础上,提供了强大的 CRUD 封装、分页插件、条件构造器等功能,彻底解放了开发者手写 SQL 的负担。

本文将从实战角度出发,深度解析 Spring Boot 3 与 MyBatis-Plus 的整合流程,结合丰富的代码案例,讲解核心功能的使用技巧,并针对持久层常见的性能瓶颈(如分页效率、SQL 优化、缓存设计)提供解决方案,帮助开发者构建高效、健壮的持久层架构。

一、环境准备与基础整合

1.1 环境依赖

首先搭建 Spring Boot 3 + MyBatis-Plus 基础环境,选用 MySQL 8.0 作为数据库(适配 Spring Boot 3 的兼容性),引入核心依赖(使用 Maven 管理)。

xml 复制代码
>    <groupId>org.springframework.boot<artifactId>spring-boot-starter-parent</artifactId>
    <version>3.2.</version>
    <relativePath/>
<dependencies<!-- Spring Boot Web 依赖(可选,用于接口测试) -->
<dependency>
        <groupId>org.springframework.boot<artifactId>spring-boot-starter-web</artifactId>
    <!-- MyBatis-Plus 核心<dependency<groupId>com.baomidou<artifactId>mybatis-plus-boot-st</artifactId>
       <version>3.5</version</dependency><!-- MySQL 驱动(适配 MySQL 8.0) -->
    <groupId</groupId<artifactId></artifactId<scope></scope>
   <!--  lombok 简化实体类(可选,推荐) --><dependency>
<groupId>org.projectl</groupId>
        <artifactId>lombok<optional</optional>
</dependency>

   <!-- 测试依赖 -->
<dependency>
        <groupId>org.springframework.boot<artifactId>spring-boot-starter-test</artifactId>
        </scope></dependency>

<!-- 分页插件依赖(MyBatis-Plus 内置,需手动配置<dependency<groupId>com.baomidou<artifactId>mybatis-plus-extension</artifactId>
        <version>3.5.</version></dependency>
</dependencies>

1.2 配置文件编写

在 application.yml 中配置数据库连接信息、MyBatis-Plus 核心参数(如 mapper 扫描路径、日志打印、驼峰命名映射等),确保框架能够正常工作。

yaml 复制代码
spring:
  # 数据库配置
  datasource:
    url: jdbc:mysql://localhost:3306/mp_demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

# MyBatis-Plus 配置
mybatis-plus:
  # 扫描 mapper.xml 文件(如果使用 XML 编写 SQL)
  mapper-locations: classpath:mapper/**/*.xml
  # 扫描实体类包(用于别名配置,可选)
  type-aliases-package: com.example.mpdemo.entity
  # 开启驼峰命名映射(数据库下划线命名 → Java 驼峰命名)
  configuration:
    map-underscore-to-camel-case: true
    # 打印 SQL 日志(开发环境开启,生产环境关闭)
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  # 全局配置(可选,统一配置主键生成策略等)
  global-config:
    db-config:
      # 主键生成策略:自增(MySQL 适用),可选 AUTO、INPUT、ASSIGN_ID 等
      id-type: auto

1.3 核心配置类

编写 MyBatis-Plus 配置类,注册分页插件、乐观锁插件等核心组件(Spring Boot 3 中需使用 @Configuration 注解,替代传统的 XML 配置)。

java 复制代码
package com.example.mpdemo.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * MyBatis-Plus 核心配置类
 * 注册分页插件、乐观锁插件等
 */
@Configuration
// 扫描 mapper 接口包(必须配置,否则 mapper 接口无法被 Spring 管理)
@MapperScan("com.example.mpdemo.mapper")
public class MyBatisPlusConfig {

    /**
     * 注册 MyBatis-Plus 拦截器(分页、乐观锁等功能依赖此拦截器)
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 1. 分页插件(指定数据库类型为 MySQL)
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 2. 乐观锁插件(用于解决并发更新冲突)
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}

二、核心功能实战(附代码案例)

本节将结合实际业务场景(以「用户管理」为例),讲解 MyBatis-Plus 的核心功能,包括实体类设计、Mapper 接口编写、CRUD 操作、条件构造器、分页查询等,所有案例均可直接复制到项目中使用。

2.1 实体类设计(Entity)

使用 Lombok 的 @Data 注解简化实体类编写,结合 MyBatis-Plus 的注解配置数据库表、字段映射关系、主键策略等。

java 复制代码
package com.example.mpdemo.entity;

import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;

import java.time.LocalDateTime;

/**
 * 用户实体类(对应数据库表:sys_user)
 */
@Data
// 指定数据库表名(如果实体类名与表名一致,可省略此注解)
@TableName("sys_user")
public class SysUser {

    /**
     * 主键 ID(自增策略,对应数据库主键自增)
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 用户名(非空,唯一)
     */
    @TableField("username")
    private String username;

    /**
     * 密码(加密存储)
     */
    private String password;

    /**
     * 昵称
     */
    private String nickname;

    /**
     * 性别(0:女,1:男,2:未知)
     */
    private Integer gender;

    /**
     * 手机号
     */
    private String phone;

    /**
     * 状态(0:禁用,1:正常)
     */
    private Integer status;

    /**
     * 创建时间(自动填充)
     */
    @TableField(value = "create_time", fill = FieldFill.INSERT)
    private LocalDateTime createTime;

    /**
     * 更新时间(自动填充)
     */
    @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
    private LocalDateTime updateTime;

    /**
     * 逻辑删除标识(0:未删除,1:已删除)
     * MyBatis-Plus 自动过滤已删除数据
     */
    @TableLogic
    private Integer isDeleted;

    /**
     * 版本号(用于乐观锁,每次更新自动递增)
     */
    @Version
    private Integer version;
}
关键注解说明:
  • @TableName:指定实体类对应的数据表名,解决「实体类名与表名不一致」问题。
  • @TableId:指定主键字段,type 属性配置主键生成策略(AUTO 自增、ASSIGN_ID 雪花算法等)。
  • @TableField:指定实体类字段与数据库字段的映射关系,可省略(默认驼峰命名映射)。
  • @TableLogic:标记逻辑删除字段,MyBatis-Plus 会自动在查询、更新时添加「is_deleted = 0」条件。
  • @Version:标记乐观锁版本字段,并发更新时会自动校验版本号,避免数据冲突。
  • @TableField(fill = ...):配置字段自动填充,需配合「元对象处理器」使用(下文讲解)。

2.2 元对象处理器(自动填充字段)

对于 createTime、updateTime 等公共字段,无需手动设置值,通过 MyBatis-Plus 的元对象处理器(MetaObjectHandler)实现自动填充。

java 复制代码
package com.example.mpdemo.handler;

import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**
 * 元对象处理器:实现公共字段自动填充
 */
@Component // 必须交给 Spring 管理
public class MyMetaObjectHandler implements MetaObjectHandler {

    /**
     * 插入操作时,自动填充 createTime 和 updateTime
     */
    @Override
    public void insertFill(MetaObject metaObject) {
        // 填充 createTime(字段名:createTime,值:当前时间)
        strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
        // 填充 updateTime
        strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
        // 可选:填充默认状态(如 status = 1,isDeleted = 0)
        strictInsertFill(metaObject, "status", Integer.class, 1);
        strictInsertFill(metaObject, "isDeleted", Integer.class, 0);
        strictInsertFill(metaObject, "version", Integer.class, 1);
    }

    /**
     * 更新操作时,自动填充 updateTime
     */
    @Override
    public void updateFill(MetaObject metaObject) {
        strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}

2.3 Mapper 接口编写

MyBatis-Plus 提供了 BaseMapper 接口,封装了所有基础 CRUD 操作,开发者只需让自定义 Mapper 继承 BaseMapper,即可无需编写任何方法,直接使用 CRUD 功能。

java 复制代码
package com.example.mpdemo.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mpdemo.entity.SysUser;
import org.apache.ibatis.annotations.Mapper;

/**
 * 用户 Mapper 接口
 * 继承 BaseMapper,获得 MyBatis-Plus 内置的 CRUD 方法
 */
@Mapper // 或在配置类中使用 @MapperScan 扫描,二选一
public interface SysUser<SysUser> {

    // 基础 CRUD 方法无需编写,BaseMapper 已封装
    // 如需自定义 SQL,可在此处编写方法(配合 XML 或注解)

    /**
     * 自定义查询:根据用户名查询用户(注解方式编写 SQL)
     */
    @Select("SELECT * FROM sys_user WHERE username = #{username} AND is_deleted = 0")
    SysUser selectByUsername(@Param("username") String username);

    /**
     * 自定义查询:根据角色 ID 查询用户列表(XML 方式编写 SQL,见下方)
     */
<SysUser> selectUserByRoleId(@Param("roleId") Long roleId);
}
XML 方式编写自定义 SQL(可选)

如果自定义 SQL 较为复杂,推荐使用 XML 方式编写(更易维护),在 resources/mapper 目录下创建 SysUserMapper.xml 文件。

复制代码
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.example.mpdemo.mapper.SysUserMapper">

<!-- 自定义查询:根据角色 ID 查询用户列表<select id="selectUserByRoleId" resultType="com.example.mpdemo.entity.SysUser">
        SELECT u.*
        FROM sys_user u
        LEFT JOIN sys_user_role ur ON u.id = ur.user_id
        WHERE ur.role_id = #{roleId}
          AND u.is_deleted = 0
        ORDER BY u.create</select<!-- 自定义更新:批量更新用户状态<update id="updateUserStatusBatch">
        UPDATE sys_user
        SET status = #{status}
        WHERE id IN<foreach collection="ids" item="id" open="(" separator="," close=")">
            #{</foreach>
          AND is_deleted = 0
   </mapper>

2.4 Service 层封装(可选,推荐)

MyBatis-Plus 提供了 IService 接口和 ServiceImpl 实现类,进一步封装了 Mapper 层的操作,支持批量操作、事务管理等功能,推荐在实际开发中使用 Service 层隔离持久层与业务层。

java 复制代码
// 1. Service 接口(继承 IService)
package com.example.mpdemo.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.example.mpdemo.entity.SysUser;

import java.util.List;

public interface SysUserService extends<SysUser> {

    // 基础 CRUD 方法无需编写,IService 已封装
    // 自定义业务方法
    SysUser getByUsername(String username);

<SysUser> getUserListByRoleId(Long roleId);

    boolean<Long> ids, Integer status);
}

// 2. Service 实现类(继承 ServiceImpl,实现自定义接口)
package com.example.mpdemo.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.mpdemo.entity.SysUser;
import com.example.mpdemo.mapper.SysUserMapper;
import com.example.mpdemo.service.SysUserService;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> implements SysUserService {

    // 注入 Mapper(可选,ServiceImpl 已自动注入)
    private final SysUserMapper sysUserMapper;

    public SysUserServiceImpl(SysUserMapper sysUserMapper) {
        this.sysUserMapper = sysUserMapper;
    }

    @Override
    public SysUser getByUsername(String username) {
        return sysUserMapper.selectByUsername(username);
    }

    @Override
    public List<SysUser> getUserListByRoleId(Long roleId) {
        return sysUserMapper.selectUserByRoleId(roleId);
    }

    @Override
    public boolean updateStatusBatch<Long> ids, Integer status) {
        // 调用自定义 XML 中的方法
        return sysUserMapper.updateUserStatusBatch(ids, status) > 0;
    }
}

2.5 核心 CRUD 操作实战(测试案例)

以下通过 Spring Boot 测试类,演示 Mapper 层和 Service 层的核心 CRUD 操作,所有案例均可直接运行。

java 复制代码
package com.example.mpdemo;

import com.example.mpdemo.entity.SysUser;
import com.example.mpdemo.service.SysUserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.ArrayList;
import java.util.List;

@SpringBootTest
public class SysUserTest {

    @Autowired
    private SysUserService sysUserService;

    // 1. 新增用户(自动填充 createTime、updateTime 等字段)
    @Test
    public void testInsert() {
        SysUser user = new SysUser();
        user.setUsername("zhangsan");
        user.setPassword("123456"); // 实际开发中需加密(如 BCrypt)
        user.setNickname("张三");
        user.setGender(1);
        user.setPhone("13800138000");
        // 无需设置 createTime、updateTime、status 等字段(自动填充)
        boolean success = sysUserService.save(user);
        System.out.println("新增结果:" + success + ",新增用户 ID:" + user.getId());
    }

    // 2. 批量新增用户
    @Test
    public void testInsertBatch() {
       <SysUser> user<>();
        for (int i<= 5; i++) {
            SysUser user = new SysUser();
            user.setUsername("user" + i);
            user.setPassword("123456");
            user.setNickname("用户" + i);
            user.setGender(i % 2);
            user.setPhone("1380013800" + i);
            userList.add(user);
        }
        // 批量新增(效率高于循环单条新增)
        boolean success = sysUserService.saveBatch(userList);
        System.out.println("批量新增结果:" + success);
    }

    // 3. 根据 ID 查询用户
    @Test
    public void testSelectById() {
        SysUser user = sysUserService.getById(1L);
        System.out.println("查询到的用户:" + user);
    }

    // 4. 根据条件查询用户列表(使用条件构造器 QueryWrapper)
    @Test
    public void testSelectByCondition() {
        // 条件:性别为男(gender = 1)、状态正常(status = 1)、昵称包含「张」
        Query<SysUser> query<>();
        queryWrapper.eq("gender", 1) // 等于
                   .eq("status", 1)
                   .like("nickname", "张") // 模糊查询
                   .orderByDesc("create_time"); // 按创建时间降序

        List<SysUser> userList = sysUserService.list(queryWrapper);
        System.out.println("符合条件的用户列表:" + userList);
    }

    // 5. 分页查询用户(核心功能,解决大数据量查询性能问题)
    @Test
    public void testSelectPage() {
        // 分页参数:第 1 页,每页 10 条数据
        Page<SysUser> page =<>(1, 10);
        // 条件:状态正常(status = 1)
<SysUser> queryWrapper = new Query<>();
        queryWrapper.eq("status", 1);

        // 分页查询(返回分页结果,包含总条数、总页数、当前页数据等<SysUser> userPage = sysUserService.page(page, queryWrapper);
        System.out.println("总条数:" + userPage.getTotal());
        System.out.println("总页数:" + userPage.getPages());
        System.out.println("当前页数据:" + userPage.getRecords());
    }

    // 6. 根据 ID 更新用户(乐观锁生效)
    @Test
    public void testUpdateById() {
        SysUser user = new SysUser();
        user.setId(1L);
        user.setNickname("张三更新");
        user.setPhone("13900139000");
        // 乐观锁:如果版本号不匹配,更新失败(避免并发更新冲突)
        boolean success = sysUserService.updateById(user);
        System.out.println("更新结果:" + success);
    }

    // 7. 批量更新用户状态
    @Test
    public void testUpdateBatchStatus() {
<Long> ids<>();
        ids.add(1L);
        ids.add(2L);
        ids.add(3L);
        // 将 ID 为 1、2、3 的用户状态改为禁用(status = 0)
        boolean success = sysUserService.updateStatusBatch(ids, 0);
        System.out.println("批量更新状态结果:" + success);
    }

    // 8. 逻辑删除用户(并非物理删除,只是将 is_deleted 改为 1)
    @Test
    public void testRemoveById() {
        // 逻辑删除 ID 为 1 的用户
        boolean success = sysUserService.removeById(1L);
        System.out.println("删除结果:" + success);
        // 验证:查询已删除的用户,会自动过滤(is_deleted = 0)
        SysUser user = sysUserService.getById(1L);
        System.out.println("删除后查询:" + user); // 结果为 null
    }
}

三、高级特性与性能优化

在实际开发中,除了基础 CRUD 操作,还需要关注持久层的性能和可扩展性。本节将讲解 MyBatis-Plus 的高级特性,并针对常见的性能瓶颈提供优化方案。

3.1 条件构造器高级用法

MyBatis-Plus 提供了 QueryWrapper(查询条件)、UpdateWrapper(更新条件)、LambdaQueryWrapper(Lambda 表达式条件)三种构造器,其中 LambdaQueryWrapper 可以避免硬编码字段名,减少错误,推荐使用。

java 复制代码
@Test
public void testLambdaQueryWrapper() {
    // LambdaQueryWrapper:使用 Lambda 表达式,避免硬编码字段名
<SysUser> lambdaQueryWrapper =<>();
    // 条件:性别为女(gender = 0)、状态正常(status = 1)、创建时间大于 2024-01-01
    lambdaQueryWrapper.eq(SysUser::getGender, 0)
                      .eq(SysUser::getStatus, 1)
                      .ge(SysUser::getCreateTime, LocalDateTime.of(2024, 1, 1, 0, 0, 0))
                      .select(SysUser::getId, SysUser::getUsername, SysUser::getNickname); // 只查询指定字段(减少数据传输)<SysUser> userList = sysUserService.list(lambdaQueryWrapper);
    System.out.println("Lambda 条件查询结果:" + userList);
}

// UpdateWrapper 用法:根据条件更新,无需设置实体类所有字段
@Test
public void testUpdateWrapper() {
   <SysUser> updateWrapper = new UpdateWrapper<>();
    // 条件:昵称包含「用户」、状态正常
    updateWrapper.like("nickname", "用户")
                 .eq("status", 1);
    // 更新内容:密码改为「654321」、更新时间自动填充
    updateWrapper.set("password", "654321");

    boolean success = sysUserService.update(updateWrapper);
    System.out.println("条件更新结果:" + success);
}

3.2 分页查询优化

分页查询是解决大数据量查询性能问题的核心手段,但如果使用不当,依然会出现性能瓶颈(如 count 查询耗时过长、分页 SQL 效率低)。以下是分页查询的优化方案:

3.2.1 自定义分页 SQL(避免 count 查询)

MyBatis-Plus 的默认分页会自动执行 count 查询(获取总条数),如果数据表数据量极大(百万级以上),count 查询会非常耗时。此时可以自定义分页 SQL,避免 count 查询,或使用更高效的 count 方式。

java 复制代码
// 1. Mapper 接口中自定义分页方法
@Select("SELECT u.* FROM sys_user u WHERE u.status = #{status} ORDER BY u.create_time DESC LIMIT #{offset}, #{size}")<SysUser> selectUserPage(@Param("status") Integer status, @Param("offset") Long offset, @Param("size") Long size);

// 2. Service 层实现分页逻辑(手动计算分页参数)
@Override
public<SysUser> getUserPageWithoutCount(Integer status, Integer pageNum, Integer pageSize) {
    Page<SysUser> page =<>(pageNum, pageSize);
    // 计算偏移量:offset = (pageNum - 1) * pageSize
    Long offset = (page - 1L) * pageSize;
    // 自定义分页查询(不查询总条数)
   <SysUser> userList = sysUserMapper.selectUserPage(status, offset, pageSize);
    // 设置分页数据(总条数设为 0,避免 count 查询)
    page.setRecords(userList);
    page.setTotal(0);
    return page;
}
3.2.2 分页插件优化(指定数据库方言)

在 MyBatis-Plus 配置类中,分页插件需指定数据库方言(如 MySQL、Oracle),避免分页 SQL 生成错误,同时可以配置分页合理化(避免页码超出范围)。

java 复制代码
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
    MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
    // 分页插件优化配置
    PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
    // 开启<= 0 时查询第 1 页,页码 > 总页数时查询最后 1 页
    paginationInterceptor.setOverflow(true);
    // 设置最大单页限制(避免一次性查询过多数据,默认 500 条)
    paginationInterceptor.setMaxLimit(1000L);
    interceptor.addInnerInterceptor(paginationInterceptor);
    // 乐观锁插件
    interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
    return interceptor;
}

3.3 缓存优化(一级缓存 + 二级缓存)

MyBatis 提供了一级缓存(SqlSession 级别)和二级缓存(Mapper 级别),合理使用缓存可以减少数据库查询次数,提升性能。MyBatis-Plus 完全继承了 MyBatis 的缓存机制,以下是具体配置方法。

3.3.1 一级缓存(默认开启)

一级缓存是 SqlSession 级别(Spring 整合 MyBatis 后,SqlSession 由 Spring 管理,每次数据库操作会创建一个新的 SqlSession,因此一级缓存默认无效)。如需启用一级缓存,需配置 SqlSession 手动管理(不推荐,影响并发)。

3.3.2 二级缓存(推荐开启)

二级缓存是 Mapper 级别,多个 SqlSession 可以共享缓存,开启后可以有效减少重复查询。配置步骤如下:

java 复制代码
// 1. 在 application.yml 中开启二级缓存
mybatis-plus:
  configuration:
    cache-enabled: true # 开启二级缓存(全局开关)

// 2. 在 Mapper 接口上添加 @CacheNamespace 注解(开启当前 Mapper 的二级缓存)
@Mapper
@CacheNamespace(implementation = org.mybatis.caches.ehcache.EhcacheCache.class) // 使用 EhCache 实现缓存(需引入依赖)
public interface SysUserMapper extends BaseMapper<SysUser> {
    // ... 方法省略
}

// 3. 引入 EhCache 依赖(可选,默认使用 MyBatis 内置缓存)<dependency>
    <groupId>org.mybatis<artifactId>mybatis-ehcache</artifactId>
    <version>1.2.2</version>
</dependency>
缓存使用注意事项:
  • 缓存的实体类必须实现 Serializable 接口(否则缓存无法序列化存储)。
  • 对于频繁更新的数据(如用户余额),不建议使用缓存,避免数据不一致。
  • 可以通过 @CacheEvict(清除缓存)、@CachePut(更新缓存)注解管理缓存。

3.4 SQL 优化技巧

持久层的性能瓶颈大多源于低效 SQL,以下是结合 MyBatis-Plus 的 SQL 优化技巧:

  1. 避免全表扫描:查询时必须添加条件(如 status = 1、is_deleted = 0),避免使用 select *,只查询需要的字段(使用 select 方法指定字段)。
  2. 合理使用索引:对频繁查询的字段(如 username、phone、role_id)建立索引,提升查询效率。例如:给 username 字段建立唯一索引,给 role_id 字段建立普通索引。
  3. 批量操作替代循环单条操作:使用 saveBatch、updateBatchById 等批量方法,减少数据库连接次数(批量操作建议每次不超过 1000 条,避免数据库压力过大)。
  4. 避免嵌套查询:复杂查询尽量使用联表查询(left join、inner join)替代嵌套查询(子查询),提升 SQL 执行效率。
  5. 使用 @SelectProvider 动态生成 SQL:对于复杂的动态 SQL(如多条件组合查询),使用 @SelectProvider 注解动态生成 SQL,避免 XML 中编写大量冗余代码。

四、常见问题与解决方案

4.1 实体类字段与数据库字段映射失败

原因:实体类字段使用驼峰命名(如 createTime),数据库字段使用下划线命名(如 create_time),未开启驼峰命名映射。

解决方案:在 application.yml 中配置 map-underscore-to-camel-case: true,或使用 @TableField 注解指定字段映射关系。

4.2 分页查询返回空数据(总条数为 0)

原因:未注册分页插件,或分页插件配置错误(未指定数据库方言)。

解决方案:在 MyBatis-Plus 配置类中注册 PaginationInnerInterceptor,并指定数据库类型(DbType.MYSQL)。

4.3 乐观锁更新失败

原因:实体类未添加 @Version 注解,或更新时未携带版本号,导致乐观锁未生效;或并发更新时版本号不匹配。

解决方案:在实体类版本字段上添加 @Version 注解,更新时确保携带版本号(如通过 getById 查询后再更新)。

4.4 逻辑删除后依然能查询到数据

原因:未在实体类字段上添加 @TableLogic 注解,或 MyBatis-Plus 未自动过滤逻辑删除字段。

解决方案:在逻辑删除字段上添加 @TableLogic 注解,确保配置文件中未关闭逻辑删除功能(默认开启)。

五、总结与扩展

本文围绕 Spring Boot 3 + MyBatis-Plus 持久层开发,从基础环境搭建、核心功能实战,到高级特性与性能优化,提供了完整的实战方案和丰富的代码案例,覆盖了实际开发中常见的场景和问题。

MyBatis-Plus 不仅简化了持久层编码,还提供了强大的扩展能力,例如:

  • 自定义主键生成策略(如雪花算法、分布式 ID);
  • 多数据源整合(配合 dynamic-datasource-spring-boot-starter);
  • 代码生成器(AutoGenerator),自动生成 Entity、Mapper、Service、Controller 代码;
  • 与 Spring Security、Shiro 等安全框架整合,实现用户认证与授权。
相关推荐
无名之逆2 小时前
你可能不需要WebSocket-服务器发送事件的简单力量
java·开发语言·前端·后端·计算机·rust·编程
锅包一切2 小时前
一、C++ 发展与程序创建
开发语言·c++·后端·学习·编程
古城小栈2 小时前
后端视角:拆解春晚背后的高可用技术架构
后端·架构
心之语歌3 小时前
flutter provider 使用,状态管理更新跨组件数据共享
后端·flutter
Loo国昌3 小时前
【AI应用开发实战】05_GraphRAG:知识图谱增强检索实战
人工智能·后端·python·语言模型·自然语言处理·金融·知识图谱
百锦再3 小时前
Java的TCP和UDP实现详解
java·spring boot·tcp/ip·struts·spring cloud·udp·kafka
颜酱3 小时前
差分数组:高效处理数组区间批量更新的核心技巧
javascript·后端·算法
用户908324602733 小时前
Spring AI 1.1.2 集成 MCP(Model Context Protocol)实战:以 Tavily 搜索为例
java·后端
玹外之音3 小时前
告别 STDIO/SSE:Spring AI Streamable HTTP MCP 实战指南
后端·spring