MyBatis-Flex 夯爆了,但更值得深用
MyBatis-Flex 是一个优雅的 MyBatis 增强框架,它非常轻量、同时拥有极高的性能与灵活性。我们可以轻松的使用 Mybaits-Flex 链接任何数据库,其内置的 QueryWrapper^亮点 帮助我们极大的减少了 SQL 编写的工作的同时,减少出错的可能性。
目录
-
- 一、MyBatis-Flex 综合介绍
- 二、MyBatis-Flex 快速上手
- 三、功能使用
-
- 1、三大注解@Table、@Id 、@Column 注解的使用
- 2、日志打印
- 3、逻辑删除
- 4、乐观锁
- 5、数据填充
- 6、数据脱敏@ColumnMask
- 7、多数据源
- 8、读写分离
- 8、单个查询
- 9、分页查询
- 10、增删改功能
- 11、关联查询
- 12、数据源加密(Database Credential Encryption)
- 13、动态表名(Dynamic Table Name)
- 14、事务管理(Transaction Management)
- 15、数据权限(Data Permission)
- 16、字段权限(Field Permission)
- 17、字段加密(Field Encryption)
- 18、字典回写(Dictionary Backfill)
- 19、枚举属性处理(Enum Support)
- 20、多租户支持(Multi-Tenancy)
- 三、结语:工具是舟,人是渡者
在 Java 持久层框架的江湖中,MyBatis 以其灵活、轻量、可控性强的特点,长久占据一席之地。而在这片生态沃土之上,MyBatis-Flex正以“轻量之躯,承载企业级重担”的姿态,悄然崛起。它不是简单的 CRUD 工具,而是一套融合了设计理念、工程智慧与安全哲学的现代化持久层解决方案。正如其名——“Flex”,灵活、敏捷、可塑性强,真正做到了“工具为我所用,而非我为工具所困”。
一、MyBatis-Flex 综合介绍
1、功能对比
| 功能或特点 | 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 | ✅ | ❌ | ❌ |
2、性能对比
官方测试了 MyBatis-Flex 和 Mybaits-Plus 的「性能」对比。Mybaits-Plus 是一个非常优秀 Mybaits 增强框架,
使用 h2 数据库,在初始化的时候分别为 mybatis-flex 和 mybatis-plus 创建两个不同的数据库, 但是完全一样的数据结构、数据内容和数据量(每个库 2w 条数据)。
开始之前先进行预热,之后通过打印时间戳的方式进行对比,谁消耗的时间越少,则性能越高。
测试源码: https://gitee.com/mybatis-flex/mybatis-benchmark
在官方测试的结果如下:
-
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+ 倍。
总之夯爆了
3、MyBatis-Flex 支持的数据库类型
MyBatis-Flex 支持的数据库类型,如下表格所示,还可以通过自定义方言的方式,持续添加更多的数据库支持。
| 数据库 | 描述 |
|---|---|
| mysql | MySQL 数据库 |
| mariadb | MariaDB 数据库 |
| oracle | Oracle11g 及以下数据库 |
| oracle12c | Oracle12c 及以上数据库 |
| db2 | DB2 数据库 |
| H2 | H2 数据库 |
| hsql | HSQL 数据库 |
| sqlite | SQLite 数据库 |
| postgresql | PostgreSQL 数据库 |
| sqlserver2005 | SQLServer2005 数据库 |
| sqlserver | SQLServer 数据库 |
| dm | 达梦数据库 |
| xugu | 虚谷数据库 |
| kingbasees | 人大金仓数据库 |
| phoenix | Phoenix HBase 数据库 |
| gauss | Gauss 数据库 |
| clickhouse | ClickHouse 数据库 |
| gbase | 南大通用(华库)数据库 |
| gbase-8s | 南大通用数据库 GBase 8s |
| oscar | 神通数据库 |
| sybase | Sybase ASE 数据库 |
| OceanBase | OceanBase 数据库 |
| Firebird | Firebird 数据库 |
| derby | Derby 数据库 |
| highgo | 瀚高数据库 |
| cubrid | CUBRID 数据库 |
| goldilocks | GOLDILOCKS 数据库 |
| csiidb | CSIIDB 数据库 |
| hana | SAP_HANA 数据库 |
| impala | Impala 数据库 |
| vertica | Vertica 数据库 |
| xcloud | 行云数据库 |
| redshift | 亚马逊 redshift 数据库 |
| openGauss | 华为 openGauss 数据库 |
| TDengine | TDengine 数据库 |
| informix | Informix 数据库 |
| greenplum | Greenplum 数据库 |
| uxdb | 优炫数据库 |
| Doris | Doris数据库 |
| Hive SQL | Hive 数据库 |
| lealone | Lealone 数据库 |
| sinodb | 星瑞格数据库 |
夯爆了
二、MyBatis-Flex 快速上手
1、添加Maven依赖
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
<version>1.11.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
注意的是SpringBoot2.x是mybatis-flex-spring-boot-starter SpringBoot3.x是mybatis-flex-spring-boot3-starter
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot3-starter</artifactId>
<version>1.11.5</version>
</dependency>
2、对 Spring Boot 项目进行配置
application.yaml配置
# DataSource Config
spring:
datasource:
url: jdbc:mysql://localhost:3306/tempdatabase?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
password: sa
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
启动类打扫描注解:@MapperScan("com.example.mapper")
3、编写实体类和 Mapper 接口
实体类
就拿User实体类举例
@Data
@Table("user")
public class User {
@Id(keyType = KeyType.Auto)
private Long id;
private String name;
private Integer age;
private String email;
}
- 使用
@Table("tb_account")设置实体类与表名的映射关系 - 使用
@Id(keyType = KeyType.Auto)标识主键为自增
Mapper接口
Mapper 接口继承 BaseMapper 接口:
public interface UserMapper extends BaseMapper<User> {
}
4、使用测试
@SpringBootTest
public class SpringBootMyBatisFlexApplicationTest {
@Autowired
private UserMapper userMapper;
@Test
void selectOne() {
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.where(User::getId).eq(1L);
User user = userMapper.selectOneByQuery(queryWrapper);
System.out.println(user);
}
}
//输出:
User(id=1, name=Jone, age=20, email=test1@baomidou.com)
三、功能使用
1、三大注解@Table、@Id 、@Column 注解的使用
Table 注解的使用
MyBatis-Flex 中,@Table 主要是用于给 Entity 实体类添加标识,用于描述 实体类 和 数据库表 的关系,以及对实体类进行的一些 功能辅助。
@Table 的定义如下:
public @interface Table {
/**
* 显式指定表名称
*/
String value();
/**
* 数据库的 schema(模式)
*/
String schema() default "";
/**
* 默认为 驼峰属性 转换为 下划线字段
*/
boolean camelToUnderline() default true;
/**
* 默认使用哪个数据源,若系统找不到该指定的数据源时,默认使用第一个数据源
*/
String dataSource() default "";
/**
* 监听 entity 的 insert 行为
*/
Class<? extends InsertListener> onInsert() default NoneListener.class;
/**
* 监听 entity 的 update 行为
*/
Class<? extends UpdateListener> onUpdate() default NoneListener.class;
/**
* 监听 entity 的查询数据的 set 行为,用户主动 set 不会触发
*/
Class<? extends SetListener> onSet() default NoneListener.class;
/**
* 在某些场景下,我们需要手动编写 Mapper,可以通过这个注解来关闭 APT 的 Mapper 生成
*/
boolean mapperGenerateEnable() default true;
}
@Id 主键的使用
public @interface Id {
/**
* ID 生成策略,默认为 none
*
* @return 生成策略
*/
KeyType keyType() default KeyType.None;
/**
* 若 keyType 类型是 sequence, value 则代表的是
* sequence 序列的 sql 内容
* 例如:select SEQ_USER_ID.nextval as id from dual
*
* 若 keyType 是 Generator,value 则代表的是使用的那个 keyGenerator 的名称
*
*/
String value() default "";
/**
* sequence 序列执行顺序
* 是在 entity 数据插入之前执行,还是之后执行,之后执行的一般是数据主动生成的 id
*
* @return 执行之前还是之后
*/
boolean before() default true;
}
keyType 为主键的生成方式,KeyType 有 4 种类型:
public enum KeyType {
/**
* 自增的方式
*/
Auto,
/**
* 通过执行数据库 sql 生成
* 例如:select SEQ_USER_ID.nextval as id from dual
*/
Sequence,
/**
* 通过 IKeyGenerator 生成器生成
*/
Generator,
/**
* 其他方式,比如在代码层用户手动设置
*/
None,
}
@Column 注解的使用
MyBatis-Flex 提供了 @Column 用来对字段进行更多的配置
@Column 的代码定义:
public @interface Column {
/**
* 字段名称
*/
String value() default "";
/**
* 是否忽略该字段,可能只是业务字段,而非数据库对应字段
*/
boolean ignore() default false;
/**
* insert 的时候默认值,这个值会直接被拼接到 sql 而不通过参数设置
*/
String onInsertValue() default "";
/**
* update 的时候自动赋值,这个值会直接被拼接到 sql 而不通过参数设置
*/
String onUpdateValue() default "";
/**
* 是否是大字段,大字段 APT 不会生成到 DEFAULT_COLUMNS 里
*/
boolean isLarge() default false;
/**
* 是否是逻辑删除字段,一张表中只能存在 1 一个逻辑删除字段
* 逻辑删除的字段,被删除时,会设置为 1,正常状态为 0
*/
boolean isLogicDelete() default false;
/**
* 是否为乐观锁字段,若是乐观锁字段的话,数据更新的时候会去检测当前版本号,若更新成功的话会设置当前版本号 +1
* 只能用于数值的字段
*/
boolean version() default false;
/**
* 配置的 jdbcType
*/
JdbcType jdbcType() default JdbcType.UNDEFINED;
/**
* 自定义 TypeHandler
*/
Class<? extends TypeHandler> typeHandler() default UnknownTypeHandler.class;
}
2、日志打印
import com.mybatisflex.core.mybatis.FlexConfiguration;
import com.mybatisflex.spring.boot.ConfigurationCustomizer;
import org.apache.ibatis.logging.stdout.StdOutImpl;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfigurationCustomizer implements ConfigurationCustomizer {
@Override
public void customize(FlexConfiguration configuration) {
configuration.setLogImpl(StdOutImpl.class);
}
}
3、逻辑删除
逻辑删除指的是在删除数据的时候,并非真正的去删除,而是将表中列所对应的状态字段(status)做修改操作, 实际上并未删除目标数据。只需要在实体类加上 @Column(isLogicDelete = true) 即可
@Data
@Table("user")
public class User {
@Id(keyType = KeyType.Auto)
private Long id;
@Column("name")
private String name;
private Integer age;
private String email;
@Column(isLogicDelete = true)
private Boolean isDelete;
}
4、乐观锁
用于当有多个用户(或者多场景)去同时修改同一条数据的时候,只允许有一个修改成功。
简单使用
@Data
@Table("user")
public class User {
@Id(keyType = KeyType.Auto)
private Long id;
@Column("name")
private String name;
private Integer age;
private String email;
@Column(version = true)
private Long version;
}
5、数据填充
数据填充指的是,当 Entity 数据被插入 或者 更新的时候,会为字段进行一些默认的数据设置。这个非常有用,比如当某个 entity 被插入时候 会设置一些数据插入的时间、数据插入的用户 id,多租户的场景下设置当前租户信息等等。
MyBatis-Flex 提供了两种方式,帮助开发者进行数据填充。
- 通过
@Table注解的onInsert和onUpdate配置进行操作。 - 通过
@Column注解的onInsertValue和onUpdateValue配置进行操作。
@Table 注解的 onInsert 主要是在 Java 应用层面进行数据设置,而 @Column 注解的 onInsertValue 则是在数据库层面进行数据设置。
@Data
@Table("user")
public class User {
@Id(keyType = KeyType.Auto)
private Long id;
@Column("name")
private String name;
private Integer age;
private String email;
@Column(isLogicDelete = true)
private Boolean isDelete;
@Column(version = true)
private Long version;
@Column(onInsertValue = "now()")
private Date created;
}
6、数据脱敏@ColumnMask
数据脱敏是指对某些敏感信息通过脱敏规则进行数据的变形, 实现敏感隐私数据的可靠保护。在涉及客户安全数据或者一些商业性敏感数据的情况下,在不违反系统规则条件下,对真实数据进行改造并提供使用, 如身份证号、手机号、卡号、客户号等个人信息都需要进行数据脱敏。
@Data
@Table("user")
public class User {
@Id(keyType = KeyType.Auto)
private Long id;
@Column("name")
@ColumnMask(Masks.CHINESE_NAME)
private String name;
private Integer age;
@ColumnMask(Masks.EMAIL)
private String email;
@ColumnMask(Masks.FIXED_PHONE)
private String phone;
}
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.where(User::getId).eq(1L);
User user = userMapper.selectOneByQuery(queryWrapper);
System.out.println(user);
//User(id=1, name=太极**丰, age=20, email=tes**@baomidou.com, phone=184******68)
MyBatis-Flex 还提供了如下的8种脱敏规则(共9种:
- 手机号脱敏
- 固定电话脱敏
- 身份证号脱敏
- 车牌号脱敏
- 地址脱敏
- 邮件脱敏
- 密码脱敏
- 银行卡号脱敏
还支持自定义脱敏:
通过 MaskManager 注册新的脱敏规则:
MaskManager.registerMaskProcessor("自定义规则名称"
, data -> {
return data;
})
使用自定义的脱敏规则
@Table("tb_account")
public class Account {
@Id(keyType = KeyType.Auto)
private Long id;
@ColumnMask("自定义规则名称")
private String userName;
}
7、多数据源
YAML配置
新增mybatis-flex配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/tempdatabase?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
password: sa
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-flex:
datasource:
ds1:
url: jdbc:mysql://localhost:3306/tempdatabase?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
password: sa
username: root
ds2:
url: jdbc:mysql://localhost:3306/student?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
password: sa
username: root
数据源切换
MyBatis-Flex 提供了 4 种方式来配置数据源:
- 1、编码,使用
DataSourceKey.use方法。 - 2、
@UseDataSource("dataSourceName")在 Mapper 类上,添加注解,用于指定使用哪个数据源。 - 3、
@UseDataSource("dataSourceName")在 Mapper 方法上,添加注解,用于指定使用哪个数据源。 - 4、
@Table(dataSource="dataSourceName")在 Entity 类上添加注解,该 Entity 的增删改查请求默认使用该数据源。
在 SpringBoot 或 Solon 项目上,
@UseDataSource("dataSourceName")也可用于在 Controller 或者 Service 类上。若是 Spring 项目(非 SpringBoot), 用户需要参考MultiDataSourceAutoConfiguration进行配置后才能使用。
例如其一:
@UseDataSource("ds1")
public interface UserMapper extends BaseMapper<User> {
}
8、读写分离
写分离的功能,要求当前环境必须是多个数据库(也可理解为多个数据源),其原理是: 让主数据库(master)处理事务性操作,比如:增、删、改(INSERT、DELETE、UPDATE),而从数据库(slave)处理查询(SELECT)操作。
配置文件application.yaml
mybatis-flex:
datasource:
master:
url: jdbc:mysql://localhost:3306/tempdatabase?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
password: sa
username: root
ds1:
url: jdbc:mysql://localhost:3306/student?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
password: sa
username: root
ds2:
url: jdbc:mysql://localhost:3306/student2?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
password: sa
username: root
ds3:
url: jdbc:mysql://localhost:3306/student3?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
password: sa
username: root
分片策略:
public class MyStrategy implements DataSourceShardingStrategy {
public String doSharding(String currentDataSourceKey
, Object mapper, Method mapperMethod, Object[] methodArgs){
// 如果 mapper 的方法属于 增删改,使用 master 数据源
if (StringUtil.startWithAny(mapperMethod.getName(),
"insert", "delete", "update")){
return "master";
}
//其他场景,使用 ds1 或者 ds2等 进行负载均衡
return "ds*";
}
}
注册分片策略
DataSourceManager.setDataSourceShardingStrategy(new MyStrategy());
doSharding 的参数分别为:
- currentDataSourceKey:当前使用的数据源 key
- mapper:当前的 mapper 对象
- mapperMethod: 当前的 mapper 方法
- methodArgs:当前的 mapper 方法的参数内容
8、单个查询
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.where(User::getId).eq(1L);
User user = userMapper.selectOneByQuery(queryWrapper);
9、分页查询
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.where(User::getAge).gt(18);
Page page = new Page(1, 5);
Page paginate = userMapper.paginate(page, queryWrapper);
System.out.println(paginate);
10、增删改功能
新增方法比Mybatis plus太多了
insert(entity):插入实体类数据,不忽略null值。insertSelective(entity):插入实体类数据,但是忽略null的数据,只对有值的内容进行插入。这样的好处是数据库已经配置了一些默认值,这些默认值才会生效。insert(entity, ignoreNulls):插入实体类数据。insertWithPk(entity):插入带有主键的实体类,不忽略null值。insertSelectiveWithPk(entity):插入带有主键的实体类,忽略null值。insertWithPk(entity, ignoreNulls):带有主键的插入,此时实体类不会经过主键生成器生成主键。insertBatch(entities):批量插入实体类数据,只会根据第一条数据来构建插入的字段内容。insertBatch(entities, size):批量插入实体类数据,按 size 切分。insertBatchSelective(entities):批量插入实体类数据,忽略null值。insertOrUpdate(entity):插入或者更新,若主键有值,则更新,若没有主键值,则插入,插入或者更新都不会忽略null值。insertOrUpdateSelective(entity):插入或者更新,若主键有值,则更新,若没有主键值,则插入,插入或者更新都会忽略null值。insertOrUpdate(entity, ignoreNulls):插入或者更新,若主键有值,则更新,若没有主键值,则插入。
新增OR修改
User user = new User();
user.setAge(34);
user.setName("张三");
user.setEmail("1829153067@qq.com");
user.setPhone("1848278767");
userMapper.insertOrUpdateSelective(user);
修改:
UpdateChain 是一个对 UpdateEntity、UpdateWrapper 等进行封装的一个工具类,方便用户用于进行链式操作。
UpdateChain.of(User.class)
.set(User::getName, "刑天铠甲")
.setRaw(User::getAge, "age + 1")
.where(User::getId).eq(2)
.update();
删除数据
BaseMapper 的接口提供了 deleteById、deleteBatchByIds、deleteByMap、deleteByQuery 方法,用于删除数据;
deleteById(id):根据主键删除数据。如果是多个主键的情况下,需要传入数组,例如:new Integer[]{100,101}。delete(entity):根据实体主键来删除数据。相比deleteById(id),此方法更便于对复合主键实体类的删除。deleteBatchByIds(ids):根据多个主键批量删除数据。deleteBatchByIds(ids, size):根据多个主键批量删除数据。deleteByMap(whereConditions):根据 Map 构建的条件来删除数据。deleteByCondition(whereConditions):根据查询条件来删除数据。deleteByQuery(queryWrapper):根据查询条件来删除数据。
11、关联查询
在 MyBatis-Flex 中,我们内置了 3 种方案,帮助用户进行关联查询,比如 一对多、一对一、多对一、多对多
在 MyBatis-Flex 中,提供了 4 个 Relations 注解,他们分别是:
- RelationOneToOne:用于一对一的场景
- RelationOneToMany:用于一对多的场景
- RelationManyToOne:用于多对一的场景
- RelationManyToMany:用于多对多的场景
添加了以上配置的实体类,在通过 BaseMapper 的方法查询数据时,需要调用 select***WithRelations() 方法,Relations 注解才能生效。 否则 MyBatis-Flex 自动忽略 Relations 注解。
@Data
@Table("user")
public class User {
@Id(keyType = KeyType.Auto)
private Long id;
@Column("name")
@ColumnMask(Masks.CHINESE_NAME)
private String name;
private Integer age;
@ColumnMask(Masks.EMAIL)
private String email;
@ColumnMask(Masks.FIXED_PHONE)
private String phone;
@Column("clazz_id")
private Integer clazzId;
@RelationManyToOne(selfField = "clazzId", targetField = "clazzId")
private Clazz clazz;
@RelationOneToOne(selfField = "id", targetField = "userId"
, targetTable = "id_card")
private IdCard IdCard;
@RelationOneToMany(selfField = "id", targetField = "userId")
private List<IdCard> idCards;
// @Column(isLogicDelete = true)
// private Boolean isDelete;
// @Column(version = true)
// private Long version;
// @Column(onInsertValue = "now()")
// private Date created;
}
@Data
@Table(value = "id_card")
public class IdCard implements Serializable {
@Id(keyType = KeyType.Auto)
private Long id;
@Column("user_id")
private Long userId;
@Column("card_no")
private String cardNo;
private String content;
}
@Data
@Table(value = "clazz")
public class Clazz {
@Id(keyType = KeyType.Auto,value = "clazz_id")
private Integer clazzId;
private String name;
private Integer number;
private String address;
}
调用:
@Test
void selectWithRelations(){
List<User> accounts = userMapper.selectAllWithRelations();
System.out.println(accounts);
}
12、数据源加密(Database Credential Encryption)
在生产环境中,数据库连接信息(如用户名、密码)若以明文形式写入配置文件,存在严重的安全风险。MyBatis-Flex 支持通过 自定义数据源配置 + 密码解密逻辑 实现数据源信息的加密存储与运行时解密。
- 引入加解密工具类(如 AES、RSA 或集成 Jasypt):
@Component
public class DatabaseCredentialDecoder {
private static final String KEY = "your-secure-key-16"; // 16位密钥
public String decrypt(String encryptedValue) {
// 使用 AES 解密
try {
byte[] key = KEY.getBytes(StandardCharsets.UTF_8);
SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] decoded = Base64.getDecoder().decode(encryptedValue);
byte[] decrypted = cipher.doFinal(decoded);
return new String(decrypted, "UTF-8");
} catch (Exception e) {
throw new RuntimeException("Failed to decrypt database credential", e);
}
}
}
- 自定义
DataSource配置类,注入解密逻辑:
@Configuration
public class EncryptedDataSourceConfig {
@Value("${spring.datasource.encrypted.username}")
private String encryptedUsername;
@Value("${spring.datasource.encrypted.password}")
private String encryptedPassword;
@Bean
@Primary
public DataSource dataSource(DatabaseCredentialDecoder decoder) {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/tempdatabase?...");
config.setUsername(decoder.decrypt(encryptedUsername)); // 解密用户名
config.setPassword(decoder.decrypt(encryptedPassword)); // 解密密码
config.setDriverClassName("com.mysql.cj.jdbc.Driver");
return new HikariDataSource(config);
}
}
- 配置文件中使用密文:
spring:
datasource:
encrypted:
username: U2FsdGVkX1+abc... # 加密后的用户名
password: U2FsdGVkX1+xyz... # 加密后的密码
💡 提示:也可集成 Jasypt-spring-boot 实现更便捷的 ENC(密文) 语法支持。
13、动态表名(Dynamic Table Name)
适用于分表场景(如按时间、租户 ID 分表),MyBatis-Flex 支持在运行时动态指定实际操作的表名。
使用方式
通过 TableDef 或 APT 生成的表对象进行动态设置:
// 定义表结构
@Table("user_log") // 默认表名
public class UserLog {
@Id
private Long id;
private String content;
}
// 查询时动态切换表名
String actualTableName = "user_log_202501"; // 运行时确定
QueryWrapper wrapper = QueryWrapper.create()
.select()
.from(new TableDef("user_log").asTable(actualTableName)) // 动态指定真实表名
.where("content").like("test");
List<UserLog> logs = userLogMapper.selectListByQuery(wrapper);
⚠️ 注意:需确保数据库中存在对应物理表,且结构一致。
14、事务管理(Transaction Management)
MyBatis-Flex 基于 MyBatis 的事务机制,完全兼容 Spring 的事务管理能力,支持声明式与编程式事务。
1. 声明式事务(推荐)
使用 @Transactional 注解管理事务:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private LogMapper logMapper;
@Transactional(rollbackFor = Exception.class)
public void addUserWithLog(User user, Log log) {
userMapper.insert(user);
logMapper.insert(log);
// 若此处抛异常,两操作均回滚
}
}
2. 编程式事务
使用 TransactionTemplate 精确控制事务边界:
@Autowired
private TransactionTemplate transactionTemplate;
public void doBusiness() {
transactionTemplate.execute(status -> {
try {
userMapper.insert(new User("张三"));
logMapper.insert(new Log("新增用户"));
return true;
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
});
}
✅ 优势:MyBatis-Flex 在多数据源下仍支持 Spring 事务传播行为(如 PROPAGATION_REQUIRED),是其优于其他框架的重要特性。
15、数据权限(Data Permission)
数据权限用于控制用户可访问的数据范围(如部门隔离、角色可见性)。MyBatis-Flex 提供 拦截器机制 实现自动注入数据权限条件。
@Intercepts({
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class DataPermissionInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
String mappedStatementId = ms.getId();
// 判断是否为查询语句且涉及受保护实体
if (mappedStatementId.contains("UserMapper") && invocation.getType().equals("query")) {
BoundSql boundSql = ms.getBoundSql(invocation.getArgs().length > 1 ? invocation.getArgs()[1] : null);
String originalSql = boundSql.getSql();
// 获取当前用户部门
String userDept = SecurityUtils.getCurrentUserDept();
// 注入权限条件
String filteredSql = originalSql + " AND dept_id = " + userDept;
// 替换 SQL
BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), filteredSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
invocation.getArgs()[0] = newMappedStatement(ms, newBoundSql);
}
return invocation.proceed();
}
private MappedStatement newMappedStatement(MappedStatement oldMs, BoundSql newBoundSql) {
// 构建新 MappedStatement(略)
}
}
注册插件:
@Bean
public DataPermissionInterceptor dataPermissionInterceptor() {
return new DataPermissionInterceptor();
}
16、字段权限(Field Permission)
字段权限控制用户是否可见某些敏感字段(如薪资、身份证号)。MyBatis-Flex 支持通过 拦截器 + 注解 实现字段级过滤。
- 定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldPermission {
String[] roles() default {};
}
- 在实体类中标记:
public class User {
private Long id;
@FieldPermission(roles = {"admin", "hr"})
private BigDecimal salary;
}
- 查询后根据当前用户角色过滤字段值:
// 伪代码:在结果处理阶段
if (!currentUser.hasAnyRole(field.getAnnotation(FieldPermission.class).roles())) {
field.setAccessible(true);
field.set(entity, null); // 或设为脱敏值
}
🔐 结合 数据脱敏 使用效果更佳。
17、字段加密(Field Encryption)
对数据库中特定字段(如身份证、手机号)进行加密存储,查询时自动解密。
自定义 TypeHandler
@MappedTypes(String.class)
public class EncryptedTypeHandler extends BaseTypeHandler<String> {
private static final String KEY = "encryption-key-16";
@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
String encrypted = AES.encrypt(parameter, KEY);
ps.setString(i, encrypted);
}
@Override
public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
String encrypted = rs.getString(columnName);
return encrypted == null ? null : AES.decrypt(encrypted, KEY);
}
// 其他方法略
}
实体类中使用:
@Column(typeHandler = EncryptedTypeHandler.class)
private String idCard;
✅ 优势:透明化加解密,业务无感知。
18、字典回写(Dictionary Backfill)
将代码值(如 status=1)自动转换为描述(如 “启用”)并回写到实体字段。
public enum StatusDict {
ENABLE(1, "启用"),
DISABLE(0, "禁用");
private Integer code;
private String label;
// 构造与 getter
}
// 在查询后处理
@After("execution(* com.example.mapper.*.select*(..))")
public void afterSelect(JoinPoint jp) {
Object result = jp.getSignature() instanceof MethodSignature ? ((MethodSignature) jp.getSignature()).getReturnType() : null;
if (result instanceof User) {
User user = (User) result;
user.setStatusLabel(StatusDict.valueOf(user.getStatus()).getLabel());
}
}
或使用 MyBatis-Flex 的 onSet 监听:
@Column(onSet = "statusLabel")
private String statusLabel;
配合 SetListener 实现自动填充。
19、枚举属性处理(Enum Support)
MyBatis-Flex 支持枚举类的自动映射。
方式一:使用 @EnumValue 注解(类似 MyBatis-Plus)
public enum Gender {
MALE(1), FEMALE(2);
@EnumValue
private Integer value;
}
实体类中使用:
private Gender gender;
方式二:自定义 TypeHandler
public class GenderTypeHandler extends BaseTypeHandler<Gender> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, Gender parameter, JdbcType jdbcType) {
ps.setInt(i, parameter.getValue());
}
@Override
public Gender getNullableResult(ResultSet rs, String columnName) {
int value = rs.getInt(columnName);
return Gender.valueOf(value);
}
}
20、多租户支持(Multi-Tenancy)
MyBatis-Flex 提供完整的多租户解决方案,支持 字段隔离 与 表/库隔离。
1. 字段隔离(共享表结构)
在表中添加 tenant_id 字段,通过拦截器自动注入条件。
实体类标记多租户字段:
@Table("user")
public class User {
@Id
private Long id;
@Column(tenantId = true) // 标记为租户字段
private String tenantId;
}
配置多租户拦截器:
public class MultiTenantInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
// 在查询 SQL 中自动追加 AND tenant_id = ?
// 在插入时自动设置 tenant_id
}
}
2. 表/库隔离(动态数据源)
结合 多数据源 + 动态表名 实现不同租户使用不同库或表。
public class TenantDataSourceRouter extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TenantContext.getCurrentTenantId(); // 动态路由
}
}
三、结语:工具是舟,人是渡者
MyBatis-Flex 在保持轻量的同时,提供了远超同类框架的功能完整性与灵活性,尤其在安全性、多租户、多数据源事务等方面表现突出,是构建高可用、可扩展系统的理想选择。
工具终究是外力,而人,才是力量的源泉,唯有认清这一点,我们才能在工具的洪流中,站稳脚跟,走向更远的远方。真正的进步,不在于拥有最先进的工具,而在于能否让工具、人与业务协同共振,奏响价值创造的最强音。
文章结束,喜欢就给个一键三连吧,你的肯定是我最大的动力,点赞上一千我就是脑瘫也出下章。
更多推荐


所有评论(0)