目录
前言
更轻量
更灵活
更高的性能
功能对比
性能对比
代码实践
一对一关联查询 @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
假设有一个账户,账户有身份证,账户和身份证的关系是一对一的关系,代码如下所示:
@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'}}
]
假设一个账户有很多本书籍,一本书只能归属一个账户所有;账户和书籍的关系是一对多的关系,代码如下:
@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;
}
假设一个账户有很多本书籍,一本书只能归属一个账户所有;账户和书籍的关系是一对多的关系,书籍和账户的关系为多对一的关系,代码如下:
@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;
}
假设一个账户可以有多个角色,一个角色也可以有多个账户,他们是多对多的关系,需要通过中间表 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
在以上的父子关系查询中,默认的递归查询深度为 3 个层级,若需要查询指定递归深度,需要添加如下配置:
QueryWrapper qw = QueryWrapper.create();
qw.where(MENU.PARENT_ID.eq(0));
//设置递归查询深度为 10 层
RelationManager.setMaxDepth(10);
List
在 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 extends Serializable> 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 extends Serializable> 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);
}
}
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
的所有优点,更多的大家可以探索一番。