在Java后端开发中,ORM框架是连接Java实体与数据库的核心桥梁,而MyBatis-Plus(简称MP)作为MyBatis的增强工具,凭借“无侵入、损耗小、强大的CRUD封装、丰富的功能扩展”,彻底解决了MyBatis原生开发中SQL编写繁琐、重复代码过多的痛点,大幅提升开发效率。本文将从实战角度出发,全面解析MyBatis-Plus的核心用法,涵盖自定义SQL、乐观锁与悲观锁、Service封装、代码生成等关键知识点,结合完整案例,让你快速上手并灵活运用MP,轻松应对日常开发场景。

一、自定义SQL:应对复杂业务场景

MyBatis-Plus的BaseMapper已封装了大部分基础CRUD操作,但在实际开发中,难免会遇到复杂的查询、联表等场景,此时就需要像MyBatis一样,自定义SQL语句。自定义SQL的实现步骤清晰,分为4步:

1. 配置全局名称与映射路径(已在application.yaml中完成)

通过type-aliases-package配置实体类别名,避免在XML中写完整类路径;通过mapper-locations指定Mapper.xml文件的扫描路径。

2. 在Mapper接口中定义方法

创建UserMapper接口,继承BaseMapper,同时添加自定义方法(方法名需与XML中的SQL id一致):

package com.hd.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.hd.entity.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper  // 标记为MyBatis的Mapper接口,Spring会自动扫描
public interface UserMapper extends BaseMapper<User> {
    // 自定义方法:根据年龄查询用户(复杂场景可扩展为联表、多条件等)
    List<User> selectByAge(Integer age);
}

3. 编写Mapper.xml配置文件

在resources/mapper目录下创建UserMapper.xml,编写自定义SQL语句,注意namespace需与Mapper接口的全路径一致,resultType指定返回实体类别名,parameterType指定参数类型:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace必须与Mapper接口全路径一致 -->
<mapper namespace="com.hd.mapper.UserMapper">

    <!-- 自定义查询:根据年龄查询用户 -->
    <select id="selectByAge" resultType="user" parameterType="int">
        select * from user where age = #{age}
    </select>

</mapper>

4. 测试自定义SQL

编写测试用例,注入UserMapper,调用自定义方法,验证SQL执行结果:

package com.hd;

import com.hd.entity.User;
import com.hd.mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class MyBatisPlusTest {

    @Autowired
    private UserMapper userMapper;

    // 测试自定义SQL:根据年龄查询用户
    @Test
    void testSelectByAge() {
        List<User> users = userMapper.selectByAge(22);
        users.forEach(System.out::println);
    }

    // 补充测试:id降序查询(BaseMapper内置方法+条件构造器)
    @Test
    void testOrderByDesc() {
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.orderByDesc("id");  // 按id降序排列
        List<User> users = userMapper.selectList(wrapper);
        users.forEach(System.out::println);
    }
}

注意:自定义SQL时,需保证方法名与XML中的id完全一致,参数类型、返回类型与SQL中的parameterType、resultType匹配;若使用注解方式(@Select、@Insert等),可无需编写XML文件,适合简单SQL场景。

二、乐观锁与悲观锁:解决并发修改冲突

在多线程并发场景中,数据修改冲突是常见问题,例如:多个用户同时修改同一条数据,可能导致数据不一致。MyBatis-Plus提供了乐观锁与悲观锁的实现方案,可根据业务场景灵活选择。

1. 核心概念(通俗理解)

(1)乐观锁

乐观锁秉持“乐观态度”,认为并发修改数据的概率较低,因此不会主动上锁。在更新数据时,会判断当前数据是否被其他线程修改过(通过版本号、时间戳等方式),若未被修改,则执行更新;若已被修改,则拒绝更新或重试,避免数据覆盖。

(2)悲观锁

悲观锁秉持“悲观态度”,认为并发修改数据的概率较高,因此在获取数据时就会上锁。其他线程想要获取或修改该数据,必须等待当前线程释放锁,从而保证数据修改的原子性,避免冲突。

2. 典型场景(直观理解冲突问题)

假设场景:一件二手苹果手机,成本价800元,售价1000元。老板同时通知小李(涨价500元)和小王(降价300元)操作后台,两人同时获取到初始价格1000元:

  • 小李操作:1000 + 500 = 1500元,存入数据库

  • 小王操作:1000 - 300 = 700元,存入数据库

