Mybatis Plus学习笔记
Mybatis Plus
学习MyBatis-Plus之前要先学Mybatis –> Spring —> Spring MVC
为什么要学它?Mybatis Plus可以节省我们大量的时间,所有CRUD代码都可以自动完成,JPA,tk-mapper,Mybatis Plus
1、简介
什么是Mybatis Plus?
Mybatis本来就是简化JDBC操作的!MyBatis Plus(简称 MP),是一个 MyBatis (opens new window)的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
愿景
我们的愿景是成为 MyBatis 最好的搭档,就像
魂斗罗中的 1P、2P,基友搭配,效率翻倍。
官网:MyBatis-Plus (baomidou.com)
支持数据库
任何能使用
MyBatis进行 CRUD, 并且支持标准 SQL 的数据库,具体支持情况如下,如果不在下列表查看分页部分教程 PR 您的支持。
- MySQL,Oracle,DB2,H2,HSQL,SQLite,PostgreSQL,SQLServer,Phoenix,Gauss ,ClickHouse,Sybase,OceanBase,Firebird,Cubrid,Goldilocks,csiidb
- 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库
框架结构

2、特性
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 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 操作智能分析阻断,也可自定义拦截规则,预防误操作
1、快速入门
地址https://baomidou.com/pages/226c21/#初始化工程
使用第三方插件:
- 导入对应的依赖
- 研究依赖如何配置
- 代码如何编写
- 提高扩展技术能力
步骤
1.创建数据库 mybatis_plus
2.创建数据库
创建表
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)
);
--真实开发环境中,一般都会添加以下:version(乐观锁),deleted(逻辑删除),gmt_create,gmt_modified
插入数据
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');
3.编写项目,初始化项目! 使用SpringBoot初始化!

4.导入依赖
<dependencies>
<!-- spring-boot-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- spring-boot-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- mybatis plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.11</version>
</dependency>
</dependencies>
说明:使用Mybatis-plus可以节省我们大量的代码,尽量不要同时导入Mybatis和Mybatis-plus避免版本冲突!
5.连接数据库!这一步和mybatis相同!
application.yml
server:
port: 8888
# mysql 8 驱动不同 com.mysql.cj.jdbc.Driver 需要增加时区的配置 serverTimezone=UTC
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.1.104:3306/mybatis-plus?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
# mysql 5 驱动不同 com.mysql.jdbc.Driver
#spring:
# datasource:
# username: root
# password: 123456
# url: jdbc:mysql://192.168.1.104:3306/mybatis-plus?useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false
# driver-class-name: com.mysql.jdbc.Driver
查看驱动版本(注:高版本可兼容低版本,需要配置时区,否则无法运行)

