JAVA面试题分享四百一十三:探秘MyBatis-Flex:超越Mybatis-plus

目录

前言

更轻量

更灵活

更高的性能

功能对比

性能对比

代码实践

一对一关联查询 @RelationOneToOne

一对多关联查询 @RelationOneToMany

多对一关联查询 @RelationManyToOne

多对多关联查询 @RelationManyToMany

父子关系查询

链式操作

数据脱敏

数据缓存

SQL审计

多数据源


前言

MyBatis-Flex 是一个优雅的 MyBatis 增强框架,它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用 Mybaits-Flex 链接任何数据库,其内置的 QueryWrapper亮点 帮助我们极大的减少了 SQL 编写的工作的同时,减少出错的可能性。

更轻量

MyBatis-Flex 除了 MyBatis 本身,再无任何第三方依赖,因此会带来更高的自主性、把控性和稳定性。在任何一个系统中,依赖越多,稳定性越差。

更灵活

MyBatis-Flex 提供了非常灵活的 QueryWrapper,支持关联查询、多表查询、多主键、逻辑删除、乐观锁更新、数据填充、数据脱敏、等等....

更高的性能

MyBatis-Flex 通过独特的架构,没有任何 MyBatis 拦截器、在 SQL 执行的过程中,没有任何的 SQL Parse,因此会带来指数级的性能增长。

功能对比
功能或特点 MyBatis-Flex MyBatis-Plus Fluent-MyBatis
对 entity 的基本增删改查
分页查询
分页查询之总量缓存
分页查询无 SQL 解析设计(更轻量,及更高性能)
多表查询:from 多张表
多表查询:left join、inner join 等等
多表查询:union,union all
单主键配置
多种 id 生成策略
支持多主键、复合主键
字段的 typeHandler 配置
除了 MyBatis,无其他第三方依赖(更轻量)
QueryWrapper 是否支持在微服务项目下进行 RPC 传输 未知
逻辑删除
乐观锁
SQL 审计
数据填充 ✔️  (收费)
数据脱敏 ✔️  (收费)
字段权限 ✔️  (收费)
字段加密 ✔️  (收费)
字典回写 ✔️  (收费
Db + Row
Entity 监听
多数据源支持 借助其他框架或收费
多数据源是否支持 Spring 的事务管理,比如 @Transactional 和 TransactionTemplate 等
多数据源是否支持 "非Spring" 项目
多租户
动态表名
动态 Schema
性能对比
  • MyBatis-Flex 查询单条数据的速度,大概是 MyBatis-Plus 的 5 ~ 10+ 倍。

  • MyBatis-Flex 查询 10 条数据的速度,大概是 MyBatis-Plus 的 5~10 倍左右。

  • Mybatis-Flex 分页查询速度,大概是 Mybatis-Plus 的 5~10 倍左右。

  • Mybatis-Flex 数据更新速度,大概是 Mybatis-Plus 的 5~10+ 倍。

官网:https://mybatis-flex.com/

代码实践

除了Mybatis-plus带的那些功能,Mybatis-Flex提供了多主键、复合主键功能;提供了关联查询;特别是关联查询在日常业务开发碰到的场景很多。

Mybatis-Flex提供了一对一、一对多、多对一、多对多的场景。

1.添加依赖


    com.mybatis-flex
    mybatis-flex-spring-boot-starter
    1.5.3


    com.mybatis-flex
    mybatis-flex-processor
    1.5.3


    com.mysql
    mysql-connector-j
    8.0.31


    com.zaxxer
    HikariCP


    org.projectlombok
    lombok
    true

一对一关联查询 @RelationOneToOne

假设有一个账户,账户有身份证,账户和身份证的关系是一对一的关系,代码如下所示:

@Data
public class Account implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;

    private String userName;

    @RelationOneToOne(selfField = "id", targetField = "accountId")
    private IDCard idCard;
}

@Data
@Table(value = "tb_idcard")
public class IDCard implements Serializable {

    private Long accountId;
    private String cardNo;
    private String content;
}