最终结果:小王的操作覆盖了小李的操作,商品价格变为700元(低于成本价),若大量出售会导致老板亏损。这就是典型的并发修改冲突,若无锁机制,会造成严重的数据问题。

3. 锁机制解决冲突的原理

  • 乐观锁:小王在保存价格前,会检查数据是否被修改(如版本号是否变化)。若发现已被小李修改(版本号更新),则重新获取最新价格1500元,再执行1500 - 300 = 1200元的更新,最终价格正确。

  • 悲观锁:小李获取数据时,系统自动上锁,小王需等待小李操作完成并释放锁后,才能获取数据(此时数据已更新为1500元),小王执行1500 - 300 = 1200元的更新,最终价格正确。

4. MyBatis-Plus实战:乐观锁实现(最常用)

MyBatis-Plus提供了乐观锁插件,通过版本号(version)机制实现,步骤如下:

(1)创建商品表(含版本号字段)
CREATE TABLE product (
    id BIGINT(20) NOT NULL COMMENT '主键ID',
    name VARCHAR(30) NULL DEFAULT NULL COMMENT '商品名称',
    price INT(11) DEFAULT 0 COMMENT '价格',
    version INT(11) DEFAULT 0 COMMENT '乐观锁版本号(初始值0)',
    PRIMARY KEY (id)
);

-- 插入测试数据
INSERT INTO product (id, name, price) VALUES (1, '外星人笔记本', 1000);
(2)创建商品实体类(添加@Version注解)
package com.hd.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;

@Data
public class Product {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    private Integer price;
    @Version  // 标记为乐观锁版本号字段
    private Integer version;
}
(3)配置乐观锁插件

创建MyBatis-Plus配置类,添加乐观锁插件(可同时添加分页插件等其他插件):

package com.hd.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.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 1. 添加分页插件(可选,后续会用到)
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 2. 添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}
(4)编写测试用例,模拟并发冲突并验证
package com.hd.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.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class MyBatisPlusConfig {

    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 1. 添加分页插件(可选,后续会用到)
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 2. 添加乐观锁插件
        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
        return interceptor;
    }
}
(5)执行结果

最终输出:老板获取的最终商品价格:1200,乐观锁成功解决了并发修改冲突,避免了数据覆盖。

补充说明:悲观锁在MyBatis-Plus中可通过@Lock注解或XML中添加for update实现,适合并发冲突频繁、数据一致性要求极高的场景(如支付、库存扣减),但会降低系统并发性能,需谨慎使用。

三、Service层封装:进一步简化CRUD操作

MyBatis-Plus不仅封装了Mapper层(BaseMapper),还提供了Service层的封装(IService),进一步简化CRUD操作,同时区分Mapper层与Service层的职责,符合分层开发规范。

1. 核心说明

  • IService:通用Service接口,封装了常用的CRUD方法,采用get(查询单行)、remove(删除)、list(查询集合)、page(分页)等前缀命名,避免与Mapper层混淆。

  • ServiceImpl:IService的实现类,继承自ServiceImpl<Mapper, Entity>,自动注入Mapper,可直接调用Mapper层方法。

  • 自定义Service:若需扩展业务方法,可创建自己的Service接口继承IService,实现类继承ServiceImpl。

2. 实战实现步骤

(1)创建Service接口(继承IService)
package com.hd.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.hd.entity.User;

// 继承IService,泛型为实体类User
public interface UserService extends IService<User> {
    // 可添加自定义业务方法(如复杂查询、业务逻辑处理)
    List<User> selectUserByAgeRange(Integer minAge, Integer maxAge);
}
(2)创建Service实现类(继承ServiceImpl)
package com.hd.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hd.entity.User;
import com.hd.mapper.UserMapper;
import com.hd.service.UserService;
import org.springframework.stereotype.Service;

import java.util.List;

@Service  // 标记为Service层组件,Spring自动扫描
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    // 实现自定义业务方法(可调用Mapper层方法或自定义SQL)
    @Override
    public List<User> selectUserByAgeRange(Integer minAge, Integer maxAge) {
        // 方式1:使用条件构造器
        return lambdaQuery().ge(User::getAge, minAge).le(User::getAge, maxAge).list();
        // 方式2:调用自定义Mapper方法(需在UserMapper中添加对应方法)
        // return getBaseMapper().selectByAgeRange(minAge, maxAge);
    }
}

