更好用的Mybatis Plus:Mybatis Flex(下)

2023年 9月 28日 72.1k 0

更好用的Mybatis Plus:Mybatis Flex(下)
前言

上篇文章讲了 Mybaits Flex 的基础用法,这次讲一下 Mybatis Flex 的进阶用法,包含了一些 Mybatis Flex 核心的一些功能。(以下简称 MF)

逻辑删除

上篇文章讲到了 @Column 注解,其中有个属性为 isLogicDelete ,当这个属性为 true 时,则标识该字段为逻辑删除字段,MF 会识别到该字段并进相应的一些处理。

image.png

当你在实体类中指明了逻辑删除字段后,在查询时 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。

image.png

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 属性来帮助我们自定义插入时的监听器。

image.png

@Table 注解中提供了三个属性来帮我们监听:插入、修改和设置,我们需要分别继承 MF 的:InsertListenerUpdateListenerSetListener 进行相应的操作,如下:

image.png

image.png

更好用的Mybatis Plus:Mybatis Flex(下)-1

通过监听用户操作来帮我们完成一些额外的操作。

@Column 注解

@Column 注解中同样提供了数据填充的功能:onInsertValueonUpdateValue

image.png

类似于 Mybatis Plus 中 @TableField 注解中的 fill 属性,但是 MF 中的该功能会更强大,它甚至支持写 SQL 语句来填充值:

image.png

@Table 注解 与 @Column 注解的区别

在使用 @Table 注解中的数据填充功能时,我们设置的值是 基于Java层面的,在准备执行操作时会先触发监听器,监听器将操作执行完成之后,将参数传递至 ORM 进行处理。

而在使用 @Column 注解中的数据填充功能时,我们设置是基于 数据库层面的 ,我们设置的值会直接拼接到 SQL 中。

数据脱敏

在上一篇文章中我们讲了注解 @ColumnMask ,该注解是实现数据脱敏的核心注解,其仅有一个属性 value ,它指定了我们执行脱敏策略的名称。

image.png

而 MF 共提供了9种脱敏规则,如下:

image.png

  • 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 上进行。

    假如我们有四个库:masterslave1slave2other ,我们的 增删改 操作在 master 数据源上进行,而查询操作随机在 slave1slave2 上进行,而在 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

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论