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 等安全框架整合,实现用户认证与授权。
相关推荐
wb043072013 小时前
使用 Java 开发 MCP 服务并发布到 Maven 中央仓库完整指南
java·开发语言·spring boot·ai·maven
nbwenren4 小时前
Springboot中SLF4J详解
java·spring boot·后端
helx825 小时前
SpringBoot中自定义Starter
java·spring boot·后端
rleS IONS5 小时前
SpringBoot获取bean的几种方式
java·spring boot·后端
lifewange6 小时前
Go语言-开源编程语言
开发语言·后端·golang
白毛大侠6 小时前
深入理解 Go:用户态和内核态
开发语言·后端·golang
R***z1017 小时前
Spring Boot 整合 MyBatis 与 PostgreSQL 实战指南
spring boot·postgresql·mybatis
王码码20357 小时前
Go语言中的数据库操作:从sqlx到ORM
后端·golang·go·接口
星辰_mya7 小时前
雪花算法和时区的关系
数据库·后端·面试·架构师
赵丙双8 小时前
spring boot AutoConfiguration.replacements 文件的作用
java·spring boot