https://www.w3cschool.cn/mybatis_plus/mybatis_plus-udwn3mgc.html
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本
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
的数据库,具体支持情况如下:
-
MySQL
,Oracle
,DB2
,H2
,HSQL
,SQLite
,PostgreSQL
,SQLServer
,Phoenix
,Gauss
,ClickHouse
,Sybase
,OceanBase
,Firebird
,Cubrid
,Goldilocks
,csiidb
- 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库
MyBatis-Plus 快速入门-快速开始
我们将通过一个简单的 Demo 来阐述 MyBatis-Plus 的强大功能,在此之前,我们假设您已经:
- 拥有
Java
开发环境以及相应 IDE - 熟悉
Spring Boot
- 熟悉
Maven
现有一张 User
表,其表结构如下:
id | name | age | |
1 | Jone | 18 | test1@baomidou.com |
2 | Jack | 20 | test2@baomidou.com |
3 | Tom | 28 | test3@baomidou.com |
4 | Sandy | 21 | test4@baomidou.com |
5 | Billie | 24 | test5@baomidou.com |
其对应的数据库 Schema
脚本如下:
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主键ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年龄',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (id)
);
其对应的数据库 Data
脚本如下:
DELETE FROM user;
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'),
(4, 'Sandy', 21, 'test4@baomidou.com'),
(5, 'Billie', 24, 'test5@baomidou.com');
初始化工程
创建一个空的 Spring Boot 工程(工程将以 H2 作为默认数据库进行演示)
提示:可以使用 Spring Initializer快速初始化一个 Spring Boot 工程
添加依赖
引入 Spring Boot Starter 父工程:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.4</version>
<relativePath/>
</parent>
引入 spring-boot-starter
、spring-boot-starter-test
、mybatis-plus-boot-starter
、h2
依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
配置
在 application.yml
配置文件中添加 H2
数据库的相关配置:
# DataSource Config
spring:
datasource:
driver-class-name: org.h2.Driver
schema: classpath:db/schema-h2.sql
data: classpath:db/data-h2.sql
url: jdbc:h2:mem:test
username: root
password: test
在 Spring Boot 启动类中添加 @MapperScan
注解,扫描 Mapper 文件夹:
@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
编码
编写实体类 User.java
(此处使用了Lombok简化代码)
@Data
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
编写 Mapper 包下的 UserMapper
接口
public interface UserMapper extends BaseMapper<User> {
}
开始使用
添加测试类,进行功能测试:
@SpringBootTest
public class SampleTest {
@Autowired
private UserMapper userMapper;
@Test
public void testSelect() {
System.out.println(("----- selectAll method test ------"));
List<User> userList = userMapper.selectList(null);
Assert.assertEquals(5, userList.size());
userList.forEach(System.out::println);
}
}
提示:UserMapper
中的 selectList()
方法的参数为 MP
内置的条件封装器 Wrapper
,所以不填写就是无任何条件
控制台输出:
User(id=1, name=Jone, age=18, email=test1@baomidou.com)
User(id=2, name=Jack, age=20, email=test2@baomidou.com)
User(id=3, name=Tom, age=28, email=test3@baomidou.com)
User(id=4, name=Sandy, age=21, email=test4@baomidou.com)
User(id=5, name=Billie, age=24, email=test5@baomidou.com)
小结
通过以上几个简单的步骤,我们就实现了 User
表的 CRUD
功能,甚至连 XML
文件都不用编写!
从以上步骤中,我们可以看到集成MyBatis-Plus
非常的简单,只需要引入 starter
工程,并配置 mapper
扫描路径即可。
但 MyBatis-Plus
的强大远不止这些功能,想要详细了解 MyBatis-Plus
的强大功能?那就继续往下看吧!
MyBatis-Plus 快速入门-安装
MyBatis-Plus
3.0 版本基于 JDK8,提供了 lambda
形式的调用,所以安装集成 MP3.0 要求如下:- JDK 8+
- Maven or Gradle
Release
Spring Boot
Maven:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
Gradle:
compile group: 'com.baomidou', name: 'mybatis-plus-boot-starter', version: '3.5.1'
Spring
Maven:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>3.5.1</version>
</dependency>
Gradle:
compile group: 'com.baomidou', name: 'mybatis-plus', version: '3.5.1'
注意:引入 MyBatis-Plus
之后请不要再次引入 MyBatis
以及 MyBatis-Spring
,以避免因版本差异导致的问题。
Snapshot
快照 SNAPSHOT 版本需要添加仓库,且版本号为快照版本 点击查看最新快照版本号。
Maven:
<repository>
<id>snapshots</id>
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
</repository>
Gradle:
repositories {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
}
MyBatis-Plus 快速入门-配置
MyBatis-Plus
的配置异常的简单,我们仅需要一些简单的配置即可使用 MyBatis-Plus
的强大功能!
Spring Boot 工程
- 配置
MapperScan
注解
@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Spring 工程
- 配置
MapperScan
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.baomidou.mybatisplus.samples.quickstart.mapper"/>
</bean>
- 调整
SqlSessionFactory
为 MyBatis-Plus
的 SqlSessionFactory
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
</bean>
通常来说,一般的简单工程,通过以上配置即可正常使用 MyBatis-Plus
MyBatis-Plus 快速入门-注解
本文将介绍 MybatisPlus 注解包相关类详解
@TableName
- 描述:表名注解,标识实体类对应的表
- 使用位置:实体类
@TableName("sys_user")
public class User {
private Long id;
private String name;
private Integer age;
private String email;
}
属性 | 类型 | 必须指定 | 默认值 | 描述 |
values |
String | 否 | "" | 表名 |
schema |
String | 否 | "" | schema |
keepGlobalPrefix |
boolean | 否 | false | 是否保持使用全局的 tablePrefix 的值(当全局 tablePrefix 生效时) |
resultMap |
String | 否 | "" | xml 中 resultMap 的 id(用于满足特定类型的实体类对象绑定) |
autoResultMap |
boolean | 否 | false | 是否自动构建 resultMap 并使用(如果设置 resultMap 则不会进行 resultMap 的自动构建与注入) |
excludeProperty |
String[] | 否 | {} | 需要排除的属性名 |
关于 autoResultMap
的说明:
MP 会自动构建一个 resultMap
并注入到 MyBatis 里(一般用不上),请注意以下内容:
因为 MP 底层是 MyBatis,所以 MP 只是帮您注入了常用 CRUD 到 MyBatis 里,注入之前是动态的(根据您的 Entity 字段以及注解变化而变化),但是注入之后是静态的(等于 XML 配置中的内容)。
而对于 typeHandler
属性,MyBatis 只支持写在 2 个地方:
- 定义在
resultMap
里,作用于查询结果的封装 - 定义在
insert
和 update
语句的 #{property}
中的 property
后面(例:#{property,typehandler=xxx.xxx.xxx}
),并且只作用于当前 设置值
除了以上两种直接指定 typeHandler
的形式,MyBatis 有一个全局扫描自定义 typeHandler
包的配置,原理是根据您的 property
类型去找其对应的 typeHandler
并使用。
@TableId
- 描述:主键注解
- 使用位置:实体类主键字段
@TableName("sys_user")
public class User {
@TableId
private Long id;
private String name;
private Integer age;
private String email;
}
属性 | 类型 | 必须指定 | 默认值 | 描述 |
value | String | 否 | "" | 主键字段名 |
type | Enum | 否 | IdType.NONE | 指定主键类型 |
IdType
值 | 描述 |
AUTO |
数据库 ID 自增 |
NONE |
无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) |
INPUT |
insert 前自行 set 主键值 |
ASSIGN_ID |
分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator 的方法nextId (默认实现类为DefaultIdentifierGenerator 雪花算法) |
ASSIGN_UUID |
分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator 的方法nextUUID (默认 default 方法) |
ID_WORKER |
分布式全局唯一 ID 长整型类型(please use ASSIGN_ID ) |
UUID |
32 位 UUID 字符串(please use ASSIGN_UUID ) |
ID_WORKER_STR |
分布式全局唯一 ID 字符串类型(please use ASSIGN_ID ) |
@TableField
描述:字段注解(非主键)
@TableName("sys_user")
public class User {
@TableId
private Long id;
@TableField("nickname")
private String name;
private Integer age;
private String email;
}
属性 | 类型 | 必须指定 | 默认值 | 描述 |
value |
String | 否 | "" | 数据库字段名 |
exist |
boolean | 否 | true | 是否为数据库表字段 |
ondition |
String | 否 | "" | 字段where 实体查询比较条件,有值设置则按设置的值为准,没有则为默认全局的%s=#{%s} |
update |
String | 否 | "" | 字段 update set 部分注入,例如:当在version字段上注解update="%s+1" 表示更新时会 set version=version+1 (该属性优先级高于 el 属性) |
insertStrategy |
Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_NULL insert into table_a(<if test="columnProperty != null">column</if>) values (<if test="columnProperty != null">#{columnProperty}</if>) |
updataStrategy |
Enum | 否 | FieldStrategy.DEFAULT | 举例:IGNORED update table_a set column=#{columnProperty} |
whereStrategy |
Enum | 否 | FieldStrategy.DEFAULT | 举例:NOT_EMPTY where <if test="columnProperty != null and columnProperty!=''">column=#{columnProperty}</if> |
fill |
Enum | 否 | FieldFill.DEFAULT | 字段自动填充策略 |
select |
boolean | 否 | true | 是否进行select查询 |
keepGlobalFormat |
boolean | 否 | false | 是否保持使用全局的format进行处理 |
jabcType |
jabcType | 否 | JdbcType.UNDEFINED | JDBC类型(该默认值不代表会按照该值生效) |
typeHandler |
Class<?extends TypeHandler> | 否 | UnknownTypeHandler.class | 类型处理器(该默认值不代表会按照该值生效) |
numericScale |
String | 否 | "" | 指定小数点后保留的位数 |
关于jdbcType
和typeHandler
以及numericScale
的说明:
numericScale
只生效于 update 的 sql. jdbcType
和typeHandler
如果不配合@TableName#autoResultMap = true
一起使用,也只生效于 update 的 sql. 对于typeHandler
如果你的字段类型和 set 进去的类型为equals
关系,则只需要让你的typeHandler
让 Mybatis 加载到即可,不需要使用注解
FieldStrategy
值 | 描述 |
IGNORED |
忽略判断 |
NOT_NULL |
非 NULL 判断 |
NOT_EMPTY |
非空判断(只对字符串类型字段,其他类型字段依然为非 NULL 判断) |
DEFAULT |
追随全局配置 |
FieldFill
值 | 描述 |
DEFAULT |
默认不处理 |
INSERT |
插入时填充字段 |
UPDATE |
更新时填充字段 |
INSERT_UPDATE |
插入和更新时填充字段 |
@Version
- 描述:乐观锁注解、标记
@Verison
在字段上
@EnumValue
- 描述:普通枚举类注解(注解在枚举字段上)
@TableLogic
- 描述:表字段逻辑处理注解(逻辑删除)
属性 | 类型 | 必须指定 | 默认值 | 描述 |
value |
String | 否 | "" | 逻辑未删除值 |
delval |
String | 否 | "" | 逻辑删除值 |
@KeySequence
- 描述:序列主键策略
oracle
- 属性:
value
、resultMap
属性 | 类型 | 必须指定 | 默认值 | 描述 |
value |
String | 否 | "" | 序列名 |
clazz |
Class | 否 | Long.class | id 的类型, 可以指定 String.class,这样返回的 Sequence 值是字符串"1" |
@OrderBy
- 描述:内置 SQL 默认指定排序,优先级低于 wrapper 条件查询
属性 | 类型 | 必须指定 | 默认值 | 描述 |
isDesc | boolean | 否 | true | 是否倒序查询 |
sort | short | 否 | Short.MAX_VALUE | 数字越小越靠前 |
MyBatis-Plus 核心功能-代码生成器(新)
注意:适用版本:mybatis-plus-generator 3.5.1 及其以上版本,对历史版本不兼容!
快速入门
安装
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
当前包未传递依赖 MP 包,需要自己引入!
使用
快速生成
FastAutoGenerator.create("url", "username", "password")
.globalConfig(builder -> {
builder.author("baomidou") // 设置作者
.enableSwagger() // 开启 swagger 模式
.fileOverride() // 覆盖已生成文件
.outputDir("D://"); // 指定输出目录
})
.packageConfig(builder -> {
builder.parent("com.baomidou.mybatisplus.samples.generator") // 设置父包名
.moduleName("system") // 设置父包模块名
.pathInfo(Collections.singletonMap(OutputFile.mapperXml, "D://")); // 设置mapperXml生成路径
})
.strategyConfig(builder -> {
builder.addInclude("t_simple") // 设置需要生成的表名
.addTablePrefix("t_", "c_"); // 设置过滤表前缀
})
.templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板
.execute();
交互式生成
FastAutoGenerator.create(DATA_SOURCE_CONFIG)
// 全局配置
.globalConfig((scanner, builder) -> builder.author(scanner.apply("请输入作者名称?")).fileOverride())
// 包配置
.packageConfig((scanner, builder) -> builder.parent(scanner.apply("请输入包名?")))
// 策略配置
.strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all")))
.controllerBuilder().enableRestStyle().enableHyphenStyle()
.entityBuilder().enableLombok().addTableFills(
new Column("create_time", FieldFill.INSERT)
).build())
/*
模板引擎配置,默认 Velocity 可选模板引擎 Beetl 或 Freemarker
.templateEngine(new BeetlTemplateEngine())
.templateEngine(new FreemarkerTemplateEngine())
*/
.execute();
// 处理 all 情况
protected static List<String> getTables(String tables) {
return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(","));
}
MyBatis-Plus CRUD接口-Service CRUD 接口
- 通用 Service CRUD 封装
IService
接口,进一步封装 CRUD 采用get
查询单行,remove
删除,list
查询集合,page
分页,前缀命名方式区分Mapper
层避免混淆, - 泛型
T
为任意实体对象 - 建议如果存在自定义通用 Service 方法的可能,请创建自己的
IBaseService
继承Mybatis-Plus
提供的基类 - 对象
Wrapper
为条件构造器
Save
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);
参数说明
参数名 | 类型 | 描述 |
entity |
T | 实体对象 |
entityList |
Collection<T> | 实体对象集合 |
batchSize |
int | 插入批次数量 |
SaveOrUpdate
// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
参数说明
参数名 | 类型 | 描述 |
entity |
T | 实体对象 |
updateWrapper |
Wrapper<T> | 实体对象封装操作类 UpdateWrapper |
entityList |
Collection<T> | 实体对象集合 |
batchSize |
int | 插入批次数量 |
Remove
// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
参数说明
参数名 | 类型 | 描述 |
queryWrapper |
Wrapper<T> | 实体包装类 QueryWrapper |
id |
Serializable | 主键 ID |
columnMap |
Map<String, Object> | 表字段 map 对象 |
idList |
Collection<? extends Serializable> | 主键 ID 列表 |
Update
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);
参数说明
参数名 | 类型 | 描述 |
updateWrapper |
Wrapper<T> | 实体对象封装操作类 UpdateWrapper |
entity |
T | 实体对象 |
entityList |
Collection<T> | 实体对象集合 |
batchSize |
int | 更新批次数量 |
Get
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
参数说明
参数名 | 类型 | 描述 |
id |
Serializable | 主键 ID |
queryWrapper |
Wrapper<T> | 实体对象封装操作类 QueryWrapper |
throwEx |
boolean | 有多个 result 是否抛出异常 |
entity |
T | 实体对象 |
mapper |
Function<? super Object, V> | 转换函数 |
List
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
参数说明
参数名 | 类型 | 描述 |
queryWrapper |
Wrapper<T> | 实体对象封装操作类 QueryWrapper |
idList |
Collection<? extends Serializable> | 主键 ID 列表 |
columnMap |
Map<String, Object> | 表字段 map 对象 |
mapper |
Function<? super Object, V> | 转换函数 |
Page
// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
参数说明
参数名 | 类型 | 描述 |
page |
IPage<T> | 翻页对象 |
queryWrapper |
Wrapper<T> | 实体对象封装操作类 QueryWrapper |
Count
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
参数说明
参数名 | 类型 | 描述 |
queryWrapper | Wrapper<T> | 实体对象封装操作类 QueryWrapper |
Chain
query
// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery();
// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list();
update
// 链式更改 普通
UpdateChainWrapper<T> update();
// 链式更改 lambda 式。注意:不支持 Kotlin
LambdaUpdateChainWrapper<T> lambdaUpdate();
// 示例:
update().eq("column", value).remove();
lambdaUpdate().eq(Entity::getId, value).update(entity);
MyBatis-Plus CRUD接口-Mapper CRUD 接口
- 通用 CRUD 封装
BaseMapper
接口,为 Mybatis-Plus 启动时自动解析实体表关系映射转换为 Mybatis 内部对象注入容器 - 泛型
T
为任意实体对象 - 参数
Serializable
为任意类型主键, Mybatis-Plus 不推荐使用复合主键约定,每一张表都有自己的唯一 id 主键 - 对象
Wrapper
为 条件构造器
Insert
// 插入一条记录
int insert(T entity);
参数说明
参数名 | 类型 | 描述 |
entity | T | 实体对象 |
Delete
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
参数说明
参数名 | 类型 | 描述 |
wrapper |
Wrapper<T> | 实体对象封装操作类(可以为 null) |
idList |
Collection<? extends Serializable> | 主键 ID 列表(不能为 null 以及 empty) |
id |
Serializable | 主键 ID |
columnMap |
Map<String, Object> | 表字段 map 对象 |
Update
// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);
参数说明
参数名 | 类型 | 描述 |
entity |
T | 实体对象 (set 条件值,可为 null) |
updateWrapper |
Wrapper<T> | 实体对象封装操作类(可以为 null,里面的 entity 用于生成 where 语句) |
Select
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
参数说明
参数名 | 类型 | 描述 |
id |
Serializable | 主键 ID |
queryWrapper |
Wrapper<T> | 实体对象封装操作类(可以为 null) |
idList |
Collection<? extends Serializable> | 主键 ID 列表(不能为 null 以及 empty) |
columnMap |
Map<String, Object> | 表字段 map 对象 |
page |
IPage<T> | 分页查询条件(可以为 RowBounds.DEFAULT) |
MyBatis-Plus CRUD接口-SimpleQuery 工具类
- 对
selectList
查询后的结果用Stream
流进行了一些封装,使其可以返回一些指定结果,简洁了api的调用 - 需要项目中已注入对应实体的
BaseMapper
- 对于下方参数
peeks
,其类型为Consumer...
,可一直往后叠加操作例如:List<Long> ids = SimpleQuery.list(Wrappers.lambdaQuery(), Entity::getId, System.out::println, user -> userNames.add(user.getName()));
keyMap
// 查询表内记录,封装返回为Map<属性,实体>
Map<A, E> keyMap(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, Consumer<E>... peeks);
// 查询表内记录,封装返回为Map<属性,实体>,考虑了并行流的情况
Map<A, E> keyMap(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, boolean isParallel, Consumer<E>... peeks);
参数说明
参数名 | 类型 | 描述 |
entity |
E | 实体对象 |
attribute |
A | 实体属性类型,也是map中key的类型 |
wrapper |
LambdaQueryWrapper<E> | 支持lambda的条件构造器 |
sFunction |
SFunction<E, A> | 实体中属性的getter,用于封装后map中作为key的条件 |
isParallel |
boolean | 为true时底层使用并行流执行 |
peeks |
Consumer<E>... | 可叠加的后续操作 |
map
// 查询表内记录,封装返回为Map<属性,属性>
Map<A, P> map(LambdaQueryWrapper<E> wrapper, SFunction<E, A> keyFunc, SFunction<E, P> valueFunc, Consumer<E>... peeks);
// 查询表内记录,封装返回为Map<属性,属性>,考虑了并行流的情况
Map<A, P> map(LambdaQueryWrapper<E> wrapper, SFunction<E, A> keyFunc, SFunction<E, P> valueFunc, boolean isParallel, Consumer<E>... peeks);
参数说明
参数名 | 类型 | 描述 |
entity |
E | 实体对象 |
attribute |
A | 实体属性类型,也是map中key的类型 |
attribute |
P | 实体属性类型,也是map中value的类型 |
wrapper |
LambdaQueryWrapper<E> | 支持lambda的条件构造器 |
keyFunc |
SFunction<E, A> | 封装后map中作为key的条件 |
valueFunc |
SFunction<E, P> | 封装后map中作为value的条件 |
isParallel |
boolean | 为true时底层使用并行流执行 |
peeks |
Consumer<E>... | 可叠加的后续操作 |
group
// 查询表内记录,封装返回为Map<属性,List<实体>>
Map<K, List<T>> group(LambdaQueryWrapper<T> wrapper, SFunction<T, A> sFunction, Consumer<T>... peeks);
// 查询表内记录,封装返回为Map<属性,List<实体>>,考虑了并行流的情况
Map<K, List<T>> group(LambdaQueryWrapper<T> wrapper, SFunction<T, K> sFunction, boolean isParallel, Consumer<T>... peeks);
// 查询表内记录,封装返回为Map<属性,分组后对集合进行的下游收集器>
M group(LambdaQueryWrapper<T> wrapper, SFunction<T, K> sFunction, Collector<? super T, A, D> downstream, Consumer<T>... peeks);
// 查询表内记录,封装返回为Map<属性,分组后对集合进行的下游收集器>,考虑了并行流的情况
M group(LambdaQueryWrapper<T> wrapper, SFunction<T, K> sFunction, Collector<? super T, A, D> downstream, boolean isParallel, Consumer<T>... peeks);
参数说明
参数名 | 类型 | 描述 |
entity |
T | 实体对象 |
attribute |
K | 实体属性类型,也是map中key的类型 |
- |
D | 下游收集器返回类型,也是map中value的类型 |
- |
A | 下游操作中间类型 |
- |
M | 最终结束返回的Map<K, D> |
wrapper |
LambdaQueryWrapper<E> | 支持lambda的条件构造器 |
sFunction |
SFunction<E, A> | 分组依据,封装后map中作为key的条件 |
downstream |
Collector<T, A, D> | 下游收集器 |
isParallel |
boolean | 为true时底层使用并行流执行 |
peeks |
Consumer<T>... | 可叠加的后续操作 |
list
// 查询表内记录,封装返回为List<属性>
List<A> list(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, Consumer<E>... peeks);
// 查询表内记录,封装返回为List<属性>,考虑了并行流的情况
List<A> list(LambdaQueryWrapper<E> wrapper, SFunction<E, A> sFunction, boolean isParallel, Consumer<E>... peeks);
参数说明
参数名 | 类型 | 描述 |
entity |
E | 实体对象 |
attribute |
A | 实体属性类型,也是list中元素的类型 |
wrapper |
LambdaQueryWrapper<E> | 支持lambda的条件构造器 |
sFunction |
SFunction<E, A> | 封装后list中的元素 |
isParallel |
boolean | 为true时底层使用并行流执行 |
peeks |
Consumer<E>... | 可叠加的后续操作 |
MyBatis-Plus 条件构造器-AbstractWrapper
说明
QueryWrapper
(LambdaQueryWrapper) 和 UpdateWrapper
(LambdaUpdateWrapper) 的父类
用于生成 sql 的 where 条件, entity 属性也用于生成 sql 的 where 条件
注意: entity 生成的 where 条件与 使用各个 api 生成的 where 条件没有任何关联行为
MyBatis-Plus 条件构造器-QueryWrapper
说明:
继承自 AbstractWrapper
,自身的内部属性 entity
也用于生成 where
条件
及 LambdaQueryWrapper
, 可以通过 new QueryWrapper().lambda()
方法获取
select
select(String... sqlSelect)
select(Predicate<TableFieldInfo> predicate)
select(Class<T> entityClass, Predicate<TableFieldInfo> predicate)
- 设置查询字段
说明:
以上方法分为两类.
第二类方法为:过滤查询字段(主键除外),入参不包含 class
的调用前需要wrapper
内的entity
属性有值! 这两类方法重复调用以最后一次为准
- 例:
select("id", "name", "age")
- 例:
select(i -> i.getProperty().startsWith("test"))
MyBatis-Plus 条件构造器-UpdateWrapper
说明:
继承自 AbstractWrapper
,自身的内部属性 entity
也用于生成 where
条件
及 LambdaUpdateWrapper
, 可以通过 new UpdateWrapper().lambda()
方法获取!
set
set(String column, Object val)
set(boolean condition, String column, Object val)
- SQL SET 字段
- 例:
set("name", "老李头")
- 例:
set("name", "")
--->数据库字段值变为空字符串 - 例:
set("name", null)
--->数据库字段值变为null
setSql
setSql(String sql)
- 设置 SET 部分 SQL
- 例:
setSql("name = '老李头'")
lambda
- 获取
LambdaWrapper
- 在
QueryWrapper
中是获取LambdaQueryWrapper
- 在
UpdateWrapper
中是获取LambdaUpdateWrapper
MyBatis-Plus 条件构造器-使用 Wrapper 自定义SQL
注意事项
需要mybatis-plus
版本 >= 3.0.7
param
参数名要么叫ew
,要么加上注解@Param(Constants.WRAPPER)
使用${ew.customSqlSegment}
不支持 Wrapper
内的entity生成where语句
kotlin持久化对象定义最佳实践
由于kotlin
相比于java
多了数据对象(data class
),在未说明情况下可能会混用。建议按照以下形式定义持久化对象
@TableName("sys_user")
class User {
@TableId(type = IdType.AUTO)
var id: Int? = null
@TableField("username")
var name: String? = null
var roleId: Int? = null
}
注意:这里的TableId
及TableField
并非必要,只是为了展示Mybatis-Plus
中的annotation
使用
这里所有成员都需要定义为可空类型(?
),并赋予null
的初始值,方便我们在以下场景中使用(类似java中的updateSelective
)
val wrapper = KtUpdateWrapper(User::class.java).eq(User::id, 2)
val newRecord = User()
newRecord.name = "newName"
userMapper!!.update(newRecord, wrapper)
不建议使用data class
及全参数构造方法,这样我们会写很多不必要的null
来构造一个空对象
用注解
@Select("select * from mysql_data ${ew.customSqlSegment}")
List<MysqlData> getAll(@Param(Constants.WRAPPER) Wrapper wrapper);
用XML
List<MysqlData> getAll(Wrapper ew);
<select id="getAll" resultType="MysqlData">
SELECT * FROM mysql_data ${ew.customSqlSegment}
</select>
kotlin使用wrapper
kotlin 可以使用 QueryWrapper
和 UpdateWrapper
但无法使用 LambdaQueryWrapper
和 LambdaUpdateWrapper
如果想使用 lambda 方式的 wrapper 请使用 KtQueryWrapper
和 KtUpdateWrapper
val queryWrapper = KtQueryWrapper(User()).eq(User::name, "sss").eq(User::roleId, "sss2")
userMapper!!.selectList(queryWrapper)
val updateConditionWrapper = KtUpdateWrapper(User()).eq(User::name, "sss").eq(User::roleId, "sss2")
val updateRecord = User()
updateRecord.name = "newName"
userMapper!!.update(updateRecord, updateConditionWrapper)
val updateRecord = User()
updateRecord.id = 2
updateRecord.name = "haha"
userMapper.updateById(updateRecord)
链式调用 lambda 式
// 区分:
// 链式调用 普通
UpdateChainWrapper<T> update();
// 链式调用 lambda 式。注意:不支持 Kotlin
LambdaUpdateChainWrapper<T> lambdaUpdate();
// 等价示例:
query().eq("id", value).one();
lambdaQuery().eq(Entity::getId, value).one();
// 等价示例:
update().eq("id", value).remove();
lambdaUpdate().eq(Entity::getId, value).remove();
MyBatis-Plus 核心功能-主键策略
提示
主键生成策略必须使用 INPUT
支持父类定义 @KeySequence
子类继承使用
支持主键类型指定(3.3.0 开始自动识别主键类型)
内置支持:
-
DB2KeyGenerator
-
H2KeyGenerator
-
KingbaseKeyGenerator
-
OracleKeyGenerator
-
PostgreKeyGenerator
如果内置支持不满足你的需求,可实现 IKeyGenerator
接口来进行扩展.
举个例子:
@KeySequence(value = "SEQ_ORACLE_STRING_KEY", clazz = String.class)
public class YourEntity {
@TableId(value = "ID_STR", type = IdType.INPUT)
private String idStr;
}
Spring-Boot
方式一:使用配置类
@Bean
public IKeyGenerator keyGenerator() {
return new H2KeyGenerator();
}
方式二:通过 MybatisPlusPropertiesCustomizer 自定义
@Bean
public MybatisPlusPropertiesCustomizer plusPropertiesCustomizer() {
return plusProperties -> plusProperties.getGlobalConfig().getDbConfig().setKeyGenerator(new H2KeyGenerator());
}
Spring
方式一: XML 配置
<bean id="globalConfig" class="com.baomidou.mybatisplus.core.config.GlobalConfig">
<property name="dbConfig" ref="dbConfig"/>
</bean>
<bean id="dbConfig" class="com.baomidou.mybatisplus.core.config.GlobalConfig.DbConfig">
<property name="keyGenerator" ref="keyGenerator"/>
</bean>
<bean id="keyGenerator" class="com.baomidou.mybatisplus.extension.incrementer.H2KeyGenerator"/>
方式二:注解配置
@Bean
public GlobalConfig globalConfig() {
GlobalConfig conf = new GlobalConfig();
conf.setDbConfig(new GlobalConfig.DbConfig().setKeyGenerator(new H2KeyGenerator()));
return conf;
}
MyBatis-Plus 核心功能-自定义ID生成器
提示
自 3.3.0 开始,默认使用雪花算法+UUID
(不含中划线)
自定义示例工程:
- spring-boot 示例 :传送门
方法 | 主键生成策略 | 主键类型 | 说明 |
nextId |
ASSIGN_ID, ID_WORKER, ID_WORKER_STR |
Long, Integer, String |
支持自动转换为 String 类型,但数值类型不支持自动转换,需精准匹配,例如返回 Long,实体主键就不支持定义为 Integer |
nextUUID |
ASSIGN_UUID,UUID | String | 默认不含中划线的 UUID 生成 |
Spring-Boot
方式一:声明为 Bean 供 Spring 扫描注入
@Component
public class CustomIdGenerator implements IdentifierGenerator {
@Override
public Long nextId(Object entity) {
//可以将当前传入的class全类名来作为bizKey,或者提取参数来生成bizKey进行分布式Id调用生成.
String bizKey = entity.getClass().getName();
//根据bizKey调用分布式ID生成
long id = ....;
//返回生成的id值即可.
return id;
}
}
方式二:使用配置类
@Bean
public IdentifierGenerator idGenerator() {
return new CustomIdGenerator();
}
方式三:通过 MybatisPlusPropertiesCustomizer 自定义
@Bean
public MybatisPlusPropertiesCustomizer plusPropertiesCustomizer() {
return plusProperties -> plusProperties.getGlobalConfig().setIdentifierGenerator(new CustomIdGenerator());
}
Spring
方式一: XML 配置
<bean name="customIdGenerator" class="com.baomidou.samples.incrementer.CustomIdGenerator"/>
<bean id="globalConfig" class="com.baomidou.mybatisplus.core.config.GlobalConfig">
<property name="identifierGenerator" ref="customIdGenerator"/>
</bean>
方式二:注解配置
@Bean
public GlobalConfig globalConfig() {
GlobalConfig conf = new GlobalConfig();
conf.setIdentifierGenerator(new CustomIdGenerator());
return conf;
}
MyBatis-Plus 扩展-逻辑删除
说明:
只对自动注入的 sql 起效:
- 插入: 不作限制
- 查找: 追加
where
条件过滤掉已删除数据,且使用 wrapper.entity
生成的 where 条件会忽略该字段 - 更新: 追加
where
条件防止更新到已删除数据,且使用 wrapper.entity 生成的 where 条件会忽略该字段 - 删除: 转变为 更新
例如:
- 删除:
update user set deleted=1 where id = 1 and deleted=0
- 查找:
select id,name,deleted from user where deleted=0
字段类型支持说明:
- 支持所有数据类型(推荐使用
Integer
,Boolean
,LocalDateTime
) - 如果数据库字段使用
datetime
,逻辑未删除值和已删除值支持配置为字符串null
,另一个值支持配置为函数来获取值如now()
附录:
- 逻辑删除是为了方便数据恢复和保护数据本身价值等等的一种方案,但实际就是删除。
- 如果你需要频繁查出来看就不应使用逻辑删除,而是以一个状态去表示。
使用方法
步骤 1
- 配置
com.baomidou.mybatisplus.core.config.GlobalConfig$DbConfig
例如: application.yml
mybatis-plus:
global-config:
db-config:
logic-delete-field: flag # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
步骤 2
- 实体类字段上加上
@TableLogic
注解
@TableLogic
private Integer deleted;
常见问题
1. 如何 insert ?
- 字段在数据库定义默认值(推荐)
-
insert
前自己 set
值 - 使用 自动填充功能
2. 删除接口自动填充功能失效
- 使用
update
方法并: UpdateWrapper.set(column, value)
(推荐) - 使用
update
方法并: UpdateWrapper.setSql("column=value")
- 使用 Sql 注入器 注入
com.baomidou.mybatisplus.extension.injector.methods.LogicDeleteByIdWithFill
并使用(推荐)
MyBatis-Plus 扩展-通用枚举
解决了繁琐的配置,让 mybatis 优雅的使用枚举属性!
自3.1.0开始,如果你无需使用原生枚举,可配置默认枚举来省略扫描通用枚举配置
- 升级说明:
3.1.0 以下版本改变了原生默认行为,升级时请将默认枚举设置为EnumOrdinalTypeHandler
- 影响用户:
实体中使用原生枚举
- 其他说明:
配置枚举包扫描的时候能提前注册使用注解枚举的缓存
1、声明通用枚举属性
- 方式一: 使用
@EnumValue
注解枚举属性
public enum GradeEnum {
PRIMARY(1, "小学"), SECONDORY(2, "中学"), HIGH(3, "高中");
GradeEnum(int code, String descp) {
this.code = code;
this.descp = descp;
}
@EnumValue//标记数据库存的值是code
private final int code;
//。。。
}
- 方式二: 枚举属性,实现
IEnum
接口如下:
public enum AgeEnum implements IEnum<Integer> {
ONE(1, "一岁"),
TWO(2, "二岁"),
THREE(3, "三岁");
private int value;
private String desc;
@Override
public Integer getValue() {
return this.value;
}
}
- 实体属性使用枚举类型
public class User {
/**
* 名字
* 数据库字段: name varchar(20)
*/
private String name;
/**
* 年龄,IEnum接口的枚举处理
* 数据库字段:age INT(3)
*/
private AgeEnum age;
/**
* 年级,原生枚举(带{@link com.baomidou.mybatisplus.annotation.EnumValue}):
* 数据库字段:grade INT(2)
*/
private GradeEnum grade;
}
2、配置扫描通用枚举
- 配置文件
resources/application.yml
mybatis-plus:
# 支持统配符 * 或者 ; 分割
typeEnumsPackage: com.baomidou.springboot.entity.enums
....
- 自定义配置类
MybatisPlusAutoConfiguration
@Configuration
public class MybatisPlusAutoConfiguration {
@Bean
public MybatisPlusPropertiesCustomizer mybatisPlusPropertiesCustomizer() {
return properties -> {
GlobalConfig globalConfig = properties.getGlobalConfig();
globalConfig.setBanner(false);
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setDefaultEnumTypeHandler(MybatisEnumTypeHandler.class);
properties.setConfiguration(configuration);
};
}
}
如何序列化枚举值为数据库存储值?
Jackson
- 重写
toString
方法
springboot:
@Bean
public Jackson2ObjectMapperBuilderCustomizer customizer(){
return builder -> builder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
}
jackson:
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.WRITE_ENUMS_USING_TO_STRING, true);
以上两种方式任选其一,然后在枚举中复写 toString
方法即可。
- 注解处理
public enum GradeEnum {
PRIMARY(1, "小学"), SECONDORY(2, "中学"), HIGH(3, "高中");
GradeEnum(int code, String descp) {
this.code = code;
this.descp = descp;
}
@EnumValue
@JsonValue //标记响应json值
private final int code;
}
Fastjson
- 重写
toString
方法
全局处理方式
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(SerializerFeature.WriteEnumUsingToString);
局部处理方式
@JSONField(serialzeFeatures= SerializerFeature.WriteEnumUsingToString)
private UserStatus status;
以上两种方式任选其一,然后在枚举中复写 toString
方法即可。
MyBatis-Plus 扩展-字段类型处理器
类型处理器,用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement
设置参数值和从 ResultSet
或 CallableStatement
中取出一个值,本文讲解 mybaits-plus 内置常用类型处理器如何通过TableField注解快速注入到 mybatis 容器中。
- JSON 字段类型
@Data
@Accessors(chain = true)
@TableName(autoResultMap = true)
public class User {
private Long id;
...
/**
* 注意!! 必须开启映射注解
*
* @TableName(autoResultMap = true)
*
* 以下两种类型处理器,二选一 也可以同时存在
*
* 注意!!选择对应的 JSON 处理器也必须存在对应 JSON 解析依赖包
*/
@TableField(typeHandler = JacksonTypeHandler.class)
// @TableField(typeHandler = FastjsonTypeHandler.class)
private OtherInfo otherInfo;
}
该注解对应了 XML 中写法为
<result column="other_info" jdbcType="VARCHAR" property="otherInfo" typeHandler="com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler" />
MyBatis-Plus 扩展-自动填充功能
原理:
- 实现元对象处理器接口:
com.baomidou.mybatisplus.core.handlers.MetaObjectHandler
- 注解填充字段
@TableField(.. fill = FieldFill.INSERT)
生成器策略部分也可以配置!
public class User {
// 注意!这里需要标记为填充字段
@TableField(.. fill = FieldFill.INSERT)
private String fillField;
....
}
- 自定义实现类
MyMetaObjectHandler
@Slf4j
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
log.info("start insert fill ....");
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐使用)
// 或者
this.strictInsertFill(metaObject, "createTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "createTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
@Override
public void updateFill(MetaObject metaObject) {
log.info("start update fill ....");
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now()); // 起始版本 3.3.0(推荐)
// 或者
this.strictUpdateFill(metaObject, "updateTime", () -> LocalDateTime.now(), LocalDateTime.class); // 起始版本 3.3.3(推荐)
// 或者
this.fillStrategy(metaObject, "updateTime", LocalDateTime.now()); // 也可以使用(3.3.0 该方法有bug)
}
}
注意事项:
- 填充原理是直接给
entity
的属性设置值!!! - 注解则是指定该属性在对应情况下必有值,如果无值则入库会是
null
-
MetaObjectHandler
提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为null
则不填充 - 字段必须声明
TableField
注解,属性fill选择对应策略,该声明告知Mybatis-Plus需要预留注入SQL字段 - 填充处理器
MyMetaObjectHandler
在 Spring Boot 中需要声明@Component
或@Bean
注入 - 要想根据注解
FieldFill.xxx
和字段名以及字段类型来区分必须使用父类的strictInsertFill
或者strictUpdateFill
方法 - 不需要根据任何来区分可以使用父类的
fillStrategy
方法 -
update(T t,Wrapper updateWrapper)
时t
不能为空,否则自动填充失效
public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入填充字段
*/
INSERT,
/**
* 更新填充字段
*/
UPDATE,
/**
* 插入和更新填充字段
*/
INSERT_UPDATE
}
MyBatis-Plus 扩展-SQL注入器
注入器配置
全局配置 sqlInjector
用于注入 ISqlInjector
接口的子类,实现自定义方法注入。
参考默认注入器 DefaultSqlInjector
- SQL 自动注入器接口
ISqlInjector
public interface ISqlInjector {
/**
* <p>
* 检查SQL是否注入(已经注入过不再注入)
* </p>
*
* @param builderAssistant mapper 信息
* @param mapperClass mapper 接口的 class 对象
*/
void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass);
}
自定义自己的通用方法可以实现接口 ISqlInjector
也可以继承抽象类 AbstractSqlInjector
注入通用方法 SQL 语句 然后继承 BaseMapper
添加自定义方法,全局配置 sqlInjector
注入 MP
会自动将类所有方法注入到 mybatis
容器中。
MyBatis-Plus 扩展-执行SQL分析打印
该功能依赖 p6spy
组件,完美的输出打印 SQL 及执行时长 3.1.0 以上版本
示例工程:
-
p6spy
依赖引入
Maven:
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>最新版本</version>
</dependency>
Gradle:
compile group: 'p6spy', name: 'p6spy', version: '最新版本'
-
application.yml
配置:
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:h2:mem:test
...
-
spy.properties
配置:
#3.2.1以上使用
modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
#3.2.1以下使用或者不配置
#modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
# 自定义日志打印
logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
#日志输出到控制台
appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
# 使用日志系统记录 sql
#appender=com.p6spy.engine.spy.appender.Slf4JLogger
# 设置 p6spy driver 代理
deregisterdrivers=true
# 取消JDBC URL前缀
useprefix=true
# 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
excludecategories=info,debug,result,commit,resultset
# 日期格式
dateformat=yyyy-MM-dd HH:mm:ss
# 实际驱动可多个
#driverlist=org.h2.Driver
# 是否开启慢SQL记录
outagedetection=true
# 慢SQL记录标准 2 秒
outagedetectioninterval=2
注意!
-
driver-class-name
为 p6spy
提供的驱动类 -
url
前缀为 jdbc:p6spy
跟着冒号为对应数据库连接地址 - 打印出 sql 为
null
,在 excludecategories
增加 commit
- 批量操作不打印 sql,去除
excludecategories
中的 batch
- 批量操作打印重复的问题请使用
MybatisPlusLogFactory
(3.2.1 新增) - 该插件有性能损耗,不建议生产环境使用。
MyBatis-Plus 扩展-数据安全保护
该功能为了保护数据库配置及数据安全,在一定的程度上控制开发人员流动导致敏感信息泄露。
- 3.3.2 开始支持
- 配置安全
YML 配置:
// 加密配置 mpw: 开头紧接加密内容( 非数据库配置专用 YML 中其它配置也是可以使用的 )
spring:
datasource:
url: mpw:qRhvCwF4GOqjessEB3G+a5okP+uXXr96wcucn2Pev6Bf1oEMZ1gVpPPhdDmjQqoM
password: mpw:Hzy5iliJbwDHhjLs1L0j6w==
username: mpw:Xb+EgsyuYRXw7U7sBJjBpA==
密钥加密:
// 生成 16 位随机 AES 密钥
String randomKey = AES.generateRandomKey();
// 随机密钥加密
String result = AES.encrypt(data, randomKey);
如何使用:
// Jar 启动参数( idea 设置 Program arguments , 服务器可以设置为启动环境变量 )
--mpw.key=d1104d7c3b616f0b
注意!
- 加密配置必须以
mpw:
字符串开头 - 随机密钥请负责人妥善保管,当然越少人知道越好。
MyBatis-Plus 扩展-多数据源
简介
dynamic-datasource-spring-boot-starter
是一个基于springboot的快速集成多数据源的启动器。
其支持 Jdk 1.7+
, SpringBoot 1.4.x 1.5.x 2.x.x
文档
特性
- 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
- 支持数据库敏感配置信息 加密
ENC()
。 - 支持每个数据库独立初始化表结构
schema
和数据库database
。 - 支持无数据源启动,支持懒加载数据源(需要的时候再创建连接)。
- 支持 自定义注解 ,需继承
DS(3.2.0+)
。 - 提供并简化对
Druid
,HikariCp
,BeeCp
,Dbcp2
的快速集成。 - 提供对
Mybatis-Plus
,Quartz
,ShardingJdbc
,P6sy
,Jndi
等组件的集成方案。 - 提供 自定义数据源来源 方案(如全从数据库加载)。
- 提供项目启动后 动态增加移除数据源 方案。
- 提供Mybatis环境下的 纯读写分离 方案。
- 提供使用
spel
动态参数 解析数据源方案。内置spel
,session
,header
,支持自定义。 - 支持 多层数据源嵌套切换 。(ServiceA >>> ServiceB >>> ServiceC)。
- 提供 **基于seata的分布式事务方案。
- 提供 本地多数据源事务方案。
约定
- 本框架只做 切换数据源 这件核心的事情,并不限制你的具体操作,切换了数据源可以做任何
CRUD
。 - 配置文件所有以下划线
_
分割的数据源 首部 即为组的名称,相同组名称的数据源会放在一个组下。 - 切换数据源可以是组名,也可以是具体数据源名称。组名则切换时采用负载均衡算法切换。
- 默认的数据源名称为
master
,你可以通过 spring.datasource.dynamic.primary
修改。 - 方法上的注解优先于类上注解。
-
DS
支持继承抽象类上的DS
,暂不支持继承接口上的DS
。
使用方法
- 引入
dynamic-datasource-spring-boot-starter
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>${version}</version>
</dependency>
- 配置数据源
spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master:
url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
slave_1:
url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
slave_2:
url: ENC(xxxxx) # 内置加密,使用请查看详细文档
username: ENC(xxxxx)
password: ENC(xxxxx)
driver-class-name: com.mysql.jdbc.Driver
#......省略
#以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2
# 多主多从 纯粹多库(记得设置primary) 混合配置
spring: spring: spring:
datasource: datasource: datasource:
dynamic: dynamic: dynamic:
datasource: datasource: datasource:
master_1: mysql: master:
master_2: oracle: slave_1:
slave_1: sqlserver: slave_2:
slave_2: postgresql: oracle_1:
slave_3: h2: oracle_2:
- 使用
@DS
切换数据源
@DS
可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。
注解 | 结果 |
没有@DS | 默认数据源 |
@DS("dsName") | dsName可以为组名也可以为具体某个库的名称 |
@Service
@DS("slave")
public class UserServiceImpl implements UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public List selectAll() {
return jdbcTemplate.queryForList("select * from user");
}
@Override
@DS("slave_1")
public List selectByCondition() {
return jdbcTemplate.queryForList("select * from user where age >10");
}
}
MyBatis-Plus 插件-插件主体
注意
- 版本要求:3.4.0 版本以上
MybatisPlusInterceptor
该插件是核心插件,目前代理了 Executor#query
和 Executor#update
和 StatementHandler#prepare
方法
属性
-
private List interceptors = new ArrayList<>();
InnerInterceptor
我们提供的插件都将基于此接口来实现功能
目前已有的功能:
- 自动分页:
PaginationInnerInterceptor
- 多租户:
TenantLineInnerInterceptor
- 动态表名:
DynamicTableNameInnerInterceptor
- 乐观锁:
OptimisticLockerInnerInterceptor
- sql 性能规范:
IllegalSQLInnerInterceptor
- 防止全表更新与删除:
BlockAttackInnerInterceptor
注意:
使用多个功能需要注意顺序关系,建议使用如下顺序
- 多租户,动态表名
- 分页,乐观锁
- sql 性能规范,防止全表更新与删除
总结: 对 sql 进行单次改造的优先放入,不对 sql 进行改造的最后放入
使用方式(以分页插件举例)
spring
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<!-- 其他属性 略 -->
<property name="configuration" ref="configuration"/>
<property name="plugins">
<array>
<ref bean="mybatisPlusInterceptor"/>
</array>
</property>
</bean>
<bean id="configuration" class="com.baomidou.mybatisplus.core.MybatisConfiguration">
<!-- 需配置该值为false,避免1或2级缓存可能出现问题,该属性会在旧插件移除后一同移除 -->
<property name="useDeprecatedExecutor" value="false"/>
</bean>
<bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
<property name="interceptors">
<list>
<ref bean="paginationInnerInterceptor"/>
</list>
</property>
</bean>
<bean id="paginationInnerInterceptor" class="com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor">
<!-- 对于单一数据库类型来说,都建议配置该值,避免每次分页都去抓取数据库类型 -->
<constructor-arg name="dbType" value="H2"/>
</bean>
spring-boot
@Configuration
@MapperScan("scan.your.mapper.package")
public class MybatisPlusConfig {
/**
* 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
@Bean
public ConfigurationCustomizer configurationCustomizer() {
return configuration -> configuration.setUseDeprecatedExecutor(false);
}
}
mybatis-config.xml
<plugins>
<plugin interceptor="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
<property name="@page" value="com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor"/>
<property name="page:dbType" value="h2"/>
</plugin>
</plugins>
property
的配置说明详见 MybatisPlusInterceptor#setProperties
的源码方法注释
拦截忽略注解 @InterceptorIgnore
属性名 | 类型 | 默认值 | 描述 |
tenantLine | String | "" | 行级租户 |
dynamicTableName | String | "" | 动态表名 |
blockAttack | String | "" | 攻击 SQL 阻断解析器,防止全表更新与删除 |
illegalSql | String | "" | 垃圾 SQL 拦截 |
该注解作用于 xxMapper.java
方法之上 各属性代表对应的插件 各属性不给值则默认为 false
设置为 true
忽略拦截 更多说明详见源码注释
MyBatis-Plus 插件-分页插件
PaginationInnerInterceptor
支持的数据库
- mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb
- 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库
属性介绍
属性名 | 类型 | 默认值 | 描述 |
overflow | boolean | false | 溢出总页数后是否进行处理,默认不处理 |
maxLimit | Long | 单页分页条数限制,默认无限制 | |
dbType | DbType | 数据库类型,根据类型获取应使用的分页方言 | |
dialect | IDialect | 方言实现类 |
建议单一数据库类型的均设置 dbType
自定义的 mapper#method 使用分页
IPage<UserVo> selectPageVo(IPage<?> page, Integer state);
// or (class MyPage extends Ipage<UserVo>{ private Integer state; })
MyPage selectPageVo(MyPage page);
// or
List<UserVo> selectPageVo(IPage<UserVo> page, Integer state);
<select id="selectPageVo" resultType="xxx.xxx.xxx.UserVo">
SELECT id,name FROM user WHERE state=#{state}
</select>
- 如果返回类型是
IPage
则入参的 IPage
不能为null
,因为 返回的IPage ==
入参的IPage
- 如果返回类型是
List
则入参的 IPage
可以为 null
(为 null
则不分页),但需要你手动 入参的IPage.setRecords
(返回的 List
); - 如果
xml
需要从 page
里取值,需要 page.属性
获取
其他:
- 生成
countSql
会在 left join
的表不参与 where
条件的情况下,把 left join
优化掉 - 所以建议任何带有
left join
的sql,都写标准sql,即给于表一个别名,字段也要 别名.字段
MyBatis-Plus 插件-乐观锁插件
OptimisticLockerInnerInterceptor
当要更新一条记录的时候,希望这条记录没有被别人更新
乐观锁实现方式:
- 取出记录时,获取当前
version
- 更新时,带上这个
version
- 执行更新时,
set version = newVersion where version = oldVersion
- 如果
version
不对,就更新失败
乐观锁配置需要两步
1、配置插件
- spring xml 方式:
<bean class="com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor" id="optimisticLockerInnerInterceptor"/>
<bean id="mybatisPlusInterceptor" class="com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor">
<property name="interceptors">
<list>
<ref bean="optimisticLockerInnerInterceptor"/>
</list>
</property>
</bean>
- spring boot 注解方式:
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
2、在实体类的字段上加上@Version注解
@Version
private Integer version;
说明:
- 支持的数据类型只有:
int
,Integer
,long
,Long
,Date
,Timestamp
,LocalDateTime
- 整数类型下
newVersion = oldVersion + 1
-
newVersion
会回写到 entity
中 - 仅支持
updateById(id)
与 update(entity, wrapper)
方法 - 在
update(entity, wrapper)
方法下, wrapper
不能复用!!!
示例:
// Spring Boot 方式
@Configuration
@MapperScan("按需修改")
public class MybatisPlusConfig {
/**
* 旧版
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
/**
* 新版
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
mybatisPlusInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mybatisPlusInterceptor;
}
}
MyBatis-Plus 插件-多租户插件
TenantLineInnerInterceptor
示例工程:mybatis-plus-sample-tenant
属性介绍
属性名 | 类型 | 默认值 | 描述 |
tenantLineHandler |
TenantLineHandler | 租户处理器( TenantId 行级 ) |
public interface TenantLineHandler {
/**
* 获取租户 ID 值表达式,只支持单个 ID 值
* <p>
*
* @return 租户 ID 值表达式
*/
Expression getTenantId();
/**
* 获取租户字段名
* <p>
* 默认字段名叫: tenant_id
*
* @return 租户字段名
*/
default String getTenantIdColumn() {
return "tenant_id";
}
/**
* 根据表名判断是否忽略拼接多租户条件
* <p>
* 默认都要进行解析并拼接多租户条件
*
* @param tableName 表名
* @return 是否忽略, true:表示忽略,false:需要解析并拼接多租户条件
*/
default boolean ignoreTable(String tableName) {
return false;
}
}
说明:
- 多租户 != 权限过滤,不要乱用,租户之间是完全隔离的!!!
- 启用多租户后所有执行的
method
的sql都会进行处理. - 自写的sql请按规范书写(sql涉及到多个表的每个表都要给别名,特别是
inner join
的要写标准的 inner join
)
MyBatis-Plus 插件-防全表更新与删除插件
BlockAttackInnerInterceptor
针对 update
和 delete
语句,作用: 阻止恶意的全表更新删除
注入MybatisPlusInterceptor
类,并配置BlockAttackInnerInterceptor
拦截器
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}
测试示例(全表更新)
@SpringBootTest
public class QueryWrapperTest {
@Autowired
private UserService userService;
/**
+ SQL:UPDATE user SET name=?,email=?;
*/
@Test
public void test() {
User user = new User();
user.setId(999L);
user.setName("custom_name");
user.setEmail("xxx@mail.com");
// com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: Prohibition of table update operation
userService.saveOrUpdate(user, null);
}
}
测试示例(部分更新)
@SpringBootTest
public class QueryWrapperTest {
@Autowired
private UserService userService;
/**
+ SQL:UPDATE user SET name=?, email=? WHERE id = ?;
*/
@Test
public void test() {
LambdaUpdateWrapper<User> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(User::getId, 1);
User user = new User();
user.setId(10L);
user.setName("custom_name");
user.setEmail("xxx@mail.com");
userService.saveOrUpdate(user, wrapper);
}}
MyBatis-Plus 插件-动态表名插件
DynamicTableNameInnerInterceptor
简单示例:mybatis-plus-sample-dynamic-tablename
注意事项:
- 原理为解析替换设定表名为处理器的返回表名,表名建议可以定义复杂一些避免误替换
- 例如:真实表名为
user
设定为 mp_dt_user
处理器替换为 user_2019
等