若 selfField 是主键,且当前表只有 1 个主键时,可以不填写。因此,以上的配置可以简化为 @RelationOneToOne(targetField = "accountId")

执行sql:

SELECT `id`, `user_name`, `age` FROM `tb_account`

SELECT `account_id`, `card_no`, `content` FROM `tb_idcard`
WHERE account_id IN (1, 2, 3, 4, 5)

结果打印:

[
 Account{id=1, userName='孙悟空', age=18, idCard=IDCard{accountId=1, cardNo='0001', content='内容1'}},
 Account{id=2, userName='猪八戒', age=19, idCard=IDCard{accountId=2, cardNo='0002', content='内容2'}},
 Account{id=3, userName='沙和尚', age=19, idCard=IDCard{accountId=3, cardNo='0003', content='内容3'}},
 Account{id=4, userName='六耳猕猴', age=19, idCard=IDCard{accountId=4, cardNo='0004', content='内容4'}},
 Account{id=5, userName='王麻子叔叔', age=19, idCard=IDCard{accountId=5, cardNo='0005', content='内容5'}}
 ]

一对多关联查询 @RelationOneToMany

假设一个账户有很多本书籍,一本书只能归属一个账户所有;账户和书籍的关系是一对多的关系,代码如下:

@Data
public class Account implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;

    private String userName;

    @RelationOneToMany(selfField = "id", targetField = "accountId")
    private List books;
}

@Data
@Table(value = "tb_book")
public class Book implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;
    private Long accountId;
    private String title;
}

若 Account.books 是一个 Map,而非 List,那么,我们需要通过配置 mapKeyField 来指定列来充当 Map 的 Key, 如下代码所示:

@Data
public class Account implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;

    private String userName;

    @RelationOneToMany(selfField = "id", targetField = "accountId"
        , mapKeyField = "id") //使用 Book 的 id 来填充这个 map 的 key
    private Map books;
}

多对一关联查询 @RelationManyToOne

假设一个账户有很多本书籍,一本书只能归属一个账户所有;账户和书籍的关系是一对多的关系,书籍和账户的关系为多对一的关系,代码如下:

@Data
public class Account implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;

    private String userName;
}

@Data
@Table(value = "tb_book")
public class Book implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;
    private Long accountId;
    private String title;

    @RelationManyToOne(selfField = "accountId", targetField = "id")
    private Account account;
}

多对多关联查询 @RelationManyToMany

假设一个账户可以有多个角色,一个角色也可以有多个账户,他们是多对多的关系,需要通过中间表 tb_role_mapping 来维护:

@Data
public class Account implements Serializable {

    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;

    @RelationManyToMany(
            joinTable = "tb_role_mapping", // 中间表
            selfField = "id", joinSelfColumn = "account_id",
            targetField = "id", joinTargetColumn = "role_id"
    )
    private List roles;
}

@Data
@Table(value = "tb_role")
public class Role implements Serializable {

    private Long id;
    private String name;
}

父子关系查询

@Data
@Table(value = "tb_menu")
public class Menu implements Serializable {

    private Long id;

    private Long parentId;

    private String name;

    @RelationOneToMany(selfField = "id", targetField = "parentId")
    private List children;
}

在以上的父子关系查询中,默认的递归查询深度为 3 个层级,若需要查询指定递归深度,需要添加如下配置:

QueryWrapper qw = QueryWrapper.create();
qw.where(MENU.PARENT_ID.eq(0));

//设置递归查询深度为 10 层
RelationManager.setMaxDepth(10);
List menus = menuMapper.selectListWithRelationsByQuery(qw);

链式操作

在 MyBatis-Flex 中,内置了 QueryChain.java 、 UpdateChain.java 以及 DbChain.java 用于对数据进行链式查询操作和链式操作(修改和删除)。

例如,查询文章列表代码如下:

@SpringBootTest
class ArticleServiceTest {

    @Autowired
    ArticleService articleService;