6.传统的方式pojo-mapper(连接mybatis,配置mapper.xml文件)-service-controller
使用了mybatis-plus之后
-
pojo
@Data @AllArgsConstructor @NoArgsConstructor public class User { private Long id; //对应包装类 private String name; private Integer age; private String email; } -
mapper接口
UserMapper
//在对应的mapper上面继承基本的类 BaseMapper @Mapper//不写需要在启动类扫描Mapper @Repository//代表持久层 public interface UserMapper extends BaseMapper<User> { //所有CRUD操作都已经编写完成了 //你不需要向以前一样配置一大堆文件了! }注意点:不写@Mapper注解需要在主启动类
MybatisPlusProjectApplication上扫描我们Mapper包下的所有接口MybatisPlusProjectApplication
@SpringBootApplication @MapperScan("com.mian.mapper")//Mapper接口添加@Mapper此处可省略 public class MybatisPlusProjectApplication { public static void main(String[] args) { SpringApplication.run(MybatisPlusProjectApplication.class, args); } } -
测试类中进行测试
@SpringBootTest public class MybatisPlusProjectTest { //继承了BaseMapper, 所有的方法都来自父类 //我们也可以编写自己的扩展方法 private UserMapper userMapper; @Autowired public MybatisPlusProjectTest(UserMapper userMapper) { this.userMapper = userMapper; } @Test void contextLoads() { //参数是一个Wrapper , 条件构造器,这里我们先不用,先写为null //查询全部用户 List<User> users = userMapper.selectList(null); users.forEach(user -> { System.out.println(user); }); } } -
查询结果

思考问题
- sql是谁帮我们写的?—mybatis-plus
- 方法是谁帮我们写的?—mybatis-plus
2、配置日志
我们所有的sql是不可见的,我们希望知道他是怎么执行的,所以我们必须看日志!
application.yml
server:
port: 8888
# mysql 8 驱动不同 com.mysql.cj.jdbc.Driver 需要增加时区的配置 serverTimezone=UTC
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://192.168.1.104:3306/mybatis-plus?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false
driver-class-name: com.mysql.cj.jdbc.Driver
# mysql 5 驱动不同 com.mysql.jdbc.Driver
#spring:
# datasource:
# username: root
# password: 123456
# url: jdbc:mysql://192.168.1.104:3306/mybatis-plus?useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false
# driver-class-name: com.mysql.jdbc.Driver
# 配置日志 (默认控制台输出)
mybatis-plus:
configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

配置完日志之后你会喜欢上mybatis-plus!
3、CRUD扩展
1、插入数据
@Test
public void insertTest() {
try {
User user = User.class.newInstance();
user.setName("张三");
user.setAge(22);
user.setEmail("123456@email.com");
int result = userMapper.insert(user);//id自动生成
System.out.println(result);
List<User> users = userMapper.selectList(null);
users.forEach(userResult -> {
System.out.println(userResult);
});
} catch (Exception e) {
System.out.println(e.getMessage());
}
}

注意点:数据库插入的id默认值为:全局的唯一id @TableId(type = IdType.ID_WORKER) //默认全局唯一id ,不加默认生成
此处使用为@TableId(type = IdType.ASSIGN_ID),新版本取消了ID_WORKER,默认使用雪花算法生成
2、主键生成策略 @TableId对应主键
分布式系统唯一id生成:https://www.cnblogs.com/haoxinyue/p/5208136.html
雪花算法😦Twitter的snowflake算法
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0,可以保证几乎全球唯一
默认全局唯一
(过时,已取消,推荐使用雪花算法)
@TableId(type = IdType.ID_WORKER) //默认全局唯一id ,不加默认生成
主键自增
我们需要配置主键自增:
- 实体类字段上@TableId(type = IdType.AUTO)
- 数据库字段一定要是自增!

再次插入
@Test
public void insertTest2() {
try {
User user = User.class.newInstance();
user.setName("李四");
user.setAge(23);
user.setEmail("123456@email.com");
int result = userMapper.insert(user);//id自动生成
System.out.println(result);
List<User> users = userMapper.selectList(null);
users.forEach(userResult -> {
System.out.println(userResult);
});
} catch (Exception e) {
System.out.println(e.getMessage());
}
}

其余源码解释
public enum IdType {
AUTO(0), //数据库自增ID
NONE(1), //该类型为未设置主键类型
INPUT(2), //用户输入ID
//该类型可以通过自己注册自动填充插件进行
//以下类型、只有当插入对象ID 为空,才自动填充。
ASSIGN_ID(3), //全局唯一ID(雪花算法) 推荐使用 可排序
ASSIGN_UUID(4); //全局唯一ID(UUID) 不推荐 无序
//ID_WORKER(3),//全局唯一ID (idWorker)新版已被取消,使用雪花算法
3、更新数据
注意:updateById()参数是 一个对象!
@Test
public void updateTest() {
try {
User user = User.class.newInstance();
user.setId(1565862102255124483L);
user.setName("王五");
user.setAge(28);
user.setEmail("111111@gmail.com");
int result = userMapper.updateById(user);
System.out.println(result);
List<User> users = userMapper.selectList(null);
users.forEach(userResult -> {
System.out.println(userResult);
});
} catch (Exception e) {
System.out.println(e.getMessage());
}
}

所有的sql都是动态帮你配置的
4、自动填充
创建时间,修改时间! 这些个操作都是自动化完成的,我们不希望手动更新!
阿里巴巴开发手册:所有的数据库表:gmt_create .gmt_modified几乎所有的表都要配置上!而且需要自动化!
方式一:数据库级别(工作中不建议)
1.在表中新增字段 create_time , update_time

2.再次测试插入方法,我们需要先把实体类同步
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id; //对应包装类
private String name;
private Integer age;
private String email;
private Date createTime;
private Date updateTime;
}
再次更新查看结果即可
@Test
public void updateTest() {
try {
User user = User.class.newInstance();
user.setId(1565862102255124483L);
user.setName("王五");
user.setAge(28);
user.setEmail("111111@gmail.com");
int result = userMapper.updateById(user);
System.out.println(result);
List<User> users = userMapper.selectList(null);
users.forEach(userResult -> {
System.out.println(userResult);
});
} catch (Exception e) {
System.out.println(e.getMessage());
}
}

方式二:代码级别
不是所有人都能直接操作数据库
1.删除数据库默认值

2.实体类字段属性上添加注解 @TableField
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id; //对应包装类
private String name;
private Integer age;
private String email;
//记住用util包下的Date!!
//字段添加填充内容 在插入的时候字段填充
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
//在插入和更新的时候字段填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
3.编写处理器来处理这个注解
建立一个handler包,在包下建立自己的MyMetaObjectHandler

// 需要继承MetaObjectHandler并且重写里面的两个方法insertFill,updateFill
@Log4j2 //日志
@Configuration //加入到IOC容器中
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)
}
}
4.测试插入
@Test
public void insertTest2() {
try {
User user = User.class.newInstance();
user.setName("李四");
user.setAge(23);
user.setEmail("123456@email.com");
int result = userMapper.insert(user);//id自动生成
System.out.println(result);
List<User> users = userMapper.selectList(null);
users.forEach(userResult -> {
System.out.println(userResult);
});
} catch (Exception e) {
System.out.println(e.getMessage());
}
}

5测试更新,观察时间即可!
5、乐观锁&悲观锁
乐观锁:顾名思义十分乐观,他总是认为不会出现问题,无论干什么都不去上锁!如果出现了问题,再次更新值测试
悲观锁:顾名思义十分悲观,他总是认为出现问题,无论干什么都会上锁!再去操作!
我们这里主要讲解 乐观锁机制!
乐观锁实现方式:
- 取出记录时,获取当前version
- 更新时,带上这个version
- 执行更新时,set version = newVersion where version = oldVersion
- 如果version不对,就更新失败
实现线程通信的安全
乐观锁:1、先查询,获得版本号 version = 1
-- A
update user set name = "张三", version = version + 1
where id = 2 and version = 1
--B线程抢先完成,这个时候version = 2,会导致A修改失败!
update user set name = "李四" , version = version + 1
where id = 2 and version = 1
测试
1、添加version字段

