Mybatis Plus 为简化开发、提高效率而生
前言
MyBatis-Plus是一个个MyBatis的增强工具,在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。MyBatis-Plus提供了通用的mapper和service,可以在不编写任何SQL语句的情况下,快速的实现对单表的CRUD、批量、逻辑删除、分页等操作。
<font face=" 楷体,华文行楷,隶书,黑体" color=red size=4>哈喽,大家好,/我是有勇气的牛排/(全网同名)🐮🐮🐮</font>
<font face=" 楷体,华文行楷,隶书,黑体" color=blue size=4>有问题的小伙伴欢迎在文末/评论,点赞、收藏/是对我最大的支持!!!。</font>
官网:https://www.couragesteak.com/
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
支持数据库
任何能使用
MyBatis进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。
- MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb,informix,TDengine,redshift
- 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库,优炫数据库
2 开发环境
JDK: JDK8+
构建工具:mavem
MySQL:5.7
Spring Boot
MyBatis-Plus:3.5.1
3 数据库准备
创建表
CREATE DATABASE `mybatis_plus` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;
use `mybatis_plus`;
CREATE TABLE `user` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '姓名',
`age` int(11) DEFAULT NULL COMMENT '年龄',
`email` varchar(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
添加数据
INSERT INTO user (id, name, age, email) VALUES
(1, "有勇气的牛排1", 18, "test1@couragesteak.com"),
(2, "有勇气的牛排2", 19, "test2@couragesteak.com"),
(3, "有勇气的牛排3", 20, "test3@couragesteak.com"),
(4, "有勇气的牛排4", 21, "test4@couragesteak.com"),
(5, "有勇气的牛排5", 22, "test5@couragesteak.com");

4 BaseMapper 接口
4.1 案例 selectList
pojo/entity:UserEntity
@Data
public class UserEntity {
private Long id; // 默认为雪花算法
private String name;
private Integer age;
private String email;
}
mapper:UserMapper.java(接口)
// 继承 MyBatis-Plus的 BaseMapper<泛型>
@Repository // 将接口标识为持久层组件
public interface UserMapper extends BaseMapper<UserEntity> {
}
Controller:TestController.java
@RestController
public class TestController {
@Autowired
private UserMapper userMapper;
// http://127.0.0.1:8080/test
@RequestMapping("/test")
public String testSelectList(){
// 通过条件构造器,查询一个list集合,若没有条件,则可以设置null为参数
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
return "666";
}
}
测试方法==Controller
/*
* @Author : 有勇气的牛排(全网同名)
* @FileName: MyBatisPlusTest.java
* desc :
* */
package com.couragesteak;
import com.couragesteak.mapper.UserMapper;
import com.couragesteak.pojo.User;
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;
@Test
public void testSelectList(){
System.out.println("666");
// 通过条件构造器,查询一个list集合,若没有条件,则可以设置null为参数
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
}

4.2 插入方法 insert
@Test
public void testInsert() {
User user = new User();
// user.setId(6);
user.setName("有勇气的牛排6");
user.setAge(18);
user.setEmail("test6@couragesteak.com");
int result = userMapper.insert(user);
System.out.println(result);
System.out.println("result: " + result);
System.out.println("result: " + user.getId());
}
4.3 删除方法 delete
4.3.1 deleteById
删除id=1637055722611748866的用户
DELETE FROM user WHERE id=?
// 通过id删除用户信息
int result = userMapper.deleteById(1637055722611748866L);
System.out.println("result: " + result);
4.3.2 deleteBatchIds 批量删除
DELETE FROM user WHERE id IN ( ? , ? , ? )
// 通过多个id实现批量删除
List<Long> list = Arrays.asList(1L, 2L, 3L);
int result = userMapper.deleteBatchIds(list);
System.out.println("result: " + result);
4.3.3 deleteByMap 按照条件删除
DELETE FROM user WHERE name = ? AND age = ?
// 根据map集合中所设置的条件删除用户信息
Map<String, Object> map = new HashMap<>();
map.put("name", "有勇气的牛排6");
map.put("age", 22);
int result = userMapper.deleteByMap(map);
System.out.println("result: " + result);
4.4 update 修改信息
UPDATE user SET name=?, email=? WHERE id=?
// 修改用户信息
User user = new User();
user.setId(8L);
user.setName("牛排哥");
user.setEmail("test1@couragesteak.com");
int result = userMapper.updateById(user);
System.out.println("result: " + result);

4.5 查询功能
4.5.1 selectById
通过id查询用户信息
SELECT id,name,age,email FROM user WHERE id=?
// 通过id查询用户信息
User user = userMapper.selectById(1L);
System.out.println(user);

4.5.2 selectBatchIds 批量查询
SELECT id,name,age,email FROM user WHERE id IN ( ? , ? , ? )
// 根据多个id查询多个用户信息
List<Long> list = Arrays.asList(1L, 2L, 3L);
List<User> users = userMapper.selectBatchIds(list);
users.forEach(System.out::println);

4.5.3 selectByMap 条件查询
SELECT id,name,age,email FROM user WHERE name = ? AND age = ?
// 根据map集合中的条件查询用户信息
Map<String, Object> map = new HashMap<>();
map.put("name", "牛排哥");
map.put("age", 18);
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);