    @Test
    void testChain() {
        List
 articles = articleService.queryChain()             .select(ARTICLE.ALL_COLUMNS)             .from(ARTICLE)             .where(ARTICLE.ID.ge(100))             .list();     } }

若不是在 Service 中,我们也可以通过 QueryChain.of(mapper) 方法,自己创建一个 QueryChain 实例,代码如下:

List
 articles = QueryChain.of(mapper)     .select(ARTICLE.ALL_COLUMNS)     .from(ARTICLE)     .where(ARTICLE.ID.ge(100))     .list();

数据脱敏

对真实数据进行改造并提供使用, 如身份证号、手机号、卡号、客户号等个人信息都需要进行数据脱敏 MyBatis-Flex提供了@ColumnMask() 注解,以及内置的9种脱敏规则,帮助开发者方便的进行数据脱敏。例如:

@Table("tb_account")
public class Account {

    @Id(keyType = KeyType.Auto)
    private Long id;

    @ColumnMask(Masks.CHINESE_NAME)
    private String userName;
}

除此之外,MyBatis-Flex 还提供了如下的8种脱敏规则,方便开发者直接使用:

  • 手机号脱敏

  • 固定电话脱敏

  • 身份证号脱敏

  • 车牌号脱敏

  • 地址脱敏

  • 邮件脱敏

  • 密码脱敏

  • 银行卡号脱敏

当 Mybaits-Flex 内置的9种脱敏规则无法满足要求时,我们还可以自定义脱敏规则,其步骤如下:

MaskManager.registerMaskProcessor("自定义规则名称"
        , data -> {
            return data;
        })

在某些场景下,程序希望查询得到的数据是原始数据,而非脱敏数据。比如要去查询用户的手机号,然后给用户发送短信。又或者说,我们进入编辑页面编辑用户数据, 如果编辑页面展示的是脱敏数据,然后再次点击保存,那么数据库的真实数据也会被脱敏覆盖。

因此,MaskManager 提供了 execWithoutMask、skipMask、restoreMask 三个方法来处理这种场景:

try {
    MaskManager.skipMask();
    
    //此处查询到的数据不会进行脱敏处理
    accountMapper.selectListByQuery(...);
} finally {
    MaskManager.restoreMask();
}

数据缓存

MyBatis-Flex 是一个 MyBatis 增强框架,所以您可以使用 MyBatis 提供的二级缓存来作为数据缓存。但是它仍然有很多的缺点,比如不适用于分布式环境,在这里推荐使用 Spring Cache 模块来处理数据缓存。

@Service
@CacheConfig(cacheNames = "account")
public class AccountServiceImpl extends CacheableServiceImpl {
    
    @Override
    @CacheEvict(allEntries = true)
    public boolean remove(QueryWrapper query) {
        return super.remove(query);
    }

    @Override
    @CacheEvict(key = "#id")
    public boolean removeById(Serializable id) {
        return super.removeById(id);
    }

    @Override
    @CacheEvict(allEntries = true)
    public boolean removeByIds(Collection ids) {
        return super.removeByIds(ids);
    }

    // 根据查询条件更新时,实体类主键可能为 null。
    @Override
    @CacheEvict(allEntries = true)
    public boolean update(Account entity, QueryWrapper query) {
        return super.update(entity, query);
    }

    @Override
    @CacheEvict(key = "#entity.id")
    public boolean updateById(Account entity, boolean ignoreNulls) {
        return super.updateById(entity, ignoreNulls);
    }

    @Override
    @CacheEvict(allEntries = true)
    public boolean updateBatch(Collection entities, int batchSize) {
        return super.updateBatch(entities, batchSize);
    }

    @Override
    @Cacheable(key = "#id")
    public Account getById(Serializable id) {
        return super.getById(id);
    }

    @Override
    @Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
    public Account getOne(QueryWrapper query) {
        return super.getOne(query);
    }

    @Override
    @Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
    public  R getOneAs(QueryWrapper query, Class asType) {
        return super.getOneAs(query, asType);
    }

    @Override
    @Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
    public List list(QueryWrapper query) {
        return super.list(query);
    }