2、添加对应实体类字段
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id; //对应包装类
private String name;
private Integer age;
private String email;
@Version //乐观锁注解
private Integer version;
//记住用util包下的Date!!
//字段添加填充内容 在插入的时候字段填充
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
//在插入和更新的时候字段填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
3、注册组件
//可以将主启动类上的扫描包注解放到这里
@MapperScan("com.mian.mapper") //扫描mapper接口路径
@EnableTransactionManagement //自动管理事务
@Configuration //代表这是一个配置类
public class MybatisPlusConfig {
// 注册乐观锁插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
4、测试
首先执行查询

更新 注意version

@Test
public void versionTest2() {
try {
//线程1
User user1 = userMapper.selectById(1565862102255124489L);
user1.setName("赵六");
//线程2:模拟另一个线程执行插队操作
User user2 = userMapper.selectById(1565862102255124489L);
user1.setName("孙七");
userMapper.updateById(user2);
//在线程1执行更新操作之前,线程2抢先更新了,使得version变为了2,线程1更新失败,如果没有乐观锁就会覆盖插队线程的值
userMapper.updateById(user1);
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
6、查询操作
通过ID查询单条
@Test
public void queryByIdTest() {
User user = userMapper.selectById(1565862102255124489L);
System.out.println(user);
}

通过ID批量查询
@Test
public void queryByIdsTest() {
List<User> users = userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
users.forEach(System.out::println);
}

按条件查询 - 使用Map操作
@Test
public void queryByMapTest() {
Map<String, Object> map = new HashMap<>();
map.put("name", "Jack");
map.put("age", "20");
List<User> users = userMapper.selectByMap(map);
users.forEach(System.out::println);
}

7、分页查询
分页在网站使用的十分之多!
- 1、原始的limit进行分页
- 2、pageHelper第三方插件
- 3、mybatisPlus其实也内置了分页插件!
如何使用!
1、配置拦截器组件即可
//可以将主启动类上的扫描包注解放到这里
@MapperScan("com.mian.mapper") //扫描mapper接口路径
@EnableTransactionManagement //自动管理事务
@Configuration //代表这是一个配置类
public class MybatisPlusConfig {
// 注册乐观锁插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
/**
* 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
*/
//@Bean
//public ConfigurationCustomizer configurationCustomizer() {
// return configuration -> configuration.setUseDeprecatedExecutor(false);
//}
}
2、直接使用page对象即可
分页查询
@Test
public void queryPageTest() {
//参数一:查询的页面
//参数二:页面大小
Page<User> page = new Page<>(1, 3);
//需要两个参数:IPage<T> selectPage(IPage<T> var1, @Param("ew") Wrapper<T> var2);
page = userMapper.selectPage(page, null);
page.getRecords().forEach(System.out::println);
System.out.println(page.getTotal());
// page.hasPrevious();//是否有上一页
// page.hasNext();//是否有下一页
// page.getTotal();//得到总数
// page.getRecords();//得到记录
// page.getSize();//得到大小
// page.getCurrent();//得到参数
}

8、删除操作
通过id删除
@Test
public void deleteByIdTest() {
userMapper.deleteById(1565862102255124489L);
}
通过id批量删除
@Test
public void deleteByIdsTest() {
userMapper.deleteBatchIds(Arrays.asList(1, 2, 3));
}
通过条件删除
@Test
public void deleteByMapTest() {
HashMap<String, Object> map = new HashMap<>();
map.put("name", "赵六");
userMapper.deleteByMap(map);
}
9、逻辑删除
物理删除:从数据库中直接移除
逻辑删除: 在数据库中没有被移除,而是通过一个变量来让他失效!
管理员可以查看被删除的记录!防止数据的丢失,类似于回收站!
测试:
1.在数据表中增加一个deleted字段

2.实体类中增加属性
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Long id; //对应包装类
private String name;
private Integer age;
private String email;
@Version //乐观锁注解
private Integer version;
@TableLogic
private Integer deleted;
//记住用util包下的Date!!
//字段添加填充内容 在插入的时候字段填充
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
//在插入和更新的时候字段填充
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updateTime;
}
3.配置
旧版需要修改配置类
//逻辑删除
@Bean
public ISqlInjector sqlInjector(){
return new LogicSqlInjector();
}
新版本直接修改application.yml
mybatis-plus:
configuration.log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 0 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 1 # 逻辑未删除值(默认为 0)
测试删除
通过ID删除
@Test
public void deleteByIdTest() {
userMapper.deleteById(1565862102255124489L);
}
本质走的是更新操作,不是删除操作

测试查询
@Test
public void queryByIdTest() {
User user = userMapper.selectById(1565862102255124489L);
System.out.println(user);
}
查询时候会自动过滤删除的数据

4、性能分析插件
我们在平时的开发中,会遇到一些慢sql.
作用:性能分析拦截器,用于输出每条SQL语句及其执行时间
mybatisplus也提供了性能分析插件,如果超过这个时间就停止运行!
Mybatis Plus 3.2后已移除,需要自行使用第三方插件
3.2以前使用
1.导入插件
MybatisPlusConfig.java中添加如下配置
//性能分析
@Bean
@Profile({"dev","test"}) //设置dev 和 test环境开启
public PerformanceInterceptor performanceInterceptor(){
PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
performanceInterceptor.setMaxTime(100); // ms 设置sql执行的最大时间,超过不执行sql
performanceInterceptor.setFormat(true); // 开启sql的格式化支持
return performanceInterceptor;
}
记住在SpringBoot中配置环境为 dev或者test环境
application.yml中添加设置开发环境
# 设置开发环境 当前激活环境dev
spring:
profiles: active=dev
2.测试查询
@Test
void contextLoads() {
//参数是一个Wrapper , 条件构造器,这里我们先不用,先写为null
//查询全部用户
List<User> users = userMapper.selectList(null);
for (User user : users) {
System.out.println(user);
}
}


5、条件构造器
@Test
public void queryByQueryWrapper() {
// 查询name不为空的用户,并且邮箱不为空的用户,年龄大于等于12
// 查询一般用QueryWrapper
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.isNotNull(User::getName)
.isNotNull(User::getEmail)
.ge(User::getAge, 12);
List<User> users = userMapper.selectList(queryWrapper);// 和我们刚才学习的map对比一下
users.forEach(System.out::println);
}


@Test
public void queryByQueryWrapper2() {
// 查询name为Jack
// 查询一般用QueryWrapper
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getName, "Jack");
User user = userMapper.selectOne(queryWrapper);
System.out.println(user);
}
@Test
public void queryByQueryWrapper3() {
// 查询age在20~30之间的用户
// 查询一般用QueryWrapper
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.between(User::getAge, 20, 30);
Long count = userMapper.selectCount(queryWrapper);
System.out.println(count);
}
@Test
public void queryByQueryWrapper4() {
// 查询name中没有e的 且email以t为开头的
// 查询一般用QueryWrapper
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.notLike(User::getName, "e")
.likeRight(User::getEmail, "t");
List<Map<String, Object>> maps = userMapper.selectMaps(queryWrapper);
maps.forEach(System.out::println);
}
@Test
public void queryByQueryWrapper5() {
// 子查询
// 查询一般用QueryWrapper
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.inSql(User::getId, "select id from user where id < 3");
List<Object> objects = userMapper.selectObjs(queryWrapper);
objects.forEach(System.out::println);
}
@Test
public void queryByQueryWrapper6() {
// 排序
// 查询一般用QueryWrapper
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.orderByAsc(User::getName);
List<User> users = userMapper.selectList(queryWrapper);
users.forEach(System.out::println);
}
6、代码自动生成器
mapper、pojo、service、controller都自动生成!
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper.xml、Service、Controller 等各个模块的代码,极大的提升了开发效率。
导入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
只需要改实体类名字 和包名 还有 数据库配置即可
public class MianCode {
public static void main(String[] args) {
String projectPath = System.getProperty("user.dir");
String url = "jdbc:mysql://192.168.1.104:3306/mybatis-plus?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&allowPublicKeyRetrieval=true&verifyServerCertificate=false&useSSL=false";
String username = "root";
String password = "123456";
FastAutoGenerator
//数据库配置(DataSourceConfig)
.create(
new DataSourceConfig.Builder(url, username, password)//jdbc路径,数据库账号,数据库密码
//可选配置
.dbQuery(new MySqlQuery())//数据库查询
.schema("mybatis-plus")//数据库 schema(部分数据库适用)
.typeConvert(new MySqlTypeConvert())//数据库类型转换器
.keyWordsHandler(new MySqlKeyWordsHandler())//数据库关键字处理器
)
//全局配置(GlobalConfig)
.globalConfig(builder -> {
builder
.fileOverride()//覆盖已生成文件
.disableOpenDir()//禁止打开输出目录
.outputDir(projectPath + "/src/main/java") //指定输出目录
.author("mian")//设置作者
//.enableKotlin()//开启 kotlin 模式
.enableSwagger()//开启 swagger 模式
.dateType(DateType.TIME_PACK)//时间策略
.commentDate("yyyy-MM-dd")//时间策略
.build();
})
//包配置(PackageConfig)
.packageConfig(builder -> {
builder
.parent("com.mian") //父包名
.moduleName("sys") //父包模块名
.entity("pojo")//Entity 包名
.service("service")//Service 包名
.serviceImpl("service.impl")//Service 包名
.mapper("mapper")//Mapper 包名
.xml("mapper.xml")//Mapper XML 包名
.controller("controller")//Controller 包名
.other("other")//自定义文件包名
.pathInfo(Collections.singletonMap(OutputFile.xml, projectPath + "/src/main/resources/mybatis/mapper")); //设置mapperXml路径配置信息
})
/**
* 模板配置(TemplateConfig)
* 方法 说明 示例
* disable 禁用所有模板
* disable(TemplateType...) 禁用模板 TemplateType.ENTITY
* entity(String) 设置实体模板路径(JAVA) /templates/entity.java
* entityKt(String) 设置实体模板路径(kotlin) /templates/entity.java
* service(String) 设置 service 模板路径 /templates/service.java
* serviceImpl(String) 设置 serviceImpl 模板路径 /templates/serviceImpl.java
* mapper(String) 设置 mapper 模板路径 /templates/mapper.java
* mapperXml(String) 设置 mapperXml 模板路径 /templates/mapper.xml
* controller(String) 设置 controller 模板路径 /templates/controller.java
*/
.templateEngine(new FreemarkerTemplateEngine())//使用Freemarker引擎模板,默认的是Velocity引擎模板
/**
* 注入配置(InjectionConfig)
* 方法 说明 示例
* beforeOutputFile(BiConsumer<TableInfo, Map<String, Object>>) 输出文件之前消费者
* customMap(Map<String, Object>) 自定义配置 Map 对象 Collections.singletonMap("test", "baomidou")
* customFile(Map<String, String>) 自定义配置模板文件 Collections.singletonMap("test.txt", "/templates/test.vm")
*/
//策略配置(StrategyConfig)
/**
* 方法 说明 示例
* enableCapitalMode 开启大写命名 默认值:false
* enableSkipView 开启跳过视图 默认值:false
* disableSqlFilter 禁用 sql 过滤 默认值:true,语法不能支持使用 sql 过滤表的话,可以考虑关闭此开关
* enableSchema 启用 schema 默认值:false,多 schema 场景的时候打开
* likeTable(LikeTable) 模糊表匹配(sql 过滤) likeTable 与 notLikeTable 只能配置一项
* notLikeTable(LikeTable) 模糊表排除(sql 过滤) likeTable 与 notLikeTable 只能配置一项
* addInclude(String...) 增加表匹配(内存过滤) include 与 exclude 只能配置一项
* addExclude(String...) 增加表排除匹配(内存过滤) include 与 exclude 只能配置一项
* addTablePrefix(String...) 增加过滤表前缀
* addTableSuffix(String...) 增加过滤表后缀
* addFieldPrefix(String...) 增加过滤字段前缀
* addFieldSuffix(String...) 增加过滤字段后缀
* entityBuilder 实体策略配置
* controllerBuilder controller 策略配置
* mapperBuilder mapper 策略配置
* serviceBuilder service 策略配置
*/
/**
* Entity 策略配置
* 方法 说明 示例
* nameConvert(INameConvert) 名称转换实现
* superClass(Class<?>) 设置父类 BaseEntity.class
* superClass(String) 设置父类 com.baomidou.global.BaseEntity
* disableSerialVersionUID 禁用生成 serialVersionUID 默认值:true
* enableColumnConstant 开启生成字段常量 默认值:false
* enableChainModel 开启链式模型 默认值:false
* enableLombok 开启 lombok 模型 默认值:false
* enableRemoveIsPrefix 开启 Boolean 类型字段移除 is 前缀 默认值:false
* enableTableFieldAnnotation 开启生成实体时生成字段注解 默认值:false
* enableActiveRecord 开启 ActiveRecord 模型 默认值:false
* versionColumnName(String) 乐观锁字段名(数据库)
* versionPropertyName(String) 乐观锁属性名(实体)
* logicDeleteColumnName(String) 逻辑删除字段名(数据库)
* logicDeletePropertyName(String) 逻辑删除属性名(实体)
* naming 数据库表映射到实体的命名策略 默认下划线转驼峰命名:NamingStrategy.underline_to_camel
* columnNaming 数据库表字段映射到实体的命名策略 默认为 null,未指定按照 naming 执行
* addSuperEntityColumns(String...) 添加父类公共字段
* addIgnoreColumns(String...) 添加忽略字段
* addTableFills(IFill...) 添加表字段填充
* addTableFills(List<IFill>) 添加表字段填充
* idType(IdType) 全局主键类型
* convertFileName(ConverterFileName) 转换文件名称
* formatFileName(String) 格式化文件名称
*/
/**
* Controller 策略配置
* 方法 说明 示例
* superClass(Class<?>) 设置父类 BaseController.class
* superClass(String) 设置父类 com.baomidou.global.BaseController
* enableHyphenStyle 开启驼峰转连字符 默认值:false
* enableRestStyle 开启生成@RestController 控制器 默认值:false
* convertFileName(ConverterFileName) 转换文件名称
* formatFileName(String) 格式化文件名称
*/
/**
* Service 策略配置
* 方法 说明 示例
* superServiceClass(Class<?>) 设置 service 接口父类 BaseService.class
* superServiceClass(String) 设置 service 接口父类 com.baomidou.global.BaseService
* superServiceImplClass(Class<?>) 设置 service 实现类父类 BaseServiceImpl.class
* superServiceImplClass(String) 设置 service 实现类父类 com.baomidou.global.BaseServiceImpl
* convertServiceFileName(ConverterFileName) 转换 service 接口文件名称
* convertServiceImplFileName(ConverterFileName) 转换 service 实现类文件名称
* formatServiceFileName(String) 格式化 service 接口文件名称
* formatServiceImplFileName(String) 格式化 service 实现类文件名称
*/
/**
* Mapper 策略配置
* 方法 说明 示例
* superClass(Class<?>) 设置父类 BaseMapper.class
* superClass(String) 设置父类 com.baomidou.global.BaseMapper
* enableMapperAnnotation 开启 @Mapper 注解 默认值:false
* enableBaseResultMap 启用 BaseResultMap 生成 默认值:false
* enableBaseColumnList 启用 BaseColumnList 默认值:false
* cache(Class<? extends Cache>) 设置缓存实现类 MyMapperCache.class
* convertMapperFileName(ConverterFileName) 转换 mapper 类文件名称
* convertXmlFileName(ConverterFileName) 转换 xml 文件名称
* formatMapperFileName(String) 格式化 mapper 文件名称
* formatXmlFileName(String) 格式化 xml 实现类文件名称
*/
.strategyConfig(builder -> {
builder
.addInclude("user")//设置需要生成的表名
.addTablePrefix("t_", "c_") //设置过滤表前缀
//.addFieldSuffix("_flag")//增加过滤表后缀
.entityBuilder()//实体策略配置
.enableLombok()//开启 lombok 模型
.enableTableFieldAnnotation()//开启生成实体时生成字段注解
.versionColumnName("version")//乐观锁字段名(数据库)
.versionPropertyName("version")//乐观锁属性名(实体)
.logicDeleteColumnName("deleted")//逻辑删除字段名(数据库)
.logicDeletePropertyName("deleted")//逻辑删除属性名(实体)
.naming(NamingStrategy.underline_to_camel)//数据库表映射到实体的命名策略
.addTableFills(new Column("create_time", FieldFill.INSERT))//添加表字段填充
.addTableFills(new Property("updateTime", FieldFill.INSERT_UPDATE))//添加表字段填充
.idType(IdType.ASSIGN_ID)//全局主键类型
.formatFileName("%s")//格式化文件名称
.controllerBuilder()//Controller 策略配置
.enableHyphenStyle()//开启驼峰转连字符
.enableRestStyle()//开启生成@RestController 控制器
.formatFileName("%sController")//格式化文件名称
.serviceBuilder()//Service 策略配置
.formatServiceFileName("%sService")//格式化 service 接口文件名称
.formatServiceImplFileName("%sServiceImp")//格式化 service 实现类文件名称
.mapperBuilder()//Mapper 策略配置
.enableMapperAnnotation()//开启 @Mapper 注解
.formatMapperFileName("%sMapper")//格式化 mapper 文件名称
.formatXmlFileName("%sMapper")//格式化 xml 实现类文件名称
.build();
})
.execute();
}
}
3、扩展
逻辑删除
说明:
只对自动注入的 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. 删除接口自动填充功能失效
- 使用
deleteById方法(推荐)- 使用
update方法并:UpdateWrapper.set(column, value)(推荐)- 使用
update方法并:UpdateWrapper.setSql("column=value")- 使用
Sql 注入器注入com.baomidou.mybatisplus.extension.injector.methods.LogicDeleteByIdWithFill并使用(3.5.0版本已废弃,推荐使用deleteById)
通用枚举
解决了繁琐的配置,让 mybatis 优雅的使用枚举属性! 从 3.5.2 版本开始只需完成 步骤1: 声明通用枚举属性 即可使用
自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: 配置扫描通用枚举
- 注意!! 从 3.5.2 开始无需配置
- 注意!! spring mvc 配置参考,安装集成 MybatisSqlSessionFactoryBean 枚举包扫描,spring boot 例子配置如下:
方式一:仅配置指定包内的枚举类使用 MybatisEnumTypeHandler
配置文件 resources/application.yml
mybatis-plus:
# 支持统配符 * 或者 ; 分割
typeEnumsPackage: com.baomidou.springboot.entity.enums
....
当添加这个配置后,mybatis-plus 提供的 MybatisSqlSessionFactoryBean 会自动扫描包内合法的枚举类(使用了 @EnumValue 注解,或者实现了 IEnum 接口),分别为这些类注册使用 MybatisEnumTypeHandler。
换句话说,只有指定包下的枚举类会使用新的 TypeHandler。其他包下,或者包内没有做相关改造的枚举类,仍然会使用 mybatis 的 DefaultEnumTypeHandler。
方式二:直接指定 DefaultEnumTypeHandler
此方式用来 全局 修改 mybatis 使用的 EnumTypeHandler。
配置文件 resources/application.yml
mybatis-plus:
# 修改 mybatis 的 DefaultEnumTypeHandler
configuration:
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
自定义配置类 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 方法即可.
字段类型处理器
类型处理器,用于 JavaType 与 JdbcType 之间的转换,用于 PreparedStatement 设置参数值和从 ResultSet 或 CallableStatement 中取出一个值,本文讲解
mybatis-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" />
自动填充功能
原理:
- 实现元对象处理器接口: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的属性设置值!!!- 注解则是指定该属性在对应情况下必有值,如果无值则入库会是
nullMetaObjectHandler提供的默认方法的策略均为:如果属性有值则不覆盖,如果填充值为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
}
SQL注入器
注入器配置
全局配置 sqlInjector 用于注入 ISqlInjector 接口的子类,实现自定义方法注入。
参考默认注入器 DefaultSqlInjector(opens new window)
- 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 容器中。
执行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 新增)
- 该插件有性能损耗,不建议生产环境使用。
数据安全保护
该功能为了保护数据库配置及数据安全,在一定的程度上控制开发人员流动导致敏感信息泄露。
- 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: 字符串开头
- 随机密钥请负责人妥善保管,当然越少人知道越好。
多数据源
一个基于springboot的快速集成多数据源的启动器
这是一个第三方 mybatis 扩展库,与 mybatis-plus 本身无关,属于组织参与者
小锅盖个人发起的项目,任何行为与 baomidou 组织其它成员无关。
简介
dynamic-datasource-spring-boot-starter 是一个基于springboot的快速集成多数据源的启动器。
其支持 Jdk 1.7+, SpringBoot 1.4.x 1.5.x 2.x.x。
文档 | Documentation
详细文档 https://www.kancloud.cn/tracy5546/dynamic-datasource/2264611(opens new window)
特性
- 支持 数据源分组 ,适用于多种场景 纯粹多库 读写分离 一主多从 混合模式。
- 支持数据库敏感配置信息 加密 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");
}
}
MybatisX快速开发插件
MybatisX 是一款基于 IDEA 的快速开发插件,为效率而生。
安装方法:打开 IDEA,进入 File -> Settings -> Plugins -> Browse Repositories,输入 mybatisx 搜索并安装。
功能
XML 跳转 
生成代码(需先在 idea 配置 Database 配置数据源) 
重置模板 
JPA 提示
生成新增