4.5.4 selectList
SELECT id,name,age,email FROM user
// 查询所有数据
// SELECT id,name,age,email FROM user
List<User> users = userMapper.selectList(null);
users.forEach(System.out::println);
4.6 MyBatis 自定义Mapper查询方法
时间久了还真会忘,下面一起回顾下
创建映射
classpath: mapper/UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.couragesteak.mapper.UserMapper">
<!-- Map<String, Object> selectMapById(Long id); -->
<select id="selectByMapId" resultType="map">
select id,name,age,email from user where id = #{id}
</select>
</mapper>
UserMapper.java 接口
// 继承 MyBatis-Plus的 BaseMapper<泛型>
@Repository // 将接口标识为持久层组件
public interface UserMapper extends BaseMapper<User> {
/**
* 自定义查询: 根据id查询用户信息为map集合
* 映射文件: classpath: mapper/UserMapper.xml
* @param id
* @return
*/
Map<String, Object> selectByMapId(Long id);
}
调用
@SpringBootTest
public class MyBatisPlusTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
// 自定义查询方法
Map<String, Object> map = userMapper.selectByMapId(1L);
System.out.println(map);
}
}

5 Service CRUD接口
5.1 创建自己的Service
这里我们通过继承 MyBatisPlus的IService,来实现我们自己的Serice
com.couragesteak.service: sercice接口
package com.couragesteak.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.couragesteak.pojo.User;
/**
* UserService 继承 IService 模板提供的基础功能
* IServcice<泛型为-实体类对象>
*/
public interface UserService extends IService<User> {
}
com.couragesteak.service.Impl: sercice接口实现类
/*
* @Author : 有勇气的牛排(全网同名)
* @FileName: UserServiceImpl.java
* desc : Service实现类
* */
package com.couragesteak.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.couragesteak.mapper.UserMapper;
import com.couragesteak.pojo.User;
import com.couragesteak.service.UserService;
/**
* 1. 继承 ServiceImpl<M:当前接口类型, T:当前实体类型>
* 2. 实现自己的接口 UserSercice
*/
@Service // 将Sercice标记为组件
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}

