大家好,我是鹏磊。
https://www.ddkk.com
前言
MyBatis-Flex 是一个优雅的 MyBatis 增强框架,它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用 Mybaits-Flex 链接任何数据库,其内置的 QueryWrapper亮点 帮助我们极大的减少了 SQL 编写的工作的同时,减少出错的可能性。
更轻量
MyBatis-Flex 除了 MyBatis 本身,再无任何第三方依赖,因此会带来更高的自主性、把控性和稳定性。在任何一个系统中,依赖越多,稳定性越差。
更灵活
MyBatis-Flex 提供了非常灵活的 QueryWrapper,支持关联查询、多表查询、多主键、逻辑删除、乐观锁更新、数据填充、数据脱敏、等等....
更高的性能
MyBatis-Flex 通过独特的架构,没有任何 MyBatis 拦截器、在 SQL 执行的过程中,没有任何的 SQL Parse,因此会带来指数级的性能增长。
功能对比
|
|
|
|
|
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
性能对比
-
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.添加依赖
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-processor</artifactId>
<version>1.5.3</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.0.31</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
一对一关联查询 @RelationOneToOne
假设有一个账户,账户有身份证,账户和身份证的关系是一对一的关系,代码如下所示:
@Data
publicclassAccountimplementsSerializable {
@Id(keyType = KeyType.Auto)
private Long id;
private String userName;
@RelationOneToOne(selfField = "id", targetField = "accountId")
private IDCard idCard;
}
@Data
@Table(value = "tb_idcard")
publicclassIDCardimplementsSerializable {
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
publicclassAccountimplementsSerializable {
@Id(keyType = KeyType.Auto)
private Long id;
private String userName;
@RelationOneToMany(selfField = "id", targetField = "accountId")
private List<Book> books;
}
@Data
@Table(value = "tb_book")
publicclassBookimplementsSerializable {
@Id(keyType = KeyType.Auto)
private Long id;
private Long accountId;
private String title;
}
若 Account.books 是一个 Map,而非 List,那么,我们需要通过配置 mapKeyField 来指定列来充当 Map 的 Key, 如下代码所示:
@Data
publicclassAccountimplementsSerializable {
@Id(keyType = KeyType.Auto)
private Long id;
private String userName;
@RelationOneToMany(selfField = "id", targetField = "accountId"
, mapKeyField = "id")//使用 Book 的 id 来填充这个 map 的 key
private Map<Long, Book> books;
}
多对一关联查询 @RelationManyToOne
假设一个账户有很多本书籍,一本书只能归属一个账户所有;账户和书籍的关系是一对多的关系,书籍和账户的关系为多对一的关系,代码如下:
@Data
publicclassAccountimplementsSerializable {
@Id(keyType = KeyType.Auto)
private Long id;
private String userName;
}
@Data
@Table(value = "tb_book")
publicclassBookimplementsSerializable {
@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
publicclassAccountimplementsSerializable {
@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<Role> roles;
}
@Data
@Table(value = "tb_role")
publicclassRoleimplementsSerializable {
private Long id;
private String name;
}
父子关系查询
@Data
@Table(value = "tb_menu")
publicclassMenuimplementsSerializable {
private Long id;
private Long parentId;
private String name;
@RelationOneToMany(selfField = "id", targetField = "parentId")
private List<Menu> children;
}
在以上的父子关系查询中,默认的递归查询深度为 3 个层级,若需要查询指定递归深度,需要添加如下配置:
QueryWrapper qw = QueryWrapper.create();
qw.where(MENU.PARENT_ID.eq(0));
//设置递归查询深度为 10 层
RelationManager.setMaxDepth(10);
List<Menu> menus = menuMapper.selectListWithRelationsByQuery(qw);
链式操作
在 MyBatis-Flex 中,内置了 QueryChain.java 、 UpdateChain.java 以及 DbChain.java 用于对数据进行链式查询操作和链式操作(修改和删除)。
例如,查询文章列表代码如下:
@SpringBootTest
classArticleServiceTest {
@Autowired
ArticleService articleService;
@Test
voidtestChain() {
List<Article> articles = articleService.queryChain()
.select(ARTICLE.ALL_COLUMNS)
.from(ARTICLE)
.where(ARTICLE.ID.ge(100))
.list();
}
}
若不是在 Service 中,我们也可以通过 QueryChain.of(mapper) 方法,自己创建一个 QueryChain 实例,代码如下:
List<Article> 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")
publicclassAccountServiceImplextendsCacheableServiceImpl<MyAccountMapper, Account> {
@Override
@CacheEvict(allEntries = true)
publicbooleanremove(QueryWrapper query) {
returnsuper.remove(query);
}
@Override
@CacheEvict(key = "#id")
publicbooleanremoveById(Serializable id) {
returnsuper.removeById(id);
}
@Override
@CacheEvict(allEntries = true)
publicbooleanremoveByIds(Collection<? extends Serializable> ids) {
returnsuper.removeByIds(ids);
}
// 根据查询条件更新时,实体类主键可能为 null。
@Override
@CacheEvict(allEntries = true)
publicbooleanupdate(Account entity, QueryWrapper query) {
returnsuper.update(entity, query);
}
@Override
@CacheEvict(key = "#entity.id")
publicbooleanupdateById(Account entity, boolean ignoreNulls) {
returnsuper.updateById(entity, ignoreNulls);
}
@Override
@CacheEvict(allEntries = true)
publicbooleanupdateBatch(Collection<Account> entities, int batchSize) {
returnsuper.updateBatch(entities, batchSize);
}
@Override
@Cacheable(key = "#id")
public Account getById(Serializable id) {
returnsuper.getById(id);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
public Account getOne(QueryWrapper query) {
returnsuper.getOne(query);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
public <R> R getOneAs(QueryWrapper query, Class<R> asType) {
returnsuper.getOneAs(query, asType);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
public List<Account> list(QueryWrapper query) {
returnsuper.list(query);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
public <R> List<R> listAs(QueryWrapper query, Class<R> asType) {
returnsuper.listAs(query, asType);
}
// 无法通过注解进行缓存操作
@Override
@Deprecated
public List<Account> listByIds(Collection<? extends Serializable> ids) {
returnsuper.listByIds(ids);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #query.toSQL()")
publiclongcount(QueryWrapper query) {
returnsuper.count(query);
}
@Override
@Cacheable(key = "#root.methodName + ':' + #page.getPageSize() + ':' + #page.getPageNumber() + ':' + #query.toSQL()")
public <R> Page<R> pageAs(Page<R> page, QueryWrapper query, Class<R> asType) {
returnsuper.pageAs(page, query, asType);
}
}
SQL审计
Mybaits-Flex 的 SQL 审计功能,默认是关闭的,若开启审计功能,需添加如下配置。
如果你近期准备面试跳槽,建议在ddkk.com在线刷题,涵盖 一万+ 道 Java 面试题,几乎覆盖了所有主流技术面试题,还有市面上最全的技术五百套,精品系列教程,免费提供。
AuditManager.setAuditEnable(true)
MyBatis-Flex 内置了一个名为 MessageFactory 的接口,我们只需实现该接口,并为 AuditManager 配置新的 MessageFactory 即可,如下所示:
public classMyMessageFactoryimplementsMessageFactory {
@Override
public AuditMessage create() {
AuditMessagemessage=newAuditMessage();
// 在这里
// 设置 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 classMyMessageReporterimplementsMessageReporter {
@Override
publicvoidsendMessages(List<AuditMessage> 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<Row> rows = Db.selectAll("tb_account");
System.out.println(rows);
}finally{
DataSourceKey.clear();
}
整体来讲,这个框架是Mybatis的增强版,几乎集成了mybatis plus、jooq、fluent mybatis的所有优点,更多的大家可以探索一番。
https://www.ddkk.com

