Why MyBatis-Flex Beats MyBatis-Plus: Features, Performance & More

MyBatis-Flex is a lightweight, high‑performance MyBatis enhancement offering zero third‑party dependencies, flexible QueryWrapper support, superior CRUD speed, advanced relational mappings, built‑in data masking, caching, multi‑datasource handling, and customizable SQL auditing, making it a compelling alternative to MyBatis‑Plus for modern Java backend development.

Architect's Tech Stack
Architect's Tech Stack
Architect's Tech Stack
Why MyBatis-Flex Beats MyBatis-Plus: Features, Performance & More

Introduction

MyBatis-Flex is an elegant MyBatis enhancement framework that is lightweight, highly performant and flexible. It provides a built‑in QueryWrapper that greatly reduces SQL writing effort and error risk.

More Lightweight

MyBatis-Flex has no third‑party dependencies beyond MyBatis itself, offering higher autonomy, controllability and stability.

More Flexible

MyBatis-Flex offers a very flexible QueryWrapper supporting association queries, multi‑table queries, multiple primary keys, logical deletion, optimistic locking, data filling, data masking, etc.

Higher Performance

Through a unique architecture without MyBatis interceptors or SQL parsing, MyBatis-Flex achieves exponential performance gains.

Feature Comparison

功能或特点

MyBatis-Flex

MyBatis-Plus

Fluent-MyBatis

对 entity 的基本增删改查

分页查询

分页查询之总量缓存

分页查询无 SQL 解析设计(更轻量,及更高性能)

多表查询:from 多张表

多表查询:left join、inner join 等等

多表查询:union,union all

单主键配置

多种 id 生成策略

支持多主键、复合主键

字段的 typeHandler 配置

除了 MyBatis,无其他第三方依赖(更轻量)

QueryWrapper 是否支持在微服务项目下进行 RPC 传输

未知

逻辑删除

乐观锁

SQL 审计

数据填充

✔️(收费)

数据脱敏

✅(收费)

✔️(收费)

字段权限

✅(收费)

字段加密

✅(收费)

字典回写

✅(收费)

多数据源支持

借助其他框架或收费

多数据源事务管理(@Transactional)

多数据源非Spring项目支持

多租户

动态表名

动态 Schema

Performance Comparison

MyBatis-Flex single‑record query is about 5‑10× faster than MyBatis-Plus.

MyBatis-Flex ten‑record query is about 5‑10× faster than MyBatis-Plus.

MyBatis-Flex pagination query is about 5‑10× faster than MyBatis-Plus.

MyBatis-Flex data update is about 5‑10× faster than MyBatis-Plus.

Official site: https://mybatis-flex.com/

Code Practice

Dependency configuration example (Maven):

<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>

One‑to‑One Association @RelationOneToOne

Example code:

@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;
}
If the selfField is the primary key and the table has only one primary key, the annotation can be simplified to @RelationOneToOne(targetField = "accountId").

One‑to‑Many Association @RelationOneToMany

Example code:

@Data
public class Account implements Serializable {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private String userName;
    @RelationOneToMany(selfField = "id", targetField = "accountId")
    private List<Book> books;
}

@Data
@Table(value = "tb_book")
public class Book implements Serializable {
    @Id(keyType = KeyType.Auto)
    private Long id;
    private Long accountId;
    private String title;
}

If the collection type is a Map, specify mapKeyField to define the map key column.

@RelationOneToMany(selfField = "id", targetField = "accountId", mapKeyField = "id")
private Map<Long, Book> books;

Many‑to‑One Association @RelationManyToOne

Example code:

@Data
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;
}

Many‑to‑Many Association @RelationManyToMany

Example code:

@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<Role> roles;
}

@Data
@Table(value = "tb_role")
public class Role implements Serializable {
    private Long id;
    private String name;
}

Parent‑Child Recursive Query

Example code (default recursion depth 3, can be set to 10):

QueryWrapper qw = QueryWrapper.create();
qw.where(MENU.PARENT_ID.eq(0));
RelationManager.setMaxDepth(10);
List<Menu> menus = menuMapper.selectListWithRelationsByQuery(qw);

Chain Operations

