此文章介绍了mybatis-plus几个比较常用的插件或功能,可以提升开发效率,也使得代码更加规范化。主要介绍:多租户插件中获取租户ID以及设置忽略的表,乐观锁插件的配置即统一处理影响条数为0时抛出异常,通用枚举的配置让字典值的处理更方便。
引入mybatis-plus依赖包
注:系列文章二已经添加过,更详细使用请参见mybatis-plus官网
<!-- mybatis-plus依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
引入多租户插件
新建mybatis-plus配置文件MybatisPlusConfig.java,源码如下:
@Configuration
public class MybatisPlusConfig {
@Resource
private CommonManager commonManager;
@Resource
private CustomizerConfig customizerConfig;
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 多租户插件
interceptor.addInnerInterceptor(new TenantLineInnerInterceptor(new TenantLineHandler() {
/**
* 获取租户ID
*/
@Override
public Expression getTenantId() {
return new LongValue(commonManager.getCurrentUser().getTenantId());
}
/**
* 设置忽略的表
*/
@Override
public boolean ignoreTable(String tableName) {
return customizerConfig.getTenantIgnoreTables().contains(tableName);
}
}));
return interceptor;
}
}
获取租户ID说明
用户登录时,将用户信息存储到redis中,返回userToken,以后的所有请求都必须在请求头中携带userId和userToken信息。每个请求在进入controller层之前,通过拦截器统一拦截请求,将用户信息存储到线程变量中,此处可以从线程变量中获取用户信息,再获取其中的租户ID。若线程变量中找不到用户信息,通过请求头中的userId去redis中获取用户信息。
不启用租户控制的表(ignoreTable)
customizerConfig类是自定义的配置信息类,把需要忽略的表配置到配置文件中,方便修改;
/**
* 自定义配置文件类
*/
@Component
@ConfigurationProperties(prefix = "customizer")
@Data
public class CustomizerConfig {
/**
* 多租户插件忽略的表
*/
@Value("customizer.tenant-ignore-tables")
private List<String> tenantIgnoreTables;
/**
* 包名
*/
@Value("customizer.package-name")
private String packageName;
}
yml添加配置如下:
# 自定义配置项
customizer:
# 多租户插件忽略的表
tenant-ignore-tables: >
sys-user
,sys_action
分页插件
直接在mybatis-plus配置文件中增加配置:
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
分页插件比较简单,在此就不详细介绍了。
乐观锁插件
配置文件中继续添加插件:
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
在乐观锁的字段上面添加注解@Version,此处目前还不能统一配置,只能每个entity实体的乐观锁字段都添加此注解。
乐观锁更新失败时,并不会抛出异常(目前官方也没有修改的计划),我们可以通过AOP切面来统一处理。
引入切面依赖:
<!-- aop切面依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
增加切面配置类AspectConfig.java,如下:
/**
* 切面配置
* AOP通知种类:
* 1、前置通知(@Before):在方法调用之前执行
* 2、后置通知(@After):在方法正常调用之后执行
* 3、环绕通知(@Around):在方法调用之前和之后,都分别可以执行的通知
* 4、异常通知(@AfterThrowing):如果在方法调用过程中发生异常,则通知
* 5、最终通知(@AfterReturning):在方法调用之后执行
*/
@Aspect
@Component
public class AspectConfig {
// mp baseMapper类全路径
private static final String MP_BASE_MAPPER_NAME = "com.baomidou.mybatisplus.core.mapper.BaseMapper";
// 需要检查影响条数的方法列表
private static final List<String> CHECK_AFFECTED_ROWS_METHOD_LIST = Arrays.asList("insert", "update", "updateById", "deleteById");
/**
* mapper层最终通知:
* execution 代表所要执行的表达式主体
* 第一处 * 代表方法返回类型 *代表所有类型
* 第二处 包名代表aop监控的类所在的包
* 第三处 .. 代表该包以及其子包下的所有类方法
* 第四处 * 代表类名,*代表所有类
* 第五处 *(..) *代表类中的方法名,(..)表示方法中的任何参数
*
* @param joinPoint 连接点
* @param result 返回结果
*/
@AfterReturning(value = "execution(* com.yd.sysjava.mapper..*.*(..))",
returning = "result")
public void mapperAfterReturning(JoinPoint joinPoint, Object result) {
/*
相关方法影响条数必须大于0
insert update updateById deleteById
*/
// 获取方法名
String methodName = joinPoint.getSignature().getName();
boolean checkFailed = CHECK_AFFECTED_ROWS_METHOD_LIST.contains(methodName)
&& (result == null || (Integer) result == 0);
if (checkFailed) {
// 抛出自定义异常
throw new BusinessException(ResultMsg.FAILED_AFFECTED_ZERO.getMsg());
}
}
}
效果:先调用insert方法新增一条记录,会有一个默认的version为1,调用更新方法,正常操作的时候能够更新成功,并且version变为2。通过断点,查询完记录之后断点停住,手动去修改数据库的version字段值为3,跳过断点之后会更新失败,AOP切面会抛出异常。
@Override
public void modifyUser(ModifyUserSO so) {
Integer id = so.getId();
// 先查询
SysUser sysUser = sysUserMapper.selectById(id);
// 赋值
BeanUtils.copyProperties(so, sysUser);
// 更新-打断点
sysUserMapper.updateById(sysUser);
}
打印的更新语句如下:
UPDATE sys_user SET full_name = '潘松1', login_name = 'pansong1', password = '', user_type = '001', tenant_id = 1, version = 3, created_by = 1, created_time = '2022-04-16T11:00:36', updated_by = 1, updated_time = '2022-04-16T11:00:36' WHERE tenant_id = 1 AND id = 1 AND version = 2
此处详细的配置大家可以根据自己的实际情况进行适当调整,我在此处判断的insert等方法返回影响条数也是必须大于0的,和乐观锁没有关系。此处如果需要单独对乐观锁进行限制,则还需要进行更细致的判断。
通用枚举
相信大多数同学都有这样的经验,有些查询会关联很多表,但是其中大多数都是关联查询字典值的。现在通过通用枚举的配置,就无需在查询时关联字典表进行查询了,而且也省去了维护字典表的麻烦。只需简单几步即可搞定:
- 创建一个枚举,实现IEnum接口,此处使用userType字段举例,传入的泛型和value类型一致即可。
/**
* 用户类型:正式工、临时工、外包工、派遣工
*/
public enum UserTypeEnum implements IEnum<String> {
FORMAL("001", "正式工"),
TEMPORARY("002", "临时工");
private final String value;
private final String desc;
UserTypeEnum(String value, String desc) {
this.value = value;
this.desc = desc;
}
@Override
public String getValue() {
return this.value;
}
@Override
public String toString() {
return this.desc;
}
}
- entity实体中userType字段类型使用此枚举:
/**
* 用户类型
*/
private UserTypeEnum userType;
- mybatis-plus配置类中增加bean:
@Bean
public Jackson2ObjectMapperBuilderCustomizer jackson2Object(){
return builder -> builder.featuresToEnable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
}
以后的枚举字段只需复制UserTypeEnum进行修改,然后进行步骤二的调整即可,步骤三只需配置一次。效果如下:
这里会存在一个问题,通过此种方式,可以获取到对应的字典值,但是原始数据库中的value值(此处的001)查询不到了,如果有时候需要此值做判断,只能使用001对应的’正式工’进行判断。