Mybatis-Plus进阶之扩展插件
MybatisPlus基础篇请看:Mbatis-Plus整合springboot详细学习笔记
基本实体类:
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Employee {
// id
private Long id;
// 名称
private String name;
// 年龄
@TableField(fill = FieldFill.UPDATE)
private Integer age;
// 邮箱
private String email;
// 上级id
private Long managerId;
/** 设置自动填充 */
// 创建时间
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 更新时间
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime updateTime;
// 版本号
@Version
private Integer version;
// 逻辑删除的 局部设置
@TableField(select = false) // 查询的时候,不显示这个参数
@TableLogic
private Integer deleted;
// 子级
@TableField(exist = false)
private List<Employee> children = new ArrayList<>();
}
1、逻辑删除
springboot中配置:
# 配置逻辑删除的值 在这里配置的是 删除的时候为1 未删除的时候为0
mybatis-plus:
global-config:
db-config:
logic-not-delete-value: 0
logic-delete-value: 1
需要在实体类的字段上加上逻辑删除的注解:
// 删除的时候为1 未删除的时候为0
@TableLogic
private Integer deleted;
测试逻辑删除:
// update employee set deleted = 1 where id = ? and deleted = 0
boolean b = employeeService.removeById(1237753494614700033L);
// 查询输出后将不会显示刚刚被删除的那条 select * from employee where deleted = 1
List<Employee> list = employeeService.list(null);
// 并且只能更新未删除的
int update = employeeMapper.update(null, lambdaUpdateWrapper);
逻辑删除注意事项:
【注意: 自定义的查询方法,系统 不会 自动加上 逻辑删除条件,需要自己手动加上】
在mapper中自定义的方法,需要自己手动加上逻辑删除限制条件
2、自动填充
设置自动填充需要在字段上加上注解:
/** 设置自动填充 */
// 创建时间
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createTime;
// 更新时间
@TableField(fill = FieldFill.UPDATE)
private LocalDateTime updateTime;
配置字段自动填充:
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
/**
* 插入的时候自动填充
* @param metaObject
*/
@Override
public void insertFill(MetaObject metaObject) {
// fieldName 是实体中的属性名称
// 检查是否有这个属性
boolean createTime = metaObject.hasSetter("createTime");
if (createTime) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
}
/**
* 修改的时候自动填充
* @param metaObject
*/
@Override
public void updateFill(MetaObject metaObject) {
/**
* 先检查时候已经传入值了,若传入值了,则以传入值为准,就不再自动填充
*/
Object updateTime = getFieldValByName("updateTime", metaObject);
// 若没有传入值才自动填充
if (updateTime == null) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}
}
测试自动填充以及注意事项:
Employee employee = new Employee();
// 此时添加员工数据的时候会自动赋值上创建时间
int rows = employeeMapper.insert(employee);
// 这里修改会自动填充修改时间
int rows = employeeMapper.updateById(employee);
// 注意这种情况下【不会】自动填充修改时间
int update = employeeMapper.update(null, lambdaUpdateWrapper);
// 有实体传入的情况下,会自动填充修改时间
int update = employeeMapper.update(employee, lambdaUpdateWrapper);
3、乐观锁
配置乐观锁插件:
@Slf4j
@Configuration
public class MybatisPlusConfig {
/**
* 乐观锁插件
* @return
*/
@Bean
public OptimisticLockerInterceptor optimisticLockerInterceptor() {
return new OptimisticLockerInterceptor();
}
}
实体类中字段上加上乐观锁注解:
// 版本号
@Version
private Integer version;
乐观锁测试:
boolean b = employeeService.updateById(employee);
// 第二个参数为,【查询】 重点
boolean update = employeeService.update(employee,lambdaQueryWrapper);
注意事项:
特别说明:
-
支持的数据类型只有:int,Integer,long,Long,Date,Timestamp,LocalDateTime
-
整数类型下
newVersion = oldVersion + 1
-
newVersion
会回写到entity
中 -
仅支持
updateById(entity)
与update(entity, wrapper)
方法 -
在
update(entity, wrapper)
方法下,wrapper
不能复用!!!
4、执行sql分析打印
引入依赖:
<!-- 执行sql 分析打印 依赖-->
<dependency>
<groupId>p6spy</groupId>
<artifactId>p6spy</artifactId>
<version>3.8.2</version>
</dependency>
更改数据库配置:
spring:
datasource:
driver-class-name: com.p6spy.engine.spy.P6SpyDriver
url: jdbc:p6spy:mysql://127.0.0.1:3306/mybatisplus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
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
# 将日志输出到指定的位置
#logfile=log.log
注意事项:
driver-class-name 为 p6spy 提供的驱动类
url 前缀为 jdbc:p6spy 跟着冒号为对应数据库连接地址
打印出sql为null,在excludecategories增加commit
批量操作不打印sql,去除excludecategories中的batch
批量操作打印重复的问题请使用MybatisPlusLogFactory (3.2.1新增)
该插件有性能损耗,不建议生产环境使用
5、多租户实现
需要在分页配置类中配置:
@Slf4j
@Configuration
public class MybatisPlusConfig {
/**
* 分页配置中,设置多租户信息
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
ArrayList<ISqlParser> sqlParsers = new ArrayList<>();
// 租户解析器
TenantSqlParser tenantSqlParser = new TenantSqlParser();
tenantSqlParser.setTenantHandler(new TenantHandler() {
@Override
public Expression getTenantId(boolean where) {
// 租户的id,这里测试固定写死
return new LongValue(1088248166370832385L);
}
// 多租户字段是什么
@Override
public String getTenantIdColumn() {
// 这是表中的字段名,即 用于区分租户的字段
return "manager_id";
}
// 是否需要排除某些表,不加租户字段
@Override
public boolean doTableFilter(String tableName) {
// 为 角色 表的时候,不增加租户信息
if("role".equals(tableName)){
// 不增加租户信息
return true;
}
return false;
}
});
sqlParsers.add(tenantSqlParser);
paginationInterceptor.setSqlParserList(sqlParsers);
/
【特定sql 过滤】,可用注解替换
@SqlParser(filter = true) 为true表示,不在此方法上增加租户信息
/
paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
@Override
public boolean doFilter(MetaObject metaObject) {
MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
if ("cn.mesmile.mybatisplusdemo.mapper.EmployeeMapper.selectAll"
.equals(ms.getId())) {
// 同时这个方法对 动态表明解析也会产生作用,也会过滤掉动态表名,只显示原来的表名
// 不增加租户信息
return true;
}
return false;
}
});
return paginationInterceptor;
}
}
过滤得到不使用多租户用户的方法:
@Component
public interface EmployeeMapper extends MyMapper<Employee> {
/**
* 这是自定义的查询方法
* @param wrapper
*/
// @SqlParser(filter = true) 该注解为true 设置该方法,不需要要加入租户信息
@Select("select * from employee ${ew.customSqlSegment}")
List<Employee> selectAll(@Param(Constants.WRAPPER)Wrapper<Employee> wrapper);
}
6、动态表名解析
配置动态表名解析器:
@Slf4j
@Configuration
public class MybatisPlusConfig {
public static ThreadLocal<String> myTableName = new ThreadLocal<>();
/**
* mybatis-plus 的分页配置里面配置 动态表名解析器
* @return
*/
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
ArrayList<ISqlParser> sqlParsers = new ArrayList<>();
// 动态表名解析器
DynamicTableNameParser dynamicTableNameParser = new DynamicTableNameParser();
HashMap<String, ITableNameHandler> tableNameHandlerHashMap = new HashMap<>();
// 第一参数是需要 动态替换的表名
tableNameHandlerHashMap.put("employee", new ITableNameHandler() {
@Override
public String dynamicTableName(MetaObject metaObject, String sql, String tableName) {
log.info("metaObject: {}", metaObject);
log.info("原表执行的 sql: {}", sql);
log.info("原表的名称 tableName: {}", tableName);
// 【这里返回动态的表名】
// 如果返回的值为空,则不进行替换 这里返回的事 动态表名
return myTableName.get();
}
});
dynamicTableNameParser.setTableNameHandlerMap(tableNameHandlerHashMap);
sqlParsers.add(dynamicTableNameParser);
paginationInterceptor.setSqlParserList(sqlParsers);
return paginationInterceptor;
}
}
注意:【在多租户配置这里 , 过滤的方法, 动态表名不会生效】
/
【特定sql 过滤】,可用注解替换
@SqlParser(filter = true) 为true表示,不在此方法上增加租户信息
/
paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
@Override
public boolean doFilter(MetaObject metaObject) {
MappedStatement ms = SqlParserHelper.getMappedStatement(metaObject);
if ("cn.mesmile.mybatisplusdemo.mapper.EmployeeMapper.selectAll"
.equals(ms.getId())) {
// 同时这个方法对 动态表明解析也会产生作用,也会过滤掉动态表名,只显示原来的表名
// 不增加租户信息
return true;
}
return false;
}
});
例:
@Component
public interface EmployeeMapper extends BaseMapper<Employee> {
/**
* 这是自定义的查询方法
* @param wrapper
* @return
*/
//该注解为true 设置该方法,不需要要加入租户信息
//@SqlParser(filter = true)
@Select("select * from employee ${ew.customSqlSegment}")
List<Employee> selectAll(@Param(Constants.WRAPPER)Wrapper<Employee> wrapper);
}
测试:
MybatisPlusConfig.myTableName.set("employee_2020");
LambdaQueryWrapper<Employee> employeeLambdaQueryWrapper = Wrappers.lambdaQuery();
// 当自定义查询方法[有] @SqlParser(filter = true) 时候:select * from employee 动态表名不生效
// 当自定义查询方法[没有] @SqlParser(filter = true) 时候:select * from employee_2020 动态表名生效
List<Employee> employees = employeeMapper.selectAll(employeeLambdaQueryWrapper);
7、sql注入器 和 选装件
public class DeleteAllMethod extends AbstractMethod {
@Override
public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
// 执行的sql
String sql = "delete from "+ tableInfo.getTableName();
// mapper 接口方法名
String method = "deleteAll";
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
return addDeleteMappedStatement(mapperClass, method , sqlSource);
}
}
@Component
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
// 获取到父类已有的抽象方法
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
// 将自定义的类方法 加入到父类的集合中
methodList.add(new DeleteAllMethod());
return methodList;
}
}
在mapper类中写入方法:
@Component
public interface EmployeeMapper extends BaseMapper<Employee> {
/**
* 自定义的sql注入方法,删除所有
* @return 返回受影响行数
*/
int deleteAll();
}
选装件:InsertBatchSomeColumn 、 LogicDeleteByIdWithFill 、AlwaysUpdateSomeColumnById
加入选装件:
@Component
public class MySqlInjector extends DefaultSqlInjector {
@Override
public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
List<AbstractMethod> methodList = super.getMethodList(mapperClass);
// 将自定义的类方法 加入到父类的集合中
methodList.add(new DeleteAllMethod());
// 【选装件】 插入的时候,不插入【逻辑删除】 和 【age年龄】
methodList.add(new InsertBatchSomeColumn(t -> !t.isLogicDelete() && !t.getColumn().equals("age")));
// 【选装件】 逻辑删除的时候同时再修改值,但是修改值得字段上要加上注解 @TableField(fill = FieldFill.UPDATE)
methodList.add(new LogicDeleteByIdWithFill());
// 【选装件】 根据id更新固定的几个字段(但是不包括逻辑删除) 这里 不更新 name 字段
methodList.add(new AlwaysUpdateSomeColumnById(t -> !t.getColumn().equals("name")));
return methodList;
}
}
在mapper接口中写选装件方法:
public interface MyMapper<T> extends BaseMapper<T> {
/**
* 自定义的sql注入方法,删除所有
* @return 返回受影响行数
*/
int deleteAll();
/**
* mybatis-plus 中的【选装件】 , 自定义批量插入
* @param list
* @return
*/
int insertBatchSomeColumn(List<T> list);
/**
* mybatis-plus 中的额【选装件】 , 自定义删除
*
* 在删除的同时,填充值修改值,需要在 修改的字段上 加注解
* @TableField(fill = FieldFill.UPDATE)
* private Integer age;
* @param entity
* @return
*/
int deleteByIdWithFill(T entity);
/**
* mybatis-plus 中的额【选装件】
* 指定更新某些字段
* @param entity
* @return
*/
int alwaysUpdateSomeColumnById(@Param(Constants.ENTITY) T entity);
}