更好用的Mybatis Plus:Mybatis Flex(下)
前言
上篇文章讲了 Mybaits Flex 的基础用法,这次讲一下 Mybatis Flex 的进阶用法,包含了一些 Mybatis Flex 核心的一些功能。(以下简称 MF)
逻辑删除
上篇文章讲到了 @Column
注解,其中有个属性为 isLogicDelete
,当这个属性为 true
时,则标识该字段为逻辑删除字段,MF 会识别到该字段并进相应的一些处理。
当你在实体类中指明了逻辑删除字段后,在查询时 MF 会自动帮你拼接上 WHERE xxx = 0
的 SQL,而在你删除时则不会真正删除表中的数据,而是将你标识为逻辑删除的字段的值改为1,使其查询不到。
假如我们使用 MF 查询某张表:
@Test
void simpleSelect() {
List sysUsers = userMapper.selectAll();
sysUsers.forEach(System.out::println);
}
其执行 SQL 如下:
SELECT
*
FROM
sys_user
WHERE
deleted = 0
需要注意的是:当你进行连表查询时,如果你连的表中同样标明了逻辑删除字段也会作为条件拼接。
@Test
void complexSelect() {
List sysUserList = userMapper.selectListByQuery(QueryWrapper.create()
.select()
.from(SysUser.class)
.leftJoin(SysDept.class)
.on(SYS_USER.DEPT_ID.eq(SYS_DEPT.DEPT_ID))
.where(SYS_USER.USER_ID.gt(5)));
sysUserList.forEach(System.out::println);
}
执行 SQL 如下:
SELECT
*
FROM
sys_user
LEFT JOIN sys_dept
ON sys_dept.deleted = 0
AND sys_user.dept_id = sys_dept.dept_id
WHERE sys_user.user_id > 5 AND sys_user.deleted = 0
而当你删除数据时:
@Test
void simpleDelete() {
userMapper.deleteById(11);
}
执行 SQL 如下:
UPDATE
sys_user
SET
deleted = 1
WHERE
user_id = 11
乐观锁
当同时对同一条数据进行操作时,我们应该只允许其中一个操作成功,其余操作应该失败掉,而 MF 也提供了相应的解决方案。
在 @Column
中有一个 version
属性,当其设置为 true
时,MF 会将其视为乐观锁字段,在新增数据时如果该数据没设值,则会默认设为0。而当其进行修改时,会先比对版本是否正确,如果正确才会修改成功,修改成功后会将版本号+1。
UPDATE
account
SET
xxx = xxx,
version = version + 1
WHERE
id = ?
AND version = ?
需要注意的是:在同一张表中,只能有一个乐观锁字段,即:每个实体类中只能有一个被 @Column(version = true)
修饰的字段。
数据填充
当我们对某张表进行 Insert
或者 update
时,我们希望某些字段的值可以自动填充,此时就可以用到 MF 中提供的数据填充的功能,而在 MF 中提供了两种数据填充的方式:@Table
注解和 @Column
注解。
@Table 注解
在 @Table
注解中提供了 onInsert
属性和 onUpdate
属性来帮助我们自定义插入时的监听器。
@Table
注解中提供了三个属性来帮我们监听:插入、修改和设置,我们需要分别继承 MF 的:InsertListener
、UpdateListener
和 SetListener
进行相应的操作,如下:
通过监听用户操作来帮我们完成一些额外的操作。
@Column 注解
在 @Column
注解中同样提供了数据填充的功能:onInsertValue
和 onUpdateValue
类似于 Mybatis Plus 中 @TableField
注解中的 fill
属性,但是 MF 中的该功能会更强大,它甚至支持写 SQL 语句来填充值:
@Table 注解 与 @Column 注解的区别
在使用 @Table
注解中的数据填充功能时,我们设置的值是 基于Java层面的,在准备执行操作时会先触发监听器,监听器将操作执行完成之后,将参数传递至 ORM 进行处理。
而在使用 @Column
注解中的数据填充功能时,我们设置是基于 数据库层面的 ,我们设置的值会直接拼接到 SQL 中。
数据脱敏
在上一篇文章中我们讲了注解 @ColumnMask
,该注解是实现数据脱敏的核心注解,其仅有一个属性 value
,它指定了我们执行脱敏策略的名称。
而 MF 共提供了9种脱敏规则,如下:
MOBILE
:手机号脱敏FIXED_PHONE
:固定电话脱敏ID_CARD_NUMBER
:身份证号脱敏CHINESE_NAME
:中文名脱敏ADDRESS
:地址脱敏EMAIL
:邮箱脱敏PASSWORD
:密码脱敏CAR_LICENSE
:车牌号脱敏BANK_CARD_NUMBER
:银行卡号脱敏
自定义脱敏规则
除此之外,我们还可以去自定义脱敏规则,我们利用内部提供的 MaskManager
来实现。
MaskManager.registerMaskProcessor("自定义规则名称",
data-> {
//进行脱敏操作
//返回脱敏后的数据
return data;
});
取消脱敏
在某些特定场景下,我们可能需要得到未脱敏的原始数据来进行操作,例如用户登陆时验证用户名与密码是否正确等。
在 MaskManager
中提供了两种方式来实现该功能:execWithoutMask()
、skipMask()
和restoreMask()
,需要注意的是execWithoutMask()
方法执行后会自动还原脱敏,而 skipMask()
与 restoreMask()
需要配套使用才能实现取消脱敏和还原脱敏的操作。
使用方法如下:
SysUserMapper mapper = ...;
List userList = MaskManager.execWithoutMask(mapper::selectAll);
//业务操作
//...
又或者使用以下方式来实现:
try {
//取消脱敏
MaskManager.skipMask();
List userList = sysUserMapper.selectListByQuery(...);
//业务操作
//...
} finally {
//还原脱敏
MaskManager.retoreMask();
}
多数据源
在某些场景下我们可能会使用到多数据源的方式来实现我们的业务,设置方式如下:
mybatis-flex:
datasource:
db1:
url: jdbc:mysql://127.0.0.1:3306/db1
username: root
password: root
db2:
url: jdbc:mysql://127.0.0.1:3306/db2
username: root
password: root
MF 中也提供了两种方式来帮我们切换数据源:
DataSourceKey.use()
和 DataSourceKey.clear()
@UseDataSource("datasourceName")
:
- 当该注解加在Mapper上时,则指定该Mapper执行的方法使用指定数据源
- 当该注解加在Mapper方法上时,则指定该方法使用指定数据源
- 当该注解加在实体类上时,则指定该实体相关的增删改查使用指定数据源
@DataSourceKey.use()
try {
DataSourceKey.use("datasourceName");
//业务操作
//...
} finally {
DataSourceKey.clear();
}
@UseDataSource("datasourceName")
Mapper
当 @UseDataSource("datasourceName")
注解加在Mapper上时,指明该Mapper中所有方法使用指定数据源。
@UseDataSource("db1")
public interface SysUserMapper extends BaseMapper {
// Mapper方法
//...
}
Mapper方法
当 @UseDataSource("datasourceName")
注解加在Mapper方法上时,指明该Mapper中的该方法使用指定数据源。
public interface SysUserMapper extends BaseMapper {
@UseDataSource("db2")
List selectAll();
}
实体类
当 @UseDataSource("datasourceName")
注解加在实体类上时,指明该实体类所有的增删改查操作使用指定数据源。
@Data
@Table("sys_user")
@UseDataSource("db3")
public class SysUser {
//...
}
读写分离
读写分离是基于 多数据源 来实现的,我们通过一些操作使查询操作访问 数据库A ,而新增、修改、删除等操作在 数据库B 上进行。
假如我们有四个库:master
、slave1
、 slave2
和 other
,我们的 增删改 操作在 master
数据源上进行,而查询操作随机在 slave1
和 slave2
上进行,而在 other
库上我们进行一些特定操作。那我们需要进行以下操作:
首先我们要先在配置文件中指定我们的数据源。
mybatis-flex:
datasource:
master:
url: jdbc:mysql://localhost:3306/master
username: root
password: root
slave1:
url: jdbc:mysql://localhost:3306/slave1
username: root
password: root
slave2:
url: jdbc:mysql://localhost:3306/slave2
username: root
password: root
other:
url: jdbc:mysql://localhost:3306/other
username: root
password: root
然后我们就可以来写我们的分片策略:
public class MyShardingStrategy implements DataSourceShardingStrategy {
@Override
public String doSharding(String currentDataSourceKey,
Object mapper,
Method method,
Object[] methodArgs) {
//对于other库的操作我们直接放行
if("other".equals(currentDataSourceKey)) {
return currentDataSourceKey;
}
//如果mapper方法以 insert、update、delete开头,则在master库中执行
if(StringUtil.startWith(method.getName(), "insert", "update", "delete")) {
return "master";
}
//除去以上方法均在 salve 库中执行
return "slave*";
}
}
动态表名
当用户在对数据进行操作时,向 MF 传入表名,它能够根据上下文信息(例如用户信息、应用信息等)动态修改当前的表。
适用场景如下:
- 多租户系统,当不同租户拥有不同的表时可使用动态表名来进行操作
- 分库分表,当我们为减轻数据库压力而使用分库分表时可使用动态表名来进行操作
首先在应用启动时,我们要调用 TableManager.setDynamicTableProcessor()
方法 来配置动态表名处理器即可,如下:
TableManager.setDynamicTableProcessor(new DynamicTableProcessor(){
@Override
public String process(String tableName){
return tableName + "_1";
}
});
我们配置完成后,在对数据库进行 增删改查 操作时,均会调用 process
方法,待获取到新的表名后再进行处理。
在某些情况下,我们想临时修改映射关系,而非通过 process
方法获取,我们可以进行以下操作:
try {
TableManager.setHintTableMapping("sys_user", "sys_user_1");
//业务处理
...
} finally {
TableManager.clear();
}
除了动态表名之外,MF 还支持 动态Scheme(模式),与动态表名配置类似,如下:
TableManager.setDynamicSchemeProcessor(new DynamicSchemeProcessor() {
@Override
public String process(String schema) {
return schema + "_1";
}
});
注意:动态Scheme的配置,只对使用了 @Table(schema = "xxx")
注解的实体类有效。
而当我们使用 SpringBoot 时,可以直接通过写配置类的方式进行注入。
@Configuration
public class DynamicConfiguration {
@Bean
public DynamicTableProcessor dynamicTableProcessor() {
DynamicTableProcessor processor = new ....;
return processor;
}
@Bean
public DynamicSchemaProcessor dynamicSchemaProcessor(){
DynamicSchemaProcessor processor = new ....;
return processor;
}
}
数据权限
我们在系统中可能会遇到不同用户、不同角色或者不同部门通过同一个接口查询时,得到的结果却不同的业务场景。MF 提供了 数据权限 的功能来供我们自定义数据权限。
自定义数据方言 IDialect
在自定义数据方言中,我们可以通过重写 forSelectByQuery
方法来构建通过 QueryWrapper 来查询的方法。
public class MyDataPermissionDialect extends CommonsDialectImpl {
@Override
public String forSelectByQuery(QueryWrapper wrapper) {
//此处可以获取当前用户信息、角色信息或部门信息
//通过用户信息或部门信息给查询增加条件
wrapper.and(...);
return super.buildSelectSql(wrapper);
}
}
重写IService方法
与 MP 中一样,MF也提供了 IService
接口以及默认的实现类,我们可以通过构建自己的 IServiceImpl
来实现该功能。
public class MyServiceImpl