MyBatis-Flex provides QueryChain, UpdateChain and DbChain for fluent query, update and delete operations.

@SpringBootTest
class ArticleServiceTest {
    @Autowired
    ArticleService articleService;

    @Test
    void testChain() {
        List<Article> articles = articleService.queryChain()
            .select(ARTICLE.ALL_COLUMNS)
            .from(ARTICLE)
            .where(ARTICLE.ID.ge(100))
            .list();
    }
}

When not in a service, you can create a chain with the mapper:

List<Article> articles = QueryChain.of(mapper)
    .select(ARTICLE.ALL_COLUMNS)
    .from(ARTICLE)
    .where(ARTICLE.ID.ge(100))
    .list();

Data Masking

Use @ColumnMask with built‑in mask types (e.g., Masks.CHINESE_NAME) to mask sensitive fields such as names, phone numbers, ID numbers, etc.

@Table("tb_account")
public class Account {
    @Id(keyType = KeyType.Auto)
    private Long id;
    @ColumnMask(Masks.CHINESE_NAME)
    private String userName;
}

Phone number masking

Fixed‑line masking

ID number masking

License‑plate masking

Address masking

Email masking

Password masking

Bank card masking

Custom mask registration:

MaskManager.registerMaskProcessor("customRule", data -> data);

Temporarily disable masking:

try {
    MaskManager.skipMask();
    // queries without masking
    accountMapper.selectListByQuery(...);
} finally {
    MaskManager.restoreMask();
}

Data Caching

MyBatis second‑level cache is supported but not ideal for distributed environments; Spring Cache is recommended.

@Service
@CacheConfig(cacheNames = "account")
public class AccountServiceImpl extends CacheableServiceImpl<MyAccountMapper, Account> {
    @Override
    @CacheEvict(allEntries = true)
    public boolean remove(QueryWrapper query) {
        return super.remove(query);
    }
    @Override
    @Cacheable(key = "#id")
    public Account getById(Serializable id) {
        return super.getById(id);
    }
    // other CRUD methods with appropriate @CacheEvict / @Cacheable annotations
}

SQL Auditing

Enable auditing: AuditManager.setAuditEnable(true); Provide a custom MessageFactory to create audit messages:

public class MyMessageFactory implements MessageFactory {
    @Override
    public AuditMessage create() {
        AuditMessage message = new AuditMessage();
        // set platform, module, url, user, etc.
        return message;
    }
}
AuditManager.setMessageFactory(new MyMessageFactory());

Implement MessageReporter to send audit logs (e.g., to HTTP endpoint, log system, or message queue):

public class MyMessageReporter implements MessageReporter {
    @Override
    public void sendMessages(List<AuditMessage> messages) {
        // send messages to desired destination
    }
}

Implement MessageCollector for immediate collection (e.g., console output):

public class MyMessageCollector implements MessageCollector {
    @Override
    public void collect(AuditMessage auditMessage) {
        System.out.println(auditMessage.getFullSql());
    }
}

Two built‑in collectors are available: ScheduledMessageCollector (periodic sending via MessageReporter) and ConsoleMessageCollector (outputs to console).

Multiple Data Sources

Configuration example (application.yml):

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

Four ways to select a data source:

Programmatically: DataSourceKey.use("ds2") (must be cleared with DataSourceKey.clear()).

Annotation on mapper class: @UseDataSource("ds2").

Annotation on mapper method: @UseDataSource("ds2").

Annotation on entity: @Table(dataSource="ds2") – entity CRUD uses the specified source.

Priority order:

DataSourceKey.use() > @UseDataSource on method > @UseDataSource on class > @Table(dataSource="...")

.

try {
    DataSourceKey.use("ds2");
    List<Row> rows = Db.selectAll("tb_account");
    System.out.println(rows);
} finally {
    DataSourceKey.clear();
}

Overall, MyBatis-Flex provides a comprehensive, lightweight, and high‑performance alternative to MyBatis‑Plus for Java backend development.

JavaORMMyBatis-FlexDataMaskingSQLAuditing
Architect's Tech Stack
Written by

Architect's Tech Stack

Java backend, microservices, distributed systems, containerized programming, and more.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.