生成查询

生成修改

生成删除

常见问答
为什么 JPA 不能使用?
JPA 提示的方式需要根据 Mapper 找到实体类, 找到实体类有以下五种方式
- 继承 mybatis-plus 的 BaseMapper
- Mapper.xml 文件有 resultMap 标签
- 在 Mapper 类上增加注释指定实体类, 例如:
@Entity com.xx.xx.UserModel
为什么生成的表名和期望的表名不一致
JPA 提示生成代码, 按照以下规则找到表名
- 实体类有 JPA 注解, 例如:
@Table(name="t_user") - 实体类有 mybais-plus 注解, 例如:
@TableName("t_user") - 实体类有注释:
@TableName com.xx.xx.UserModel - 如果不存在以上规则, 将驼峰转下划线. 例如 UserMode 的表名为: user_model
生成代码的模板配置
按照指定目录找到插件模板配置目录 Scratches and Consoles -> Extensions -> MybatisX
这里会提供默认模板: 例如在 1.4.13 提供了模板: default-all,default,mybatis-plus2,mybatis-plus3
如果想重置默认模板, 可以右键点击 MybatisX 目录,选择 Restore Default Extensions 选项

自定义模板内容
| 名称 | 含义 |
|---|---|
| tableClass.fullClassName | 类的全称(包括包名) |
| tableClass.shortClassName | 类的简称 |
| tableClass.tableName | 表名 |
| tableClass.pkFields | 表的所有主键字段 |
| tableClass.allFields | 表的所有字段 |
| tableClass.baseFields | 排除主键和 blob 的所有字段 |
| tableClass.baseBlobFields | 排除主键的所有字段 |
| tableClass.remark | 表注释 |
字段信息
| 名称 | 含义 |
|---|---|
| field.fieldName | 字段名称 |
| field.columnName | 列名称 |
| field.jdbcType | jdbc 类型 |
| field.columnLength | 列段长度 |
| field.columnScale | 列的精度 |
| field.columnIsArray | 字段类型是不是数组类型 |
| field.shortTypeName | java 类型短名称, 通常用于定义字段 |
| field.fullTypeName | java 类型的长名称, 通常用于导入 |
| field.remark | 字段注释 |
| field.autoIncrement | 是否自增 |
| field.nullable | 是否允许为空 |
配置信息
| 名称 | 含义 |
|---|---|
| baseInfo.shortClassName | 配置名称 |
| baseInfo.tableName | 配置文件名称 |
| baseInfo.pkFields | 配置名称 |
| baseInfo.allFields | 后缀 |
| baseInfo.baseFields | 包名 |
| baseInfo.baseBlobFields | 模板内容 |
| baseInfo.remark | 相对模块的资源文件路径 |
企业高级特性
提示
mybatis-mate 为 mp 企业级模块,旨在更敏捷优雅处理数据。
- mybatis-mate 示例 :传送门(opens new window)
- 联系作者确认后微信公众号发文介绍 mybatis-mate 的软文,可免费获得永久个人授权证书
- 该模块属于 mybatis (MP) 的扩展库,非 mp 的收费版本,任何问题由
青苗个人负责。
数据审计(对账)
- 对比两对象属性差异,例如:银行流水对账。
// 1,异步回调,注意 @EnableAsync 开启异步
applicationEventPublisher.publishEvent(new DataAuditEvent((t) -> {
List<Change> changes = t.apply(newVersion, oldVersion);
for (Change valueChange : changes) {
ValueChange change = (ValueChange) valueChange;
System.err.println(String.format("%s不匹配,期望值 %s 实际值 %s", change.getPropertyName(), change.getLeft(), change.getRight()));
}
}));
// 2,手动调用对比
DataAuditor.compare(obj1, obj2);
数据敏感词过滤
- 数据敏感词过滤(AC 算法)配置完处理器,框架自动处理请求的所有字符串敏感词过滤,支持嵌套关键词让敏感词无处遁形。
- 数据库自维护敏感词库(免费、可控),默认加载缓存词根支持指定重新加载词库。
数据范围(数据权限)
- 注解 @DataScope
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| type | String | 否 | "" | 范围类型,用于区分对于业务分类,默认空 |
| value | DataColumn[] | 否 | {} | 数据权限字段,支持多字段组合 |
| ignore | boolean | 否 | false | 忽略权限处理逻辑 true 是 false 否 |
- 注解 @DataColumn
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| alias | String | 否 | "" | 关联表别名 |
| name | String | 是 | 字段名 |
- 行级粒度权限控制,例如:上级部门可以查看子部门信息。
// 测试 test 类型数据权限范围,混合分页模式
@DataScope(type = "test", value = {
// 关联表 user 别名 u 指定部门字段权限
@DataColumn(alias = "u", name = "department_id"),
// 关联表 user 别名 u 指定手机号字段(自己判断处理)
@DataColumn(alias = "u", name = "mobile")
})
@Select("select u.* from user u")
List<User> selectTestList(IPage<User> page, Long id, @Param("name") String username);
// 测试数据权限,最终执行 SQL 语句
SELECT u.* FROM user u WHERE (u.department_id IN ('1', '2', '3', '5')) AND u.mobile LIKE '%1533%' LIMIT 1,10
关于
IDataScopeProvider的说明:请注意必须注入 IDataScopeProvider 实现类处理数据权限,关于数据传参支持 2 种方式: 1,自定义 mapper 方法通过方法参数传递,在 setWhere 方法
Object[] args参数中获取 2,利用 ThreadLocal 传递参数,你可以拦截 controller 层或者 service 层设置数据权限处理参数,更多可以 👉参考(opens new window)
表结构自动维护
- 数据库 Schema 初始化,升级 SQL 自动维护,区别于
flyway支持分表库、可控制代码执行 SQL 脚本 - 首次会在数据库中生成 ddl_history 表,每次执行SQL脚本会自动维护版本信息。
@Component
public class MysqlDdl implements IDdl {
/**
* 执行 SQL 脚本方式
*/
@Override
public List<String> getSqlFiles() {
return Arrays.asList(
"db/tag-schema.sql",
"D:\\db\\tag-data.sql"
);
}
}
// 切换到 mysql 从库,执行 SQL 脚本
ShardingKey.change("mysqlt2");
ddlScript.run(new StringReader("DELETE FROM user;\n" +
"INSERT INTO user (id, username, password, sex, email) VALUES\n" +
"(20, 'Duo', '123456', 0, 'Duo@baomidou.com');"));
字段数据绑定(字典回写)
- 注解 @FieldBind
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| sharding | String | 否 | "" | 分库分表数据源指定 |
| type | String | 是 | 类型(用于区分不同业务) | |
| target | String | 是 | 目标显示属性(待绑定属性,注意非数据库字段请排除) |
- 数据库
sex值0、1自动映射为男、女 - 可以绑定映射为对象,例如:根据订单 ID 映射 订单对象或者编号
@FieldBind(type = "user_sex", target = "sexText")
private Integer sex;
// 绑定显示属性,非表字典(排除)
@TableField(exist = false)
private String sexText;
- 绑定业务处理类需要实现 IDataBind 接口,注入 spring 容器
@Component
public class DataBind implements IDataBind {
...
}
虚拟属性绑定
- 注解 @JsonBind
@JsonBind("绑定类型")
public class User {
...
}
- 返回 Json 虚拟属性绑定策略
@Component
public class JsonBindStrategy implements IJsonBindStrategy {
@Override
public Map<String, Function<Object, Map<String, Object>>> getStrategyFunctionMap() {
return new HashMap<String, Function<Object, Map<String, Object>>>(16) {
{
// 注入虚拟节点
put(Type.departmentRole, (obj) -> new HashMap(2) {{
User user = (User) obj;
// 枚举类型转换
put("statusText", StatusEnum.get(user.getStatus()).getDesc());
// 可调用数据库查询角色信息
put("roleName", "经理");
}});
}
};
}
}
字段加密解密
- 注解 @FieldEncrypt
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| password | String | 否 | "" | 加密密码 |
| algorithm | Algorithm | 否 | PBEWithMD5AndDES | PBE MD5 DES 混合算法 |
| encryptor | Class | 否 | IEncryptor | 加密处理器 |
- 算法 Algorithm
| 算法 | 描述 |
|---|---|
| MD5_32 | 32 位 md5 算法 |
| MD5_16 | 16 位 md5 算法 |
| BASE64 | 64 个字符来表示任意二进制数据算法 |
| AES | AES 对称算法 |
| RSA | 非对称加密算法 |
| SM2 | 国密 SM2 非对称加密算法,基于 ECC |
| SM3 | 国密 SM3 消息摘要算法,可以用 MD5 作为对比理解 |
| SM4 | 国密 SM4 对称加密算法,无线局域网标准的分组数据算法 |
| PBEWithMD5AndDES | 混合算法 |
| PBEWithMD5AndTripleDES | 混合算法 |
| PBEWithHMACSHA512AndAES_256 | 混合算法 |
| PBEWithSHA1AndDESede | 混合算法 |
| PBEWithSHA1AndRC2_40 | 混合算法 |
注意
MD5 算法为不可逆算法,存储数据库及查询结果都是密文 SM4 算法必须依赖 bouncycastle 加密库 混合算法必须依赖 jasypt 加密库 【注意】查询返回加密对象必须包含
加密注解信息,单纯的返回某个 String 或者 List 某个集合是无法解密的。
- 注解
FieldEncrypt实现数据加解密,支持多种加密算法
@FieldEncrypt
private String email;
字段脱敏
- 注解 @FieldSensitive
- 注解
FieldSensitive实现数据脱敏,内置手机号、邮箱、银行卡号等 9 种常用脱敏规则
@FieldSensitive("testStrategy")
private String username;
@Configuration
public class SensitiveStrategyConfig {
/**
* 注入脱敏策略
*/
@Bean
public ISensitiveStrategy sensitiveStrategy() {
// 自定义 testStrategy 类型脱敏处理
return new SensitiveStrategy().addStrategy("testStrategy", t -> t + "***test***");
}
}
// 跳过脱密处理,用于编辑场景
RequestDataTransfer.skipSensitive();
多数据源分库分表(读写分离)
- 注解 @Sharding
| 属性 | 类型 | 必须指定 | 默认值 | 描述 |
|---|---|---|---|---|
| value | String | 是 | "" | 分库组名,空使用默认主数据源 |
| strategy | Class | 否 | RandomShardingStrategy | 分库&分表策略 |
- 配置
mybatis-mate:
sharding:
health: true # 健康检测
primary: mysql # 默认选择数据源
datasource:
mysql: # 数据库组
- key: node1
...
- key: node2
cluster: slave # 从库读写分离时候负责 sql 查询操作,主库 master 默认可以不写
...
postgres:
- key: node1 # 数据节点
...
- 注解
Sharding切换数据源,组内节点默认随机选择(查从写主)
@Mapper
@Sharding("mysql")
public interface UserMapper extends BaseMapper<User> {
@Sharding("postgres")
Long selectByUsername(String username);
}
- 切换指定数据库节点
// 切换到 mysql 从库 node2 节点
ShardingKey.change("mysqlnode2");
多数据源动态加载卸载
👉 mybatis-mate-sharding-dynamic(opens new window)
多数据源事务( jta atomikos)
👉 mybatis-mate-sharding-jta-atomikos
4、插件
插件主体
注意
版本要求:3.4.0 版本以上
MybatisPlusInterceptor
该插件是核心插件,目前代理了 Executor#query 和 Executor#update 和 StatementHandler#prepare 方法
属性
private List<InnerInterceptor> 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 忽略拦截 更多说明详见源码注释
分页插件
PaginationInnerInterceptor
支持的数据库
- mysql,oracle,db2,h2,hsql,sqlite,postgresql,sqlserver,Phoenix,Gauss ,clickhouse,Sybase,OceanBase,Firebird,cubrid,goldilocks,csiidb
- 达梦数据库,虚谷数据库,人大金仓数据库,南大通用(华库)数据库,南大通用数据库,神通数据库,瀚高数据库
属性介绍
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| overflow | boolean | false | 溢出总页数后是否进行处理(默认不处理,参见 插件#continuePage 方法) |
| maxLimit | Long | 单页分页条数限制(默认无限制,参见 插件#handlerLimit 方法) |
|
| dbType | DbType | 数据库类型(根据类型获取应使用的分页方言,参见 插件#findIDialect 方法) |
|
| dialect | IDialect | 方言实现类(参见 插件#findIDialect 方法) |
建议单一数据库类型的均设置 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; 如果想临时不分页,可以在初始化IPage时size参数传 <0 的值;
如果返回类型是 List 则入参的 IPage 可以为 null(为 null 则不分页),但需要你手动 入参的IPage.setRecords(返回的 List);
如果 xml 需要从 page 里取值,需要page.属性获取
其他:
生成 countSql 会在
left join的表不参与where条件的情况下,把left join优化掉
所以建议任何带有left join的sql,都写标准sql,即给于表一个别名,字段也要别名.字段
乐观锁插件
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 + 1newVersion会回写到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;
}
}
多租户插件
TenantLineInnerInterceptor
属性介绍
| 属性名 | 类型 | 默认值 | 描述 |
|---|---|---|---|
| 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)
防全表更新与删除插件
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);
}}
动态表名插件
DynamicTableNameInnerInterceptor
注意事项:
- 原理为解析替换设定表名为处理器的返回表名,表名建议可以定义复杂一些避免误替换
- 例如:真实表名为 user 设定为 mp_dt_user 处理器替换为 user_2019 等

浙公网安备 33010602011771号