    @Override
    @Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
    public  List listAs(QueryWrapper query, Class asType) {
        return super.listAs(query, asType);
    }

    // 无法通过注解进行缓存操作
    @Override
    @Deprecated
    public List listByIds(Collection ids) {
        return super.listByIds(ids);
    }

    @Override
    @Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
    public long count(QueryWrapper query) {
        return super.count(query);
    }

    @Override
    @Cacheable(key = "#root.methodName + ':' + #page.getPageSize() + ':' + #page.getPageNumber() + ':' + #query.toSQL()")
    public  Page pageAs(Page page, QueryWrapper query, Class asType) {
        return super.pageAs(page, query, asType);
    }
  
}

SQL审计

Mybaits-Flex 的 SQL 审计功能,默认是关闭的,若开启审计功能,需添加如下配置。

AuditManager.setAuditEnable(true)

MyBatis-Flex 内置了一个名为 MessageFactory 的接口,我们只需实现该接口,并为 AuditManager 配置新的 MessageFactory 即可,如下所示:

public class MyMessageFactory implements MessageFactory {
    
    @Override
    public AuditMessage create() {
        AuditMessage message = new AuditMessage();
       
        // 在这里
        // 设置 message 的基础内容,包括 platform、module、url、user、userIp、hostIp 内容
        // 剩下的 query、queryParams、queryCount、queryTime、elapsedTime 为 mybatis-flex 设置
        
        return message;
    }
}

并为 AuditManager 配置新写的 MyMessageFactory

MessageFactory creator = new MyMessageFactory();
AuditManager.setMessageFactory(creator);

自定义 MessageReporter:

public class MyMessageReporter implements MessageReporter {

    @Override
    public void sendMessages(List messages) {
        //在这里把 messages 审计日志发送到指定位置
        //比如 
        // 1、通过 http 协议发送到指定服务器
        // 2、通过日志工具发送到日志平台
        // 3、通过 Kafka 等 MQ 发送到指定平台
    }

}

自定义 MessageCollector:

public class MyMessageCollector implements MessageCollector {
    @Override
    public void collect(AuditMessage auditMessage) {
        System.out.println(auditMessage.getFullSql());
    }
}

MyBatis-Flex 内置了两个 Collector,他们分别是:

  • ScheduledMessageCollector 定时把消息通过 MessageReporter 发送到指定位置。

  • ConsoleMessageCollector 使用其把消息输出到控制台。

多数据源

MyBaits-Flex 内置了功能完善的多数据源支持,不需要借助第三方插件或者依赖,开箱即用, 支持包括 druid、hikaricp、dbcp2、beecp 在内的任何数据源,MyBatis-Flex 多数据源配置如下:

mybatis-flex:
  datasource:
    ds1:
      url: jdbc:mysql://127.0.0.1:3306/db
      username: root
      password: 123456
    ds2:
      url: jdbc:mysql://127.0.0.1:3306/db2
      username: root
      password: 123456

MyBatis-Flex 提供了 4 种方式来配置数据源:

  • 编码,使用DataSourceKey.use 方法。

  • @UseDataSource("dataSourceName") 在 Mapper 类上,添加注解,用于指定使用哪个数据源。

  • @UseDataSource("dataSourceName") 在 Mapper 方法上,添加注解,用于指定使用哪个数据源。

  • @Table(dataSource="dataSourceName") 在 Entity 类上添加注解,该 Entity 的增删改查请求默认使用该数据源。

优先级:DataSourceKey.use() > @UseDataSource()在方法上 > @UseDataSource()在类上 >@Table(dataSource="...")

try{
    DataSourceKey.use("ds2")
    List rows = Db.selectAll("tb_account");
    System.out.println(rows);
}finally{
    DataSourceKey.clear();
}

整体来讲,这个框架是Mybatis的增强版,几乎集成了mybatis plus、jooq、fluent mybatis的所有优点,更多的大家可以探索一番。

你可能感兴趣的:(JAVA,面试题分享,java,mybatis,面试)