5.2 save添加
5.2.1 saveBatch 批量添加
// 批量添加:单个sql语句循环添加
// INSERT INTO user ( id, name, age ) VALUES ( ?, ?, ? )
ArrayList<User> list = new ArrayList<>();
for (int i = 0; i <= 10; i++) {
User user = new User();
user.setName("有勇气的牛排" + i);
user.setAge(18 + i);
list.add(user);
}
boolean b = userService.saveBatch(list);
System.out.println(b);
6 常用注解
6.1 @TableName 指定表
在上文的实际测试中,我们通过MyBatis-Plus实现了基本的CRUD,并没有指定要操作的表,仅仅在Mapper接口继承BaseMapper时,设置了泛型User,而操作的表为user表。
由此,我们可以得出,MyBatis-Plus在确定操作的表时,通过BaseMapper的泛型决定,即实体类型决定,并且默认操作表名和实体类名一致。
@TableName("c_user") // 设置实体类对应的表名
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
6.2 全局配置指定表前缀
如果表前缀统一,通过配置此操作,可以省去6.1节中,为每个表添加@TableName注解。
mybatis-plus:
# 设置MyBatis-Plus的全局配置
global-config:
db-config:
# 设置实体类所对应的表的统一前缀
table-prefix: "c_"
6.3 @Tableld
6.3.1 主键配置
MyBatis-Plus在实现CRUD时,会默认将id作为主键,并且在插入数据时,默认基于雪花算法的策略生成id。
public class User {
// 将属性对应的字段uid指定为主键
@TableId
private Long uid;
private String name;
}
@TableId注解的value属性,用于指定主键的字段
public class User {
// 将属性id, 对应到表uid
@TableId(value = "uid")
private Long id;
private String name;
}
type类型有:
AUTO(0): 数据库ID自增(确保数据库设置自增)
NONE(1): 用户输入ID
INPUT(2): 分配ID
ASSIGN_ID(3)(默认): 分配ID(主键类型为number/string): 雪花算法(与数据库是否自增无关)
ASSIGN_UUID(4): 分配ID(主键类型为string): 分配UUID
public class User {
// @TableId的value属性,用于设置主键的字段
// @TableId的type属性,用于设置主键生成策略
@TableId(value = "id", type = IdType.AUTO)
private Long id;
}
6.3.2 雪花算法
雪花算发是由Twitter公布的分布式主键生成算法,它能保证不同表的主键的不同性,以及相同表的主键有序性。
1. 核心思想
(1)长度攻64bit(一个long型)
(2)首先是一个符号位,1bit标识,由于long基本类型在Java时带符号的,最好位是符号位,整数是0,负数是1,所以id一般为正数,最高位是0。
(3)41bit时间戳(毫秒级),存储的时间是时间戳的差值(当前时间戳-开始时间戳),结果约等于69.73年。
(4)10bit作为机器id(5bit是数据中心,5bit是机器的id,可以部署在1024个节点)
(5)12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生1024个节点)

2. 优点
整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞,并且效率高。
6.4 @TableFild 指定属性对应字段名
(1)MyBatis-Plus在执行SQL语句时,要保证实体类中的属性名和表中的字段包吃包一致。
(2)情景一:如果不一致,MyBatis-Plus会将 驼峰命名与下划线命名自动转换。
(3)情景二,如果情景一不成立,则需要使用 @TableFild("表字段名")来设置对应字段。
public class User {
// @TableId的value属性,用于设置主键的字段
// @TableId的type属性,用于设置主键生成策略
@TableId(value = "id", type = IdType.AUTO)
private Long id;
// 指定属性所对应的字段名
@TableField("u_name")
private String name;
}
6.5 @TableLogic 逻辑删除
物理删除:真实删除,将对应数据从数据中删除。
逻辑删除:假删除,将对应数据中代表是否删除状态的字段,标记为删除状态。
应用场景:可以进行数据恢复。
实体类
@TableName("c_user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("u_name")
private String name;
private Integer age;
private String email;
// 开启逻辑删除
@TableLogic
private Integer isDeleted;
}
开启逻辑删除后,delete将会变为update
// 通过多个id实现批量删除
// 未开启逻辑删除:DELETE FROM user WHERE id IN ( ? , ? , ? )
// 开启逻辑删除:UPDATE c_user SET is_deleted=1 WHERE id IN ( ? , ? , ? ) AND is_deleted=0
List<Long> list = Arrays.asList(1L, 2L, 3L);
int result = userMapper.deleteBatchIds(list);
System.out.println("result: " + result);
查询语句也会发生变化
// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
7 条件构造器
7.1 QueryWrapper
7.1.1 组装查询条件
场景:查询用户名包含9,年龄在18~20,邮箱部位null的用户信息
@Test
public void test01() {
// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 AND (u_name LIKE ? AND age BETWEEN ? AND ? AND email IS NOT NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("u_name", "9") // (字段名, 值)
.between("age", 18, 22)
.isNotNull("email");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
}

7.1.2 组装排序条件
场景:查询用户信息,按照年龄降序,若年龄相同,则按照id升序排序
// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 ORDER BY age DESC,id ASC
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("age")
.orderByAsc("id");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);

7.1.3 组装删除
删除邮箱地址为null的用户信息
// UPDATE c_user SET is_deleted=1 WHERE is_deleted=0 AND (email IS NULL)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.isNull("email");
int result = userMapper.delete(queryWrapper);
System.out.println("result:" + result);
7.1.4 and/or 条件优先级
将 (age>20 且 u_name 包含9) 或 email=null 的用户信息修改
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.gt("age", 18)
.like("u_name", "9")
.or()
.isNull("email");
User user = new User();
user.setName("牛排哥");
int result = userMapper.update(user, queryWrapper);
System.out.println("result: " + result);
将 u_name包含9,且(age>20 或 email=null)的用户信息修改
// 将 u_name包含9,且(age>18 或 email=null)的用户信息修改
// // UPDATE c_user SET u_name=? WHERE is_deleted=0 AND (u_name LIKE ? AND (age > ? OR email IS NULL))
// lambda中的条件优先执行
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("u_name","9")
.and(i->i.gt("age",18).or().isNull("email"));
User user = new User();
user.setName("有勇气的牛排博客");
int result = userMapper.update(user, queryWrapper);
System.out.println("result: " + result);
7.1.5 组装select 设置要查询的字段
查询 用户名、年龄、邮箱
// SELECT u_name,age,email FROM c_user WHERE is_deleted=0
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.select("u_name", "age", "email");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);

7.1.6 实现子查询
查询 id<=3 的用户信息
# 子查询案例
select * from user
where id in(
select uid from user where id <=3
)
// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 AND (id IN (select id from c_user where id <= 3))
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.inSql("id","select id from c_user where id <= 3");
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
7.2 UpdateWrapper 设置条件、字段
UpdateWrapper 不仅可以设置条件,还可以设置字段
场景:将 u_name包含9,且(age>18 或 email=null)的用户信息修改
// UPDATE c_user SET u_name=?,email=? WHERE is_deleted=0 AND (u_name LIKE ? AND (age > ? OR email IS NULL))
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
// 条件
updateWrapper.like("u_name", "9")
.and(i -> i.gt("age", 18).or().isNull("email"));
// 修改(相对于QueryWrapper,这里不在创建实体类对象)
updateWrapper.set("u_name", "牛排哥").set("email", "my@couragesteak.com");
int result = userMapper.update(null, updateWrapper);
System.out.println("result: " + result);
7.3 condition
在开发中,组装条件是最常见的功能,而这些条件数据来源于用户,是可选的,因此我们需要判断这些条件,若没有选择则不需要组装,以免影响SQL结果。
动态SQL,(这种组并不好)
String username = "";
Integer ageBegin = 20;
Integer ageEnd = 30;
// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 AND (age >= ? AND age <= ?)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
// com.baomidou.mybatisplus.core.toolkit.StringUtils;
if (StringUtils.isNotBlank(username)) {
// 判断某个字符串是否不为空、不为null、不为空白符
queryWrapper.like("u_name", username);
}
if (ageBegin != null) {
queryWrapper.ge("age", 18); // ge 表示 >=
}
if (ageEnd != null) {
queryWrapper.le("age", 18); // le 表示 <=
}
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
通过condition判断,将会很大程度上简化代码
String username = "9";
Integer ageBegin = 18;
Integer ageEnd = 30;
// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 AND (u_name LIKE ? AND age >= ? AND age <= ?)
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username), "u_name", username)
.ge(ageBegin != null, "age", ageBegin)
.le(ageEnd != null, "age", ageEnd);
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);

但是,为了避免字段写错,因此可以使用 LambdaQueryWrapper
7.4 LambdaQueryWrapper 可访问实体字段, 避免写错
String username = "9";
Integer ageBegin = 18;
Integer ageEnd = 30;
// LambdaQueryWrapper<泛型为:实体类类型>
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.isNotBlank(username), User::getName, username)
.ge(ageBegin != null, User::getAge, ageBegin)
.le(ageEnd != null, User::getAge, ageEnd);
List<User> list = userMapper.selectList(queryWrapper);
list.forEach(System.out::println);
7.5 LambdaUpdateWrapper
// UPDATE c_user SET u_name=?,email=? WHERE is_deleted=0 AND (u_name LIKE ? AND (age > ? OR email IS NULL))
LambdaUpdateWrapper<User> updateWrapper = new LambdaUpdateWrapper<>();
// 条件
updateWrapper.like(User::getName, "9")
.and(i -> i.gt(User::getAge, 18).or().isNull(User::getEmail));
// 修改(相对于QueryWrapper,这里不在创建实体类对象)
updateWrapper.set(User::getName, "牛排哥").set(User::getEmail, "my@couragesteak.com");
int result = userMapper.update(null, updateWrapper);
System.out.println("result: " + result);
8 插件
8.1 分页插件
配置类: MyBatisPlusConfig.java
/*
* @Author : 有勇气的牛排(全网同名)
* @FileName: MyBatisPlusConfig.java
* desc :
* */
package com.couragesteak.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
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;
@Configuration
@MapperScan("com.couragesteak.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加具体插件: 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
测试
// SELECT id,u_name AS name,age,email,is_deleted FROM c_user WHERE is_deleted=0 LIMIT ?,?
Page<User> page = new Page<>(2, 3);
userMapper.selectPage(page, null);
System.out.println(page);
System.out.println("======");
System.out.println(page.getRecords());
System.out.println("当前页面: " + page.getCurrent()); // 当前页面
System.out.println("当前 查询 记录数: " + page.getSize()); // 查询 记录数
System.out.println("总页数: " + page.getPages()); // 总页数
System.out.println("获取总记录数: " + page.getTotal()); // 获取总记录数
System.out.println("是否有下一页: " + page.hasNext()); // 是否有下一页
System.out.println("是否有上一页: " + page.hasPrevious()); // 是否有上一页

8.2 xml 自定义分页
场景:查询 age > 20的用户信息,并且分页
yml
mybatis-plus:
# 配置类型别名所对应的包
type-aliases-package: com.couragesteak.pojo
UserMapper.java
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.couragesteak.pojo.User;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
import java.util.Map;
@Repository // 将接口标识为持久层组件
public interface UserMapper extends BaseMapper<User> {
Map<String, Object> selectByMapId(Long id);
// 自定义分页 返回值 必须为Page对象
Page<User> selectPageVo(@Param("page") Page<User> page, @Param("page") Integer age);
}
UserMapper.xml
<!-- Page<User> selectPageVo(@Param("page") Page<User> page, @Param("page") Integer age); -->
<select id="selectPageVo" resultType="User">
select id,u_name,age,email from c_user where age > #{age}
</select>
测试
// select id,u_name,age,email from c_user where age > ? LIMIT ?
Page<User> page = new Page<>(1, 3);
// 查 age> 20 的,并且分页
userMapper.selectPageVo(page, 20);
System.out.println(page);
System.out.println("======");
System.out.println(page.getRecords());
System.out.println("当前页面: " + page.getCurrent()); // 当前页面
System.out.println("当前 查询 记录数: " + page.getSize()); // 查询 记录数
System.out.println("总页数: " + page.getPages()); // 总页数
System.out.println("获取总记录数: " + page.getTotal()); // 获取总记录数
System.out.println("是否有下一页: " + page.hasNext()); // 是否有下一页
System.out.println("是否有上一页: " + page.hasPrevious()); // 是否有上一页

8.3 乐观锁插件
8.3.1 模拟数据修改冲突
创建表
use mybatis_plus;
CREATE TABLE `c_product` (
`id` bigint(20) NOT NULL COMMENT '主键ID',
`name` varchar(30) DEFAULT NULL COMMENT '商品名称',
`price` int(11) DEFAULT 0 COMMENT '价格',
`version` int(50) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
插入数据
INSERT INTO c_product (id, NAME, price) VALUES (1, '网站开发', 2000);
创建实体
@Data
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}
案例: 某商品售价100,先加50,再减30,但是小王、小李同时读取数据库,进行修改,导致小李修改的结果被小王的覆盖成为70。(应为120)
// 小李 查询 商品价格
Product product_li = productMapper.selectById(1);
// 小王 查询 商品价格
Product product_w = productMapper.selectById(1);
System.out.println("小李查询的商品价格:" + product_li.getPrice());
// 小李查询的商品价格: 2000
System.out.println("小李查询的商品价格:" + product_w.getPrice());
// 小王查询的商品价格: 2000
// 小李将商品价格 +50
product_li.setPrice(product_li.getPrice() + 50);
productMapper.updateById(product_li);
// 小王将商品价格 -30
product_w.setPrice(product_w.getPrice() - 30);
productMapper.updateById(product_w);
// 老板查询商品价格
Product product_boss = productMapper.selectById(1);
System.out.println("老板查询的商品价格:" + product_boss.getPrice());
// 老板查询的商品价格:70
8.3.2 乐观锁实现流程
在表中添加 version 字段,取出记录时获取当前version。
SELECT id,`name`,price,`version` FROM c_product WHERE id=1
更新时,version+1,如果where语句中的version版本不对,则更新失败。
UPDATE c_product SET price=price+50, `version`=`version`+1 WHERE id=1 AND
`version`=1
8.3.3 MyBatis-Plus实现乐观锁
1、修改实体类
/*
* @Author : 有勇气的牛排(全网同名)
* @FileName: Product.java
* desc : 乐观锁版本
* */
package com.couragesteak.pojo;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.Version;
import lombok.Data;
@Data
@TableName("c_product")
public class Product {
private Long id;
private String name;
private Integer price;
@Version // 标识乐观锁版本号字段
private Integer version;
}
2、添加乐观锁插件配置
/*
* @Author : 有勇气的牛排(全网同名)
* @FileName: MyBatisPlusConfig.java
* desc : 插件配置
* */
package com.couragesteak.config;
...
@Configuration
@MapperScan("com.couragesteak.mapper")
public class MyBatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加具体插件: 分页插件
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 添加乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
3、下面可以执行8.3.1中的案例,进行测试
4、优化流程 :失败重试
// 小李 查询 商品价格
Product product_li = productMapper.selectById(1);
// 小王 查询 商品价格
Product product_w = productMapper.selectById(1);
System.out.println("小李查询的商品价格:" + product_li.getPrice());
// 小李查询的商品价格: 2000
System.out.println("小李查询的商品价格:" + product_w.getPrice());
// 小王查询的商品价格: 2000
// 小李将商品价格 +50
product_li.setPrice(product_li.getPrice() + 50);
productMapper.updateById(product_li);
// 小王将商品价格 -30
product_w.setPrice(product_w.getPrice() - 30);
int result = productMapper.updateById(product_w);
if (result==0){
// 操作失败,重试
Product productNew = productMapper.selectById(1);
productNew.setPrice(productNew.getPrice()-30);
productMapper.updateById(productNew);
}
// 老板查询商品价格
Product product_boss = productMapper.selectById(1);
System.out.println("老板查询的商品价格:" + product_boss.getPrice());
// 老板查询的商品价格:120
9 通用枚举
表中有些字段值是固定的,例如性别(男或女),此时我们可以使用MaBatis-Plus的通用枚举来实现。

yaml
mybatis-plus:
# 扫描通用枚举的包
type-enums-package: com.couragesteak.enums
枚举类: SexEnum.java
@Getter
public enum SexEnum {
MALE(1,"男"),
FEMALE(2,"女");
private Integer sex;
private String sexName;
// 创建构造器
SexEnum(Integer sex, String sexName) {
this.sex = sex;
this.sexName = sexName;
}
}
实体
@Data
@TableName("c_user")
public class User {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField("u_name")
private String name;
private Integer age;
private String email;
@EnumValue // 将注解所标识属性的值存储到数据库中
private SexEnum sex;
@TableLogic
private Integer isDeleted;
}
解决报错:
报错: Cause: java.sql.SQLException: Incorrect integer value: 'MALE' for column 'sex' at row 1
show variables like '%char%';
set character_set_server=utf8mb4;
ALTER TABLE `c_user` MODIFY COLUMN `sex` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
10 代码生成器
依赖
<!-- MyBatis-Plus 代码生成器 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.1</version>
</dependency>
<!-- freemarker模板 -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
快速生成代码: FastAutoGeneratorTest.java
/*
* @Author : 有勇气的牛排(全网同名)
* @FileName: FastAutoGeneratorTest.java
* desc :
* */
package com.couragesteak;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import java.util.Collections;
public class FastAutoGeneratorTest {
public static void main(String[] args) {
FastAutoGenerator.create("jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false", "root", "root123456")
.globalConfig(builder -> {
builder.author("有勇气的牛排") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("E://java//MyBatisPlus//tmp"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.couragesteak") // 设置父包名
.moduleName("mybatisplus") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "E://java//MyBatisPlus//tmp")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("c_user") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
}
}
12 多数据源
适用于多种场景:纯粹多库、读写分离、一主多从、混合模式等。
创建两个库:mybatis_plus、mybatis_plu_1(新建),将mybatis_plus库的product表移动到mybatis_plu_1库,通过一个测试用例,分别获取用户数据商品数据,如果获取到说明,多库模拟成功。
引入依赖
<!-- 多数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>
yml配置
spring:
# 配置数据源信息
datasource:
dynamic:
# 设置默认的数据源 或者 数据源组,默认值即为master
primary: master
# 严格匹配数据源,默认false.true未匹配到指定数据源时抛异常,false使用默认数据源
strict: false
datasource:
master:
url: jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root123456
slave_1:
url: jdbc:mysql://localhost:3306/mybatis_plus_1?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root123456
指定数据源,可以是实体,也可以是Service
UserServiceImpl实现类
@Service
@DS("master")
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
}
ProductServiceImpl 实现类
@Service
@DS("slave_1") //指定所操作的数据源
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {
}
13 MyBatisX插件
idea安装插件:MyBatisX
官方文档:https://baomidou.com/pages/ba5b24/#功能

13.1 生成实体、mapper、service、映射文件
需要连接数据源



13.2 生成 insert语句
生成MyBatisSQL



13.3 生成delete语句
插入

删除
# 根据ID和用户名删除
deleteByIdAndName
int deleteByIdAndName(@Param("id") Long id, @Param("name") String name);
<delete id="deleteByIdAndName">
delete
from c_product
where id = #{id,jdbcType=NUMERIC}
AND name = #{name,jdbcType=VARCHAR}
</delete>
这也太厉害这
- 多个条件用And连接即可
查询
// 按照年龄降序
selectAllOrderByAgeDesc
[1] 感谢尚硅谷
标签: