Mybatis增强版:Mybatis-Plus(使用的最多,老牌Mybatis增强框架,2016年开源)、Fluent-MyBatis(阿里云开发的Mybatis增强框架,来自阿里云.云效产品团队)、Mybatis-Flex。
Flex英文单词意思是灵活,Mybatis-Flex官方文档中多处强调‘灵活’一词。
1、对比特性

功能或特点MyBatis-FlexMyBatis-PlusFluent-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较为显著的特点是,多了联表查询和在Mybatis-plus中收费的Mybatis-Flex免费使用。
联表查询在日常开发中使用的比较多,Mybatis-Flex支持这个,还是很不错的。
下面简单介绍一下Mybatis-Flex关于联表查询。
2、Mybatis-Flex联表查询
在 MyBatis-Flex 中,内置了 3 种方案进行联表查询,比如 一对多、一对一、多对一、多对多等场景,它们分别是:
方案1:Relations 注解
方案2:Field Query
方案3:Join Query

方案1:Relations 注解

在 MyBatis-Flex 中,提供了 4 个 Relations 注解,他们分别是:

RelationOneToOne:用于一对一的场景
RelationOneToMany:用于一对多的场景
RelationManyToOne:用于多对一的场景
RelationManyToMany:用于多对多的场景
添加了以上配置的实体类,在通过 BaseMapper 的方法查询数据时,需要调用 select***WithRelations() 方法,Relations 注解才能生效。 否则 MyBatis-Flex 自动忽略 Relations 注解。
BaseMapper 提供的 withRelations 方法。

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

public class Account implements Serializable {

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

    private String userName;

    @RelationOneToOne(selfField = "id", targetField = "accountId")
    private IDCard idCard;//关联字段

    //getter setter
}
@Table(value = "tb_idcard")
public class IDCard implements Serializable {

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

    //getter setter
}

@RelationOneToOne 配置描述:

  • selfField 当前实体类的属性
  • targetField 目标对象的关系实体类的属性

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

假设数据库 5 条 Account 数据,然后进行查询:

List<Account> accounts = accountMapper.selectAllWithRelations();
System.out.println(accounts);

其执行的 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'}}
 ]

在 Account.java 和 IDCard.java 示例中,若他们的关联关系是通过 中间表 的方式进行关联,则需要添加 joinTable joinSelfColumn joinTargetColumn 配置,如下所示:

public class Account implements Serializable {

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

    private String userName;

    @RelationOneToOne(
         joinTable = "tb_idcard_mapping"
        ,joinSelfColumn = "account_id"
        ,joinTargetColumn = "idcard_id"
        ,selfField = "id"
        ,targetField = "accountId")
    private IDCard idCard;

    //getter setter
}

其他的RelationManyToOne(用于多对一的场景)、RelationManyToMany(用于多对多的场景)和RelationOneToMany(用于一对多的场景)差不多用法,可以查看官网:Mybatis-Flex官网地址

其中还有一点比较好的,就是父子关系查询。

父子关系查询
比如在一些系统中,比如菜单会有一些父子关系,例如菜单表如下:

CREATE TABLE `tb_menu`
(
    `id`        INTEGER auto_increment,
    `parent_id`        INTEGER,
    `name`      VARCHAR(100)
);

Menu.java 定义如下:

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

    private Long id;

    private Long parentId;

    private String name;

    @RelationManyToOne(selfField = "parentId", targetField = "id")
    private Menu parent;

    @RelationOneToMany(selfField = "id", targetField = "parentId")
    private List<Menu> children;

    //getter setter
}

查询顶级菜单:

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

List<Menu> menus = menuMapper.selectListWithRelationsByQuery(qw);
System.out.println(JSON.toJSONString(menus));

SQL 执行如下:

SELECT `id`, `parent_id`, `name` FROM `tb_menu` WHERE `parent_id` = 0
SELECT `id`, `parent_id`, `name` FROM `tb_menu` WHERE id = 0
SELECT `id`, `parent_id`, `name` FROM `tb_menu` WHERE parent_id IN (1, 2, 3)

JSON 输出内容如下:

[
  {
    "children": [
      {
        "id": 4,
        "name": "子菜单",
        "parentId": 1
      },
      {
        "id": 5,
        "name": "子菜单",
        "parentId": 1
      }
    ],
    "id": 1,
    "name": "顶级菜单1",
    "parentId": 0
  },
  {
    "children": [],
    "id": 2,
    "name": "顶级菜单2",
    "parentId": 0
  },
  {
    "children": [
      {
        "id": 6,
        "name": "子菜单",
        "parentId": 3
      },
      {
        "id": 7,
        "name": "子菜单",
        "parentId": 3
      },
      {
        "id": 8,
        "name": "子菜单",
        "parentId": 3
      }
    ],
    "id": 3,
    "name": "顶级菜单3",
    "parentId": 0
  }
]

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

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

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

RelationManager.setMaxDepth(10) 的配置,只在当前第一次查询有效,查询后会清除设置。

方案2:Field Query
这个方案,不简单明了。大概的代码长下面个样子。

QueryWrapper queryWrapper = QueryWrapper.create()
        .select().from(ARTICLE)
        .where(ARTICLE.id.ge(100));

List<Article> articles = mapper.selectListByQuery(queryWrapper
    , fieldQueryBuilder -> fieldQueryBuilder
        .field(Article::getCategories) // 或者 .field("categories")
        .queryWrapper(article -> QueryWrapper.create()
            .select().from(CATEGORY)
            .where(CATEGORY.id.in(
                    select("category_id").from("article_category_mapping")
                    .where("article_id = ?", article.getId())
            )
        )
    );

方案3:Join Query

这个方案也是不咋滴,代码长得如下:

QueryWrapper queryWrapper = QueryWrapper.create()
        .select(USER.USER_ID, USER.USER_NAME, ROLE.ALL_COLUMNS)
        .from(USER.as("u"))
        .leftJoin(USER_ROLE).as("ur").on(USER_ROLE.USER_ID.eq(USER.USER_ID))
        .leftJoin(ROLE).as("r").on(USER_ROLE.ROLE_ID.eq(ROLE.ROLE_ID));
List<UserVO> userVOS = userMapper.selectListByQueryAs(queryWrapper, UserVO.class);
userVOS.forEach(System.err::println);

方案2和3总的来说,都不怎么行,不过比没有强点。个人使用的话,还是习惯用Mybatis的mapper.xml写法。

其他特性的话,和Mybatis-Plus大同小异。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