3. IService核心方法(常用)

IService封装了大量常用方法,无需手动编写,直接调用即可,核心分类如下:

(1)新增方法
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);

// 批量插入
boolean saveBatch(Collection<T> entityList);

// 存在则更新,不存在则插入(根据主键判断)
boolean saveOrUpdate(T entity);
(2)删除方法
// 根据主键删除
boolean removeById(Serializable id);

// 根据条件删除
boolean remove(Wrapper<T> queryWrapper);

// 批量删除(根据主键集合)
boolean removeByIds(Collection<? extends Serializable> idList);
(3)更新方法
// 根据主键更新
boolean updateById(T entity);

// 根据条件更新
boolean update(T updateEntity, Wrapper<T> whereWrapper);

// 批量更新
boolean updateBatchById(Collection<T> entityList);
(4)查询方法
// 根据主键查询
T getById(Serializable id);

// 查询所有
List<T> list();

// 根据条件查询列表
List<T> list(Wrapper<T> queryWrapper);

// 分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
(5)统计方法
// 统计总记录数
int count();

// 根据条件统计记录数
int count(Wrapper<T> queryWrapper);
(6)测试Service方法
@Test
public void testService() {
    // 1. 注入UserService
    @Autowired
    private UserService userService;

    // 2. 调用IService内置方法
    List<User> allUser = userService.list();  // 查询所有用户
    boolean save = userService.save(new User(null, "张三", 25, "zhangsan@163.com"));  // 新增用户

    // 3. 调用自定义业务方法
    List<User> userList = userService.selectUserByAgeRange(20, 30);  // 查询20-30岁用户
    userList.forEach(System.out::println);
}

四、代码生成:一键生成全套代码,提升开发效率

在实际开发中,实体类、Mapper、Service、Controller等代码存在大量重复编写工作,MyBatis-Plus提供了代码生成器(AutoGenerator),可根据数据库表结构,一键生成全套代码,大幅减少重复开发,规范代码格式。

1. 引入代码生成器依赖

<!-- 代码生成器依赖 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-generator</artifactId>
    <version>3.4.1</version>
</dependency>

<!-- 模板引擎(Velocity,代码生成器依赖) -->
<dependency>
    <groupId>org.apache.velocity</groupId>
    <artifactId>velocity-engine-core</artifactId>
    <version>2.3</version>
</dependency>

2. 编写代码生成工具类

创建MybatisPlusGenerator类,配置数据源、全局设置、包信息、策略等,执行main方法即可生成代码(需根据自己的项目信息修改配置):

package com.hd;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.config.DataSourceConfig;
import com.baomidou.mybatisplus.generator.config.GlobalConfig;
import com.baomidou.mybatisplus.generator.config.PackageConfig;
import com.baomidou.mybatisplus.generator.config.StrategyConfig;
import com.baomidou.mybatisplus.generator.config.po.TableFill;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;

import java.util.ArrayList;

public class MybatisPlusGenerator {

    // 1. 配置数据源(连接数据库)
    public static DataSourceConfig dataSource() {
        DataSourceConfig dataSourceConfig = new DataSourceConfig();
        dataSourceConfig.setDbType(DbType.MYSQL);  // 数据库类型
        // 数据库连接URL(替换为自己的数据库名称)
        dataSourceConfig.setUrl("jdbc:mysql:///mybatisplusdb?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai");
        dataSourceConfig.setUsername("root");  // 数据库用户名
        dataSourceConfig.setPassword("root");  // 数据库密码
        dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver");  // 驱动类
        return dataSourceConfig;
    }

    // 2. 配置全局设置
    public static GlobalConfig globalConfig() {
        GlobalConfig globalConfig = new GlobalConfig();
        // 代码生成路径(项目根路径下的src/main/java,若有模块需添加模块名)
        String projectPath = System.getProperty("user.dir");
        globalConfig.setOutputDir(projectPath + "/src/main/java");
        globalConfig.setOpen(false);  // 生成后不自动打开文件夹
        globalConfig.setIdType(IdType.ASSIGN_ID);  // 主键生成策略(雪花算法)
        globalConfig.setAuthor("李博文");  // 代码作者
        globalConfig.setServiceName("%sService");  // Service接口命名格式(去掉默认的I前缀)
        return globalConfig;
    }

    // 3. 配置包信息(生成的代码存放的包路径)
    public static PackageConfig packageConfig() {
        PackageConfig packageConfig = new PackageConfig();
        packageConfig.setParent("com.hd");  // 父包名(替换为自己的项目包名)
        packageConfig.setController("controller");  // Controller层包名
        packageConfig.setService("service");  // Service层包名
        packageConfig.setServiceImpl("service.impl");  // Service实现类包名
        packageConfig.setMapper("mapper");  // Mapper层包名
        packageConfig.setEntity("entity");  // 实体类包名
        return packageConfig;
    }

    // 4. 配置生成策略(表映射、注解、命名规则等)
    public static StrategyConfig strategyConfig() {
        StrategyConfig strategyConfig = new StrategyConfig();
        // 要生成代码的数据库表名(多个表用逗号分隔,必须与数据库表名一致)
        strategyConfig.setInclude("user", "product");
        // 实体类启用Lombok注解
        strategyConfig.setEntityLombokModel(true);
        // 逻辑删除字段(若有,可配置)
        strategyConfig.setLogicDeleteFieldName("deleted");
        // 乐观锁版本号字段(若有,可配置)
        strategyConfig.setVersionFieldName("version");
        // 数据库表名、字段名下划线转驼峰(符合Java命名规范)
        strategyConfig.setNaming(NamingStrategy.underline_to_camel);
        strategyConfig.setColumnNaming(NamingStrategy.underline_to_camel);
        // 自动填充配置(如创建时间、修改时间)
        TableFill createTime = new TableFill("reg_time", FieldFill.INSERT);  // 插入时自动填充
        ArrayList<TableFill> tableFills = new ArrayList<>();
        tableFills.add(createTime);
        strategyConfig.setTableFillList(tableFills);
        // Controller层启用Restful风格(@RestController)
        strategyConfig.setRestControllerStyle(true);
        // Controller接口路径下划线转连字符(如/userInfo -> /user-info)
        strategyConfig.setControllerMappingHyphenStyle(true);
        return strategyConfig;
    }

    // 主方法:执行代码生成
    public static void main(String[] args) {
        // 创建代码生成器对象
        AutoGenerator autoGenerator = new AutoGenerator();
        // 配置数据源
        autoGenerator.setDataSource(dataSource());
        // 配置全局设置
        autoGenerator.setGlobalConfig(globalConfig());
        // 配置包信息
        autoGenerator.setPackageInfo(packageConfig());
        // 配置生成策略
        autoGenerator.setStrategy(strategyConfig());
        // 执行生成
        autoGenerator.execute();
    }
}

3. 代码生成注意事项

  • 生成前需确保数据库连接配置正确,且数据库中存在对应的表。

  • 可根据需求修改生成策略(如添加逻辑删除、自动填充字段、调整命名规则等)。

  • 生成的代码可直接使用,也可根据业务需求进行修改(如在Service中添加自定义方法)。

  • 补充:MyBatisX插件(IDEA插件)可进一步简化开发,支持XML与接口方法跳转、自动生成SQL等,可参考官方文档:MyBatisX官方文档

五、总结与实战建议

MyBatis-Plus作为MyBatis的增强工具,核心价值在于“简化开发、提升效率、兼顾灵活”,本文覆盖了自定义SQL、乐观锁与悲观锁、Service封装、代码生成等核心实战知识点,这些也是日常开发中最常用的功能。

实战建议:

  1. 基础CRUD操作:优先使用BaseMapper和IService的内置方法,避免重复编写SQL。

  2. 复杂场景:使用自定义SQL(XML或注解方式),结合条件构造器(QueryWrapper、LambdaQueryWrapper),灵活实现复杂查询。

  3. 并发场景:根据冲突频率选择锁机制——并发冲突少用乐观锁(性能优),冲突频繁用悲观锁(一致性高)。

  4. 代码规范:使用代码生成器生成基础代码,统一代码格式;分层开发,区分Mapper(数据访问)、Service(业务逻辑)、Controller(接口交互)的职责。

  5. 调试技巧:开启SQL日志(application.yaml中配置),便于排查SQL执行问题;使用MyBatisX插件,提升开发效率。

MyBatis-Plus的功能远不止于此,还有逻辑删除、分页插件、条件构造器高级用法、多租户等高级特性,后续可结合具体业务场景深入学习。掌握本文所述的核心用法,即可轻松应对大部分后端开发场景,让开发效率翻倍!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