MyBatis-Flex vs MyBatis-Plus: A Detailed Source‑Code and Feature Comparison

This article walks through a side‑by‑side examination of MyBatis‑Flex and MyBatis‑Plus, covering entity annotations, query construction, pagination, architectural differences, APT‑generated code, multi‑table joins, partial updates, and the trade‑offs of each design, helping developers choose the right ORM for their projects.

samdeepthink
samdeepthink
samdeepthink
MyBatis-Flex vs MyBatis-Plus: A Detailed Source‑Code and Feature Comparison

Same Order Table, Two Approaches

Both frameworks define the same Order entity, but MyBatis‑Flex uses @Table and @Id while MyBatis‑Plus uses @TableName and @TableId. The annotation names differ, yet the real contrast lies in how query conditions are built.

Condition Query

MyBatis‑Plus builds a condition with LambdaQueryWrapper<Order>:

LambdaQueryWrapper<Order> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Order::getUserId, userId)
       .eq(Order::getStatus, 1)
       .orderByDesc(Order::getCreateTime);
List<Order> orders = orderMapper.selectList(wrapper);

MyBatis‑Flex uses a QueryWrapper that references compile‑time generated constants:

QueryWrapper query = QueryWrapper.create()
    .where(ORDER.USER_ID.eq(userId))
    .and(ORDER.STATUS.eq(1))
    .orderBy(ORDER.CREATE_TIME.desc());
List<Order> orders = orderMapper.selectListByQuery(query);

The ORDER class is generated by APT; it provides type‑safe field constants that cause a compile‑time error if a column name is misspelled.

Pagination Query

MyBatis‑Plus requires a pagination interceptor and a Page<Order> object:

// configure MybatisPlusInterceptor + PaginationInnerInterceptor first
Page<Order> page = new Page<>(1, 10);
orderMapper.selectPage(page, wrapper);

MyBatis‑Flex has pagination built into the core; a single call suffices:

Page<Order> page = orderMapper.paginate(1, 10, query);

Architectural Core Differences

MyBatis‑Plus: Startup‑time Injection

During MyBatis startup, AbstractSqlInjector injects a full set of CRUD MappedStatement objects into each mapper. Each operation (Insert, DeleteById, SelectList, etc.) has a dedicated class extending AbstractMethod. The SQL templates are pre‑compiled and registered in MyBatis’ Configuration, ready for runtime use. The downside is that every mapper receives the entire CRUD suite regardless of actual usage, increasing startup cost.

MyBatis‑Flex: Provider Annotations

MyBatis‑Flex relies on native MyBatis annotations such as @SelectProvider and @InsertProvider that point to an EntitySqlProvider. SQL is generated at runtime by the provider based on entity metadata, eliminating the need for pre‑generated statements or interceptors.

The framework promotes three "light" principles:

Light Dependency : only depends on MyBatis.

Light Implementation : no interceptors; features like pagination are built into the core.

Light Runtime : no SQL parsing; SQL is assembled directly.

APT: Compile‑time Code Generation

During mvn compile, the mybatis-flex-processor scans entities annotated with @Table and generates two artifacts:

A TableDef class (e.g., OrderTableDef) containing QueryColumn constants such as ORDER.USER_ID.

A mapper interface that extends BaseMapper if the project does not provide one.

This yields compile‑time type safety: using a wrong column name causes a compilation error. The trade‑off is the learning curve required to understand the generated ORDER class.

Multi‑table Query: Biggest Difference

For a join query, MyBatis‑Plus forces XML mapping:

<select id="listWithDetail" resultType="Order">
    SELECT o.*, d.product_name, d.price, d.quantity
    FROM `order` o
    LEFT JOIN order_detail d ON o.id = d.order_id
    WHERE o.status = 1
</select>

MyBatis‑Flex can express the same join directly in Java:

QueryWrapper query = QueryWrapper.create()
    .select()
    .from(ORDER)
    .leftJoin(ORDER_DETAIL).on(ORDER.ID.eq(ORDER_DETAIL.ORDER_ID))
    .where(ORDER.STATUS.eq(1));
List<Order> orders = orderMapper.selectListByQuery(query);

No XML, no extra mapper method, and the join fields are compile‑time constants, guaranteeing type safety.

QueryWrapper Design Differences

MyBatis‑Plus’ QueryWrapper<T> supports two styles: string field names (prone to typos) and lambda method references (require getters). MyBatis‑Flex’ QueryWrapper is not generic; it builds conditions with APT‑generated QueryColumn constants, offering inherent type safety without getters.

Flex’s wrapper is also serializable, enabling RPC transmission, whereas Plus’s lambda‑based wrapper cannot be serialized.

When a condition value is null, Flex automatically ignores the clause; Plus requires an explicit check such as wrapper.eq(value != null, "column", value).

Partial Field Update

MyBatis‑Plus uses UpdateWrapper to specify each column to update:

UpdateWrapper<Order> wrapper = new UpdateWrapper<>();
wrapper.eq("id", orderId)
       .set("status", 2)
       .set("total_amount", new BigDecimal("0.00"));
orderMapper.update(null, wrapper);

MyBatis‑Flex provides UpdateEntity.of(), which records only the fields whose setters are called:

Order order = UpdateEntity.of(Order.class, orderId);
order.setStatus(2);
order.setTotalAmount(new BigDecimal("0.00"));
orderMapper.update(order);

This design also allows setting a field to null without a second update call.

Db + Row: No‑Entity Operations

Flex offers a Db utility together with Row (a HashMap subclass) to operate without entity classes:

Row row = new Row();
row.set("order_no", "ORD20250703002");
row.set("user_id", 1004L);
row.set("total_amount", new BigDecimal("66.00"));
row.set("status", 0);
Db.insert("`order`", row);

This is handy for scripts, data migration, or dynamic tables.

Feature Comparison

SQL generation: startup injection (Plus) vs runtime Provider (Flex).

Interceptors: required for pagination, tenant, etc. in Plus; none in Flex.

SQL parsing: Plus parses original SQL; Flex assembles SQL directly.

Third‑party dependencies: Plus adds core+extension+starter; Flex only depends on MyBatis.

Type‑safe condition building: LambdaQueryWrapper (Plus) vs APT‑generated QueryColumn (Flex).

Null handling: manual in Plus; automatic in Flex.

Pagination: interceptor‑based (Plus) vs built‑in (Flex).

Multi‑table join: XML required in Plus; direct leftJoin in Flex.

QueryWrapper serialization: unsupported in Plus; supported in Flex for RPC.

No‑entity CRUD: unsupported in Plus; provided by Db + Row in Flex.

Partial field update: UpdateWrapper.set() (Plus) vs UpdateEntity.of() (Flex).

Composite primary keys: not supported in Plus; supported in Flex.

Data‑masking/encryption: paid feature in Plus; free in Flex.

Community: mature and large for Plus; newer and smaller for Flex.

Learning curve: low for Plus; requires understanding APT for Flex.

Conclusion

The two frameworks serve different design philosophies. MyBatis‑Plus extends MyBatis heavily, offering a rich feature set, mature ecosystem, and low learning cost, at the expense of larger size, interceptor complexity, and XML‑backed multi‑table queries. MyBatis‑Flex pursues a minimalist approach: no interceptors, no SQL parsing, zero third‑party dependencies, and a type‑safe, join‑capable QueryWrapper. Its trade‑offs are a smaller community and a steeper onboarding curve due to APT‑generated code. Existing projects on MyBatis‑Plus need not switch, but new projects that can invest in learning APT may benefit from Flex, especially when multi‑table queries are frequent.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

javaORMPaginationMyBatis PlusAPTQueryWrapperMyBatis-FlexMulti-table join
samdeepthink
Written by

samdeepthink

Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.

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.