在 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 的事务管理,比如 @TransactionalTransactionTemplate
多数据源是否支持 “非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 注解的 onInsertonUpdate 配置进行操作。
  • 通过 @Column 注解的 onInsertValueonUpdateValue 配置进行操作。

@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 是一个对 UpdateEntityUpdateWrapper 等进行封装的一个工具类,方便用户用于进行链式操作。

  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 支持通过 自定义数据源配置 + 密码解密逻辑 实现数据源信息的加密存储与运行时解密。

  1. 引入加解密工具类(如 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);
        }
    }
}
  1. 自定义 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);
    }
}
  1. 配置文件中使用密文
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 支持通过 拦截器 + 注解 实现字段级过滤。

  1. 定义注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldPermission {
    String[] roles() default {};
}
  1. 在实体类中标记:
public class User {
    private Long id;
    
    @FieldPermission(roles = {"admin", "hr"})
    private BigDecimal salary;
}
  1. 查询后根据当前用户角色过滤字段值:
// 伪代码:在结果处理阶段
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 在保持轻量的同时,提供了远超同类框架的功能完整性与灵活性,尤其在安全性、多租户、多数据源事务等方面表现突出,是构建高可用、可扩展系统的理想选择。

工具终究是外力,而人,才是力量的源泉,唯有认清这一点,我们才能在工具的洪流中,站稳脚跟,走向更远的远方。真正的进步,不在于拥有最先进的工具,而在于能否让工具、人与业务协同共振,奏响价值创造的最强音。

文章结束,喜欢就给个一键三连吧,你的肯定是我最大的动力,点赞上一千我就是脑瘫也出下章。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