一、MyBatis-Plus 基础介绍
1.1 框架简介
MyBatis 是单表增删改查需要重复编写大量 XML 与 SQL,MyBatis-Plus 在 MyBatis 基础上做无侵入增强,无需修改原有 MyBatis 代码即可接入,一键实现单表全部数据库操作,大幅提升开发效率。
- 复杂关联:自定义 Mapper XML 编写原生关联 SQL;
- 简单关联:Service 层多次单表查询,业务组装数据。
1.2 底层执行流程
- SpringBoot 启动扫描实体类;
- 反射读取
@TableName、@TableId等注解,解析表名、字段、主键、逻辑删除标识; - 自动生成 insert/update/delete/select 标准 SQL 注入 MyBatis 容器;
- 开发者直接调用 BaseMapper 内置方法完成数据库交互。
二、SpringBoot 快速整合 MyBatis-Plus 实战
2.1 环境准备
JDK8+、SpringBoot2.x、MySQL8.0、Maven 项目
2.2 数据库表初始化
sql
DROP TABLE IF EXISTS user; CREATE TABLE user ( id BIGINT NOT NULL COMMENT '主键ID', name VARCHAR(30) NULL COMMENT '姓名', age INT NULL COMMENT '年龄', email VARCHAR(50) NULL COMMENT '邮箱', deleted INT DEFAULT 0 COMMENT '逻辑删除标记', version INT DEFAULT 0 COMMENT '乐观锁版本号', PRIMARY KEY (id) ); INSERT INTO user (id, name, age, email) VALUES (1, 'Jone', 18, 'test1@baomidou.com'), (2, 'Jack', 20, 'test2@baomidou.com'), (3, 'Tom', 28, 'test3@baomidou.com');2.3 Maven 核心依赖
xml
<!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <!-- Lombok简化实体类 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- MyBatis-Plus启动器 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency>注意:不可同时引入原生 MyBatis 依赖,会造成冲突。
2.4 application.yml 配置
yaml
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/boot?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false username: root password: root mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 打印执行SQL global-config: db-config: id-type: assign_id # 默认雪花算法主键 logic-delete-field: deleted logic-delete-value: 1 # 已删除 logic-not-delete-value: 0 # 正常 mapper-locations: classpath:mapper/*.xml # 自定义XML路径2.5 实体类编写
@Data @NoArgsConstructor @AllArgsConstructor @TableName("user") public class User { @TableId(type = IdType.ASSIGN_ID) private Long id; private String name; private Integer age; private String email; @TableLogic private Integer deleted; @Version private Integer version; }2.6 Mapper 接口与启动类
Mapper 继承BaseMapper自动获得全部 CRUD 方法:
@Mapper public interface UserMapper extends BaseMapper<User> {}启动类使用@MapperScan统一扫描 Mapper,无需每个接口加@Mapper:
@SpringBootApplication @MapperScan("com.mp.mapper") public class MpApplication { public static void main(String[] args) { SpringApplication.run(MpApplication.class, args); } }2.7 基础测试查询
@SpringBootTest class MpTest { @Autowired private UserMapper userMapper; @Test void testSelectAll(){ List<User> list = userMapper.selectList(null); list.forEach(System.out::println); } }运行控制台输出完整 SQL,查询全部数据,基础环境搭建完成。
三、MyBatis-Plus 核心注解详解
3.1 @TableName
绑定实体与数据库表,实体类名和表名不一致时必须使用,核心属性 value 指定表名。
3.2 @TableId 主键注解
标记主键字段,type 控制主键生成策略,局部注解优先级高于全局配置:
- AUTO:数据库自增,适合单体项目;
- ASSIGN_ID:雪花算法,Long/String 类型,分布式推荐默认策略;
- ASSIGN_UUID:32 位 UUID 字符串;
- INPUT:手动传入主键;
- NONE:跟随全局配置。
雪花算法原理:1 位符号位 + 41 位时间戳 + 10 位机器 ID+12 位序列号,分布式全局唯一。
3.3 @TableField
普通字段注解,常用场景:
- value:实体属性与数据库字段名不一致时映射;
- exist=false:标记该属性不属于数据库字段;
- select=false:查询时不返回该字段;
- fill:自动填充(INSERT/UPDATE/INSERT_UPDATE)。
3.4 @TableLogic
逻辑删除字段标记,开启后所有查询自动拼接where deleted=0,delete 操作转为 update 更新删除标记,避免数据物理丢失。
3.5 @Version
乐观锁版本号注解,用于并发更新,更新时自动携带版本条件,防止数据覆盖丢失。
四、BaseMapper 通用 CRUD 操作
4.1 新增 insert ()
插入实体,使用雪花算法自动生成 ID 并回填至实体对象:
@Test void testInsert(){ User user = new User(); user.setName("张三"); user.setAge(22); user.setEmail("zs@qq.com"); int rows = userMapper.insert(user); System.out.println("受影响行数:"+rows); System.out.println("自动生成ID:"+user.getId()); }4.2 更新操作
- updateById:根据主键更新实体非 null 字段;
User user = new User(); user.setId(1L); user.setName("张三修改"); userMapper.updateById(user);- update (entity, wrapper):自定义条件批量更新数据。
4.3 查询常用方法
// 根据主键单查 User user = userMapper.selectById(1L); // 批量ID查询 List<User> list = userMapper.selectBatchIds(Arrays.asList(1,2)); // Map等值多条件查询 Map<String,Object> map = new HashMap<>(); map.put("name","Jack"); List<User> userList = userMapper.selectByMap(map); // 查询总条数 Integer total = userMapper.selectCount(null);4.4 删除操作
- 物理删除(未开启逻辑删除):deleteById、deleteBatchIds、deleteByMap;
- 逻辑删除(全局配置 +@TableLogic):执行 deleteById 不会删除数据,SQL 变为
update user set deleted=1 where id=? and deleted=0。
五、条件构造器 Wrapper(复杂查询核心)
Wrapper 分为四类,企业开发优先使用 Lambda 系列,杜绝字段硬编码:
- QueryWrapper:普通字符串条件查询;
- LambdaQueryWrapper:Lambda 表达式查询(推荐);
- UpdateWrapper:自定义更新条件;
- LambdaUpdateWrapper:Lambda 更新条件。
常用查询示例
// 1. 普通Wrapper:年龄20~30,姓名不为空 QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.isNotNull("name").between("age",20,30).orderByDesc("id"); List<User> list = userMapper.selectList(wrapper); // 2. LambdaWrapper(无硬编码,推荐) LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>(); lambdaWrapper.ge(User::getAge, 20).likeRight(User::getEmail, "t"); List<User> lambdaList = userMapper.selectList(lambdaWrapper);常用方法:eq 等于、ne 不等于、gt 大于、ge 大于等于、lt 小于、le 小于等于、between 区间、like 模糊查询、orderByDesc 倒序。
六、分页插件
MP 内置物理分页,无需引入 PageHelper,只需配置拦截器。
6.1 分页配置类
@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // MySQL分页插件 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }6.2 分页代码使用
@Test void testPage(){ // 参数1:当前页码,参数2:每页条数 Page<User> page = new Page<>(1,2); Page<User> pageResult = userMapper.selectPage(page, null); // 分页数据 List<User> records = pageResult.getRecords(); // 分页信息 System.out.println("总条数:"+pageResult.getTotal()); System.out.println("总页数:"+pageResult.getPages()); System.out.println("是否下一页:"+pageResult.hasNext()); }底层自动执行两条 SQL:count 统计总数 + limit 分页查询。
七、乐观锁解决并发更新丢失
7.1 使用场景
多线程同时修改同一条数据,无锁会出现后提交的数据覆盖先提交数据,造成数据丢失。乐观锁通过 version 版本号实现无锁并发控制。
7.2 完整实现
- 数据库添加 version 字段,默认值 0;
- 实体 version 字段添加
@Version; - 拦截器添加乐观锁插件:
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());- 并发测试逻辑:
// 两个线程读取同一条数据 Product p1 = productMapper.selectById(1L); Product p2 = productMapper.selectById(1L); // 线程1更新成功,version自增1 p1.setPrice(p1.getPrice()+500); productMapper.updateById(p1); // 线程2携带旧version更新,匹配不到数据,更新行数为0,更新失败 p2.setPrice(p2.getPrice()-300); int rows = productMapper.updateById(p2); if(rows == 0){ // 业务重试:重新查询最新数据再更新 }自动生成更新 SQL:update product set price=?,version=version+1 where id=? and version=?。
八、Service 层通用封装 IService
MP 在 Mapper 之上封装业务层通用接口,命名区分 Mapper:Mapper 用 select/insert,Service 用 list/get/save/remove。
8.1 代码结构
// Service接口 public interface UserService extends IService<User> {} // 实现类 @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {}8.2 Service 常用方法
@Autowired private UserService userService; userService.save(user); // 新增 userService.saveBatch(list); // 批量新增 User user = userService.getById(1L); // 根据id查询 List<User> list = userService.list(wrapper); // 条件查询 IPage<User> page = userService.page(new Page<>(1,5), wrapper); // 分页查询 userService.saveOrUpdate(user); // 存在更新,不存在新增九、自定义 SQL 处理复杂多表查询
单表操作使用 MP 内置方法,多表联查、复杂聚合 SQL 兼容原生 MyBatis XML。
- Mapper 接口定义自定义方法
public interface UserMapper extends BaseMapper<User> { List<User> selectByAge(Integer age); }- resources/mapper 下创建 UserMapper.xml
xml
<mapper namespace="com.mp.mapper.UserMapper"> <select id="selectByAge" resultType="com.mp.pojo.User"> select * from user where age = #{age} </select> </mapper>十、代码生成器 AutoGenerator
一键生成 controller、service、mapper、entity 全套代码,大幅减少重复编码。
10.1 新增依赖
xml
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.3</version> </dependency>10.2 核心生成工具类
public class MpGenerator { // 数据源配置 public static DataSourceConfig dataSource(){ DataSourceConfig ds = new DataSourceConfig(); ds.setDbType(DbType.MYSQL); ds.setUrl("jdbc:mysql:///boot?serverTimezone=Asia/Shanghai"); ds.setUsername("root"); ds.setPassword("root"); return ds; } // 全局配置 public static GlobalConfig globalConfig(){ GlobalConfig gc = new GlobalConfig(); String path = System.getProperty("user.dir"); gc.setOutputDir(path+"/src/main/java"); gc.setAuthor("作者"); gc.setServiceName("%sService"); return gc; } // 包配置 public static PackageConfig packageConfig(){ PackageConfig pc = new PackageConfig(); pc.setParent("com.mp"); pc.setController("controller"); pc.setService("service"); pc.setMapper("mapper"); pc.setEntity("pojo"); return pc; } // 策略配置 public static StrategyConfig strategy(){ StrategyConfig sc = new StrategyConfig(); sc.setEntityLombokModel(true); sc.setLogicDeleteFieldName("deleted"); sc.setVersionFieldName("version"); sc.setNaming(NamingStrategy.underline_to_camel); sc.setInclude("user"); // 指定生成表 return sc; } public static void main(String[] args) { AutoGenerator ag = new AutoGenerator(); ag.setDataSource(dataSource()); ag.setGlobalConfig(globalConfig()); ag.setPackageInfo(packageConfig()); ag.setStrategy(strategy()); ag.execute(); } }运行 main 方法自动生成整套分层代码。