1. MP前瞻
官网:https://baomidou.com/
1、MyBatis-Plus是什么:MyBatis-Plus(简称MP)是一个MyBatis的增强工具,它在MyBatis的基础上只做增强不做改变,为简化开发、提供效率而生。并且MP内部提供了丰富的 API,可以解放单表CRUD方法的编写,使开发者更专注于业务逻辑的实现。
2、MP的特性:无侵入损耗小,提供强大的CRUD操作,支持Lambda形式调用,支持 ActiveRecord 形式调用,支持主键生成,内置分页插件、代码生成器和性能分析插件等等。
3、支持的数据库:有MySQL、PostgreSQL、MariaDB、Oracle、OceanBase、SQLite、SQLServer、H2、DB2、达梦、人大金仓、虚谷等等。
4、SpringBoot整合MP,需要引入的依赖:
<!--SpringBoot2-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!--SpringBoot3-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.5</version>
</dependency>
2. MP快速入门
1、执行sql脚本:
CREATE DATABASE `mp_db` CHARACTER SET utf8mb4;
use `mp_db`;
CREATE TABLE `user`
(
`id` BIGINT NOT NULL COMMENT '主键ID',
`name` VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
`age` INT NULL DEFAULT NULL COMMENT '年龄',
`email` VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
INSERT INTO `user` (id, name, age, email) VALUES
(1, 'Jone', 18, 'test1@baomidou.com'),
(2, 'JackMa', 20, 'test2@baomidou.com'),
(3, 'Tom', 28, 'test3@baomidou.com'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
2、创建SpringBoot工程
3、引入mp相关的依赖坐标:
<dependencies>
<!--mp-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok简化实体开发-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--单元测试起步依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
4、在 application.yml中,配置数据库的连接信息:
#DataSource Config
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/mp_db?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
#MybatisPlus Config
mybatis-plus:
configuration:
#开启SQL日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
5、编写实体类:
package cn.aopmin.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* 用户实体
*
* @author 白豆五
* @since 2024/7/28
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")//指定要绑定的表名
public class User implements Serializable {
@TableId(type = IdType.AUTO, value = "id")// 设置主键生成策略
private Long id;
private String name;
private Integer age;
private String email;
}
实体类与表名规则要求:
- MP默认情况下,要求:实体类和表名 要符合 下划线与驼峰的映射规则;
- 如果不符合:就需要在实体类上加
@TableName("表名")
实体类里属性与字段的规则要求:
默认情况下,要求每个属性都必须有一个对应的字段,属性名和字段名要符合 驼峰与下划线映射规则;
非主键字段:
- 如果一个属性名,和字段名不对应,怎么办?加
@TableField(value="字段名")
- 如果一个属性,没有对应的字段存在,怎么办?加
@TableField(exists=false)
- 如果一个属性,不想查询对应字段的值,怎么办?加
@TableField(select=false)
主键字段:
- 要使用@TableId(value=“字段名”, type=主键生成策略)
- 主键生成策略,设置的方式:实体类里主键属性上加 @TableId(type=IdType.策略名)
- NONE:不设置,跟随全局
- AUTO:主键值自增,前提是原有的数据库主键支持自增
- INPUT:由我们的代码设置主键值,不让MP生成主键值
- ASSIGN_ID:雪花算法。根据时间戳+机器码+序列号生成最终的Long类型的数字,特点是单调递增的
- ASSIGN_UUID:UUID算法。不推荐,因为UUID值是乱序的,会影响主键字段上的索引
6、编写mapper接口:
package cn.aopmin.mapper;
import cn.aopmin.domain.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* 定义一个Mapper接口,继承BaseMapper<实体类>
*
* @author 白豆五
* @since 2024/7/28
*/
public interface UserMapper extends BaseMapper<User> {
}
7、在启动类上加上@MapperScan注解:
package cn.aopmin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@MapperScan("cn.aopmin.mapper") // 扫描mapper接口
@SpringBootApplication
public class MPApplication {
public static void main(String[] args) {
SpringApplication.run(MPApplication.class, args);
}
}
8、测试:
package cn.aopmin;
import cn.aopmin.domain.User;
import cn.aopmin.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;
/**
* mp快速入门
*
* @author 白豆五
* @since 2024/7/28
*/
@SpringBootTest
public class Demo01MpTest {
@Autowired
private UserMapper userMapper;
@Test
public void test() {
List<User> list = userMapper.selectList(null);
list.forEach(System.out::println);
}
}
3. mapper内置方法
文档地址:https://baomidou.com/guides/data-interface/#mapper-interface
MP内置了强大的BaseMapper,它已经提供好了单表CURD功能:只要我们的Mapper接口继承了BaseMapper,然后泛型设置表名对应的实体类,就可以直接使用整套的单表CURD功能了。
Mapper接口常用方法:
- 新增:
int insert(T entity)
- 根据id修改:
int updateById(T entity)
- 根据id删除:
int deleteById(Serializable id)
- 根据id查询:
T selectById(Serializable id)
- (根据条件) 查询一条:
T selectOne(Wrapper<T> w)
- (根据条件) 查询列表:
List<T> selectList(Wrapper<T> w)
- (根据条件) 查询数量:
Integer selectCount(Wrapper<T> w)
- (根据条件) 分页查询:
IPage selectPage(IPage page, Wrapper<T> w)
- …
4. service内置方法
文档地址:https://baomidou.com/guides/data-interface/#service-interface
同时MP也提供了通用Service接口,如果Service层也想操作,只需继承IService 接口就可以得到整套单表CURD功能。
Service接口常用方法:
- 新增:
boolean save(T entity)
- 根据id修改:
boolean updateById(T entity)
- 根据id删除:
boolean removeById(Serializable id)
- 根据id查询:
getById(Serializable id)
- (根据条件) 查询一条:
T getOne(Wrapper<T> queryWrapper)
- (根据条件) 查询列表:
List<T> list(Wrapper<T> queryWrapper)
- (根据条件) 查询数量:
int count(Wrapper<T> w)
- (根据条件) 分页查询:
IPage page(IPage page, Wrapper<T> w)
- …
5.wrapper条件构造器
文档地址:https://baomidou.com/guides/wrapper/
MyBatis-Plus 的 Wrapper 类是构建查询和更新条件的核心工具,它允许开发者以链式编程方式构造where条件,从而简化SQL语句的编写、降低SQL注入的风险。
Wrapper 常用子类:
- QueryWrapper、UpdateWrapper:使用时容易写错,但使用更灵活。
- LambdaQueryWrapper、LambdaUpdateWrapper:没有QueryWrapper、UpdateWrapper灵活,但是不易写错(拼接条件不用指定表字段,而是对应属性名),使用的更多一些。
QueryWrapper的用法:
//直接new的方式。
QueryWrapper<实体类> wrapper = new QueryWrapper<实体类>()
.select("字段1,字段2,字段3 as 别名, ifnull(...) as xx")
//条件方法:eq, ne, gt, ge, lt, le, like, notLike, in, notIn, isNull, isNotNull....
.条件方法(字段名,值)
.条件方法(是否要拼接此条件, 字段名,值)
.orderByAsc("字段名...").orderByDesc("字段名...")
//直接Wrappers的静态方法方式。
QueryWrapper<实体类> wrapper = Wrappers.<实体类>query()
.select("字段1,字段2,字段3 as 别名, ifnull(...) as xx")
//条件方法:eq, ne, gt, ge, lt, le, like, notLike, in, notIn, isNull, isNotNull....
.条件方法(字段名,值)
.条件方法(是否要拼接此条件, 字段名,值)
.orderByAsc("字段名...").orderByDesc("字段名...")
List list = xxxMapper.selectList(wrapper);
- 条件方法:
- eq(“字段名”, 值): 等于。
- ne(“字段名”, 值):不等于。
- gt(“字段名”, 值): 大于。
- ge(“字段名”, 值): 大于等于。
- lt(“字段名”, 值): 小于。
- le(“字段名”, 值): 小于等于。
- like(“字段名”, 值): 模糊匹配,使用 % 作为通配符。
- notLike(“字段名”, 值): 不模糊匹配,使用 % 作为通配符。
- in(“字段名”, 值列表): 在某个集合中。
- notIn(“字段名”, 值列表): 不在某个集合中。
- isNull(“字段名”): 为空。
- isNotNull(“字段名”): 不为空。
- orderByAsc(“字段名”): 按字段名升序排序。
- orderByDesc(“字段名”): 按字段名降序排序。
LambdaQueryWrapper的用法:
//直接new
LambdaQueryWrapper<实体类> wrapper = new LambdaQueryWrapper<实体类>()
.select(实体类::get属性, ....)
.条件方法(实体类::get属性,值)
.条件方法(是否要拼接此条件, 实体类::get属性,值)
.orderByAsc(实体类::get属性,..).orderByDesc(实体类::get属性,...)
//使用Wrappers的静态方法
LambdaQueryWrapper<实体类> wrapper = Wrappers.<实体类>lambdaQuery()
.select(实体类::get属性, ....)
.条件方法(实体类::get属性,值)
.条件方法(是否要拼接此条件, 实体类::get属性,值)
.orderByAsc(实体类::get属性,..).orderByDesc(实体类::get属性,...)
UpdateWrapper的用法:
// 直接new的方式。
UpdateWrapper<实体类> wrapper = new UpdateWrapper<实体类>()
// 设置更新字段
.set("字段名", 值)
// 设置更新条件,使用与QueryWrapper相同的条件方法
.条件方法("字段名", 值);
int result = userMapper.update(entity, wrapper);
// 使用Wrappers的静态方法
UpdateWrapper<实体类> wrapper = Wrappers.<实体类>update()
// 设置更新字段
.set("字段名", 值)
// 设置更新条件,使用与QueryWrapper相同的条件方法
.条件方法("字段名", 值);
// 执行更新操作
int result = xxxMapper.update(entity, wrapper);
LambdaUpdateWrapper的用法:
// 直接new的方式。
LambdaUpdateWrapper<实体类> wrapper = new LambdaUpdateWrapper<实体类>()
// 设置更新字段
.set("字段名", 值)
// 设置更新条件
.条件方法("字段名", 值);
int result = userMapper.update(entity, wrapper);
// 使用Wrappers的静态方法
LambdaUpdateWrapper<实体类> wrapper = Wrappers.<实体类>lambdaUpdate()
// 设置更新字段
.set("字段名", 值)
// 设置更新条件
.条件方法("字段名", 值);
// 执行更新操作
int result = userMapper.update(entity, wrapper);
6. MP分页查询
文档地址:https://baomidou.com/plugins/pagination/
MyBatis-Plus 的分页插件 PaginationInnerInterceptor
提供了强大的分页功能,支持多种数据库,使得分页查询变得简单高效。
使用步骤:
1、在配置类或启动类中配置 分页插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加
return interceptor;
}
2、调用mapper接口的selectPage方法进行分页查询
Page<实体类> page = xxxMapper.selectPage( new Page(页码,每页几条), wrapper对象 );
page.getTotal(); //获取总数量
page.getPages(); //获取总页数
page.getRecords();//获取数据列表
分页API介绍:
MP的Mapper接口提供的分页查询方法是:IPage selectPage(IPage page, Wrapper wrapper)
。
-
参数page:用于封装分页条件,包括页码和查询数量
-
参数wrapper:用于封装查询条件,实现条件查询并分页
-
返回值Page:分页查询的结果
IPage:是一个接口;Page是它的实现类,表示分页信息对象。
- 在执行分页查询前,把分页参数封装成Page对象
- 当执行分页查询后,MP会把查询结果封装到这个Page对象中。
Page对象常用方法:
- new Page(pageNumber, pageSize):创建分页信息Page对象
- getCurrent():获取当前页码
- getPages():获取总页数
- getSize():获取每页几条
- getTotal():获取总数量
示例:
/ 创建分页对象,当前页为1,每页显示10条记录
IPage<User> page = new Page<>(1, 10);
// 创建查询条件
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.eq("status", 1); // 查询条件:状态为1
// 执行分页查询
IPage<User> resultPage = userMapper.selectPage(page, wrapper);
// 获取分页信息
List<User> userList = resultPage.getRecords(); // 当前页的记录
long total = resultPage.getTotal(); // 总记录数
long pages = resultPage.getPages(); // 总页数
7. 自定义SQL
mp本身是兼容mybatis的,当mp无法满足特定需求时(比如 多表操作),我们也可以像mybatis一样,通过注解或 xml方式手写sql进行扩展。
参考:https://cloud.tencent.com/developer/article/1531517
8. 逻辑删除
在开发中,删除一条数据通常有两种方案可以实现:
- 物理删除:直接从数据库中删数据。
- 逻辑删除:并不是真正的删除数据,而是把数据的状态字段值设置为“已删除”状态;当查询数据时,不查询“已删除”状态的数据。
如何实现逻辑删除?
- 修改表,增加一个状态字段,用于存储数据是否删除的状态
use mp_db;
alter table user add deleted int default 0;
- 修改实体类,增加对应的属性,并给属性上加@TableLogic
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userName;
private String password;
private Integer age;
private String tel;
private String sex;
//增加@TableLogic注解,这个字段被声明为 逻辑删除状态字段
@TableLogic
private int deleted;
}
- 修改配置文件,告诉MP,状态字段值为几的时候是已删除,状态是几的时候是未删除
mybatis-plus:
global-config:
db-config:
#logic-delete-field: deleted #全局的默认逻辑删除字段名,即 状态字段名。
logic-delete-value: 1 #已删除状态的值
logic-not-delete-value: 0 #未删除状态的值
9. 自动填充字段
文档地址:https://baomidou.com/guides/auto-fill-field/
MP提供了一个便捷的自动填充功能,用于在新增或修改数据时自动填充指定字段,比如 创建时间、更新时间、创建人和修改人等。
10. 多数据源
文档地址:https://baomidou.com/guides/dynamic-datasource/
随着项目规模的扩大和业务需求的复杂化,单一数据源已经不能满足实际开发中的需求。在许多情况下,我们需要同时操作多个数据库,或者需要将不同类型的数据存储在不同的数据库中。这时,多数据源场景成为必不可少的解决方案。
市面上常见的多数据源实现方案如下:
方案1:基于Spring框架提供的AbstractRoutingDataSource
- 优点:简单易用,支持动态切换数据源,适用于少量数据源情况。
- 场景:适用于需要动态切换数据源,且数据库较少的情况。
- 文档地址:https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/datasource/utils/AbstractRoutingDataSource.html
方案2:使用MP提供的Dynamic-datasource多数据源框架
- 优点:提供了简单的API,支持动态切换和明确定义数据源。
- 文档地址:https://baomidou.com/guides/dynamic-datasource/#dynamic-datasource
方案3:通过自定义注解在方法或类上指定数据源
- 优点:灵活性高,能够精确地控制数据源切换;在代码中直观明了。
- 场景:适用于需要在代码层面进行数据源切换,并对数据源切换有精细要求的情况。
方案4:使用动态代理技术,在运行时动态切换数据源,实现多数据源的切换。
- 优点:灵活性高,支持在运行时动态切换数据源;适合对数据源切换的逻辑有特殊需求的情况。
- 场景:适用于需要在运行时动态决定数据源切换策略的情况。
11. 乐观锁
文档地址:https://baomidou.com/plugins/optimistic-locker/
悲观锁:
-
在自己操作资源数据期间,认为总有其它线程来干扰,所以要添加排他性的、独占性的锁。
在我加锁操作期间,其它所有线程都要阻塞排队。直到我释放锁,其他线程才可以再抢锁操作。
-
特点:
- 安全性高,因为把多线程并发变成了串行
- 性能较低,因为同一时间只有一个线程能操作,其它线程都是阻塞排队状态
-
适合:写多读少的情况
-
技术:synchronized、Lock对象…
乐观锁:
-
在自己操作资源数据期间,其它线程很少来干扰,所以并不需要真正添加锁,可以有更好的性能
自己在每次操作数据时,都先校验感知一下,数据是否被其它线程修改过了;如果被其它线程修改过了,就放弃操作或者报错;如果没有被其它线程修改,就直接执行操作。
-
特点:
- 安全性足够
- 性能比较好。因为没有真正加锁,在当前线程操作期间,可以有其它线程执行读操作,不会有任何影响
-
适合:读多写少的情况
-
具体实现:
- CAS思想:Compare And Swap 对比并交换设置。比如:Java里的AtomicInteger底层使用了CAS思想,并不需要真正加锁,也能实现多线程操作时的线程安全性。
- 版本号:给数据设置版本号,每次变动数据时都要把版本号+1。修改数据就可以感知,数据是否被其它线程修改了
MP的乐观锁,采用的是版本号方式,使用步骤如下:
- 给表里增加一个版本号字段
use mp_db;
alter table user add version int default 0;
- 给实体类里增加对应的属性,并添加@Version
@Data
@TableName("user")
public class User {
@TableId(type = IdType.AUTO)
private Long id;
private String userName;
private String password;
private Integer age;
private String tel;
private String sex;
@TableLogic
private int deleted;
//增加对应的属性,并添加@Version注解
@Version
private int version;
}
- 给配置类或引导类里,配置 乐观锁插件
@Configuration
public class MpConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//创建拦截器对象MybatisPlusInterceptor
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//添加分页插件PaginationInnerInterceptor,注意数据库的类型。如果数据库是MySQL,就设置DbType.MYSQL
//interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
//添加乐观锁插件OptimisticLockerInnerInterceptor
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
12. MP其他功能
- 代码生成器
- 动态表名插件
- SQL分析和打印
- 防全表更新和删除插件
- …