Why MyBatis-Flex Shines Among the Flood of New ORM Frameworks
The article provides a detailed technical comparison of MyBatis-Flex with MyBatis-Plus and Fluent-MyBatis, covering feature matrices, code examples for basic queries, aggregation, complex conditions, multi‑table joins, partial updates, and a step‑by‑step Spring Boot integration guide, before concluding on its maturity and suitability for production.
Overview
MyBatis is the dominant ORM in Java back‑ends. MyBatis‑Plus (open‑source since 2016) and Fluent‑MyBatis (developed by Alibaba Cloud) are widely used extensions. MyBatis‑Flex is a newer, lightweight enhancement that has attracted over 1 000 GitHub stars.
Feature comparison
Basic CRUD on entity: supported by all three.
Pagination query: supported by all three.
Pagination total cache: supported by Flex and Plus, not by Fluent‑MyBatis.
Pagination without SQL parsing (lighter, higher performance): supported by Flex and Fluent‑MyBatis, not by Plus.
Multi‑table query (FROM multiple tables, LEFT/INNER JOIN, UNION/UNION ALL): supported only by Flex; Plus lacks support; Fluent‑MyBatis supports joins but not FROM‑multiple‑tables syntax.
Composite primary‑key support: only Flex provides it.
No third‑party dependencies besides MyBatis: only Flex is dependency‑free.
QueryWrapper RPC support for micro‑services: Flex supports it, Plus does not, Fluent‑MyBatis status unknown.
SQL audit, data masking, field permission, field encryption, dictionary write‑back: only Flex provides these features out‑of‑the‑box; Plus offers them in paid editions; Fluent‑MyBatis does not.
Db + Row, entity listener, multi‑datasource (including Spring transaction and non‑Spring), dynamic schema: only Flex implements them.
Multi‑tenant and dynamic table name: Flex and Plus support multi‑tenant; Flex and Plus support dynamic table name; Fluent‑MyBatis supports neither.
Usage examples
Basic query
MyBatis‑Flex
QueryWrapper query = QueryWrapper.create()
.where(EMPLOYEE.LAST_NAME.like(searchWord)) // null condition ignored
.and(EMPLOYEE.GENDER.eq(1))
.and(EMPLOYEE.AGE.gt(24));
List<Employee> employees = employeeMapper.selectListByQuery(query);MyBatis‑Plus
QueryWrapper<Employee> queryWrapper = Wrappers.query()
.like(searchWord != null, "last_name", searchWord)
.eq("gender", 1)
.gt("age", 24);
List<Employee> employees = employeeMapper.selectList(queryWrapper);Lambda style (Plus)
LambdaQueryWrapper<Employee> queryWrapper = Wrappers.<Employee>lambdaQuery()
.like(StringUtils.isNotEmpty(searchWord), Employee::getUserName, "B")
.eq(Employee::getGender, 1)
.gt(Employee::getAge, 24);
List<Employee> employees = employeeMapper.selectList(queryWrapper);Fluent‑MyBatis
EmployeeQuery query = new EmployeeQuery()
.where.lastName().like(searchWord, If::notNull)
.and.gender().eq(1)
.and.age().gt(24)
.end();
List<Employee> employees = employeeMapper.listEntity(query);Aggregation functions
MyBatis‑Flex
QueryWrapper query = QueryWrapper.create()
.select(
ACCOUNT.ID,
ACCOUNT.USER_NAME,
max(ACCOUNT.BIRTHDAY),
avg(ACCOUNT.SEX).as("sex_avg")
);
List<Employee> employees = employeeMapper.selectListByQuery(query);MyBatis‑Plus
QueryWrapper<Employee> queryWrapper = Wrappers.query()
.select(
"id",
"user_name",
"max(birthday)",
"avg(birthday) as sex_avg"
);
List<Employee> employees = employeeMapper.selectList(queryWrapper);Drawback: field names are hard‑coded, prone to typos, and IDE refactoring/auto‑completion is unavailable.
Fluent‑MyBatis
EmployeeQuery query = new EmployeeQuery()
.select
.id()
.userName()
.max.birthday()
.avg.sex("sex_avg")
.end();
List<Employee> employees = employeeMapper.listEntity(query);Drawback: the DSL does not follow natural SQL intuition.
AND/OR conditions
Target SQL:
SELECT * FROM tb_account
WHERE id >= 100 AND (sex = 1 OR sex = 2) OR (age IN (18,19,20) AND user_name LIKE "%michael%")MyBatis‑Flex
QueryWrapper query = QueryWrapper.create()
.where(ACCOUNT.ID.ge(100))
.and(ACCOUNT.SEX.eq(1).or(ACCOUNT.SEX.eq(2)))
.or(ACCOUNT.AGE.in(18,19,20).and(ACCOUNT.USER_NAME.like("michael")));MyBatis‑Plus
QueryWrapper<Employee> query = Wrappers.query()
.ge("id", 100)
.and(i -> i.eq("sex", 1).or(x -> x.eq("sex", 2)))
.or(i -> i.in("age", 18,19,20).like("user_name", "michael")));Lambda version (Plus)
LambdaQueryWrapper<Employee> query = Wrappers.<Employee>lambdaQuery()
.ge(Employee::getId, 100)
.and(i -> i.eq(Employee::getSex, 1).or(x -> x.eq(Employee::getSex, 2)))
.or(i -> i.in(Employee::getAge, 18,19,20).like(Employee::getUserName, "michael"));Fluent‑MyBatis
AccountQuery query = new AccountQuery()
.where.id().ge(100)
.and(new AccountQuery().where.sex().eq(1).or(new AccountQuery().where.sex().eq(2).end()))
.or(new AccountQuery().where.age.in(18,19,20).and.userName().like("michael").end())
.end();Drawback: many .end() calls are easy to forget or misuse.
Multi‑table query
MyBatis‑Flex
QueryWrapper query = QueryWrapper.create()
.select().from(ACCOUNT)
.leftJoin(ARTICLE).on(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID))
.where(ACCOUNT.AGE.ge(10));
List<Account> accounts = mapper.selectListByQuery(query);MyBatis‑Plus // Not supported Fluent‑MyBatis
StudentQuery leftQuery = new StudentQuery("a1").selectAll().where.age().eq(34).end();
HomeAddressQuery rightQuery = new HomeAddressQuery("a2").where.address().like("address").end();
IQuery query = leftQuery
.join(rightQuery)
.on(l -> l.where.homeAddressId(), r -> r.where.id()).endJoin()
.build();
List<StudentEntity> entities = this.mapper.listEntity(query);Drawback: the DSL does not match SQL intuition and the numerous .end() / .endJoin() calls are easy to miss.
Partial field update
Desired SQL:
UPDATE tb_account SET user_name = "michael", age = 18, birthday = NULL WHERE id = 100;MyBatis‑Flex
Account account = UpdateEntity.of(Account.class);
account.setId(100); // primary key
account.setUserName("michael");
account.setAge(18);
account.setBirthday(null);
accountMapper.update(account);MyBatis‑Plus (UpdateWrapper)
UpdateWrapper<Account> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", 100);
updateWrapper.set("user_name", "michael");
updateWrapper.set("age", 18);
updateWrapper.set("birthday", null);
accountMapper.update(null, updateWrapper);Fluent‑MyBatis
AccountUpdate update = new AccountUpdate()
.update.userName().is("michael")
.age().is(18)
.birthday().is(null)
.end()
.where.id().eq(100).end();
accountMapper.updateBy(update);Spring Boot integration of MyBatis‑Flex
Step 1 – Create database table
CREATE TABLE IF NOT EXISTS `tb_account` (
`id` INTEGER PRIMARY KEY AUTO_INCREMENT,
`user_name` VARCHAR(100),
`age` INTEGER,
`birthday` DATETIME
);
INSERT INTO tb_account(id, user_name, age, birthday) VALUES
(1, '张三', 18, '2020-01-11'),
(2, '李四', 19, '2021-03-21');Step 2 – Add Maven dependencies
<dependencies>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
<version>1.7.5</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- test only -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>Step 3 – Configure datasource (application.yml)
# DataSource Config
spring:
datasource:
url: jdbc:mysql://localhost:3306/flex_test
username: root
password: 123456Step 4 – Add @MapperScan to the Spring Boot main class
@SpringBootApplication
@MapperScan("com.mybatisflex.test.mapper")
public class MybatisFlexTestApplication {
public static void main(String[] args) {
SpringApplication.run(MybatisFlexTestApplication.class, args);
}
}Step 5 – Define entity and mapper
@Data
@Table("tb_account")
public class Account {
@Id(keyType = KeyType.Auto)
private Long id;
private String userName;
private Integer age;
private Date birthday;
}
public interface AccountMapper extends BaseMapper<Account> { }Step 6 – Write a test case
import static com.mybatisflex.test.entity.table.AccountTableDef.ACCOUNT;
@SpringBootTest
class MybatisFlexTestApplicationTests {
@Autowired
private AccountMapper accountMapper;
@Test
void contextLoads() {
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.where(ACCOUNT.AGE.eq(18));
Account account = accountMapper.selectOneByQuery(queryWrapper);
System.out.println(account);
}
}Console output:
Account(id=1, userName=张三, age=18, birthday=Sat Jan 11 00:00:00 CST 2020)Conclusion
MyBatis‑Flex is still under heavy development, exhibits a relatively high bug rate, and has limited production‑grade experience. It can be learned quickly (approximately one week of hands‑on practice), but teams should evaluate its stability before adopting it as the primary ORM for critical projects.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Shepherd Advanced Notes
Dedicated to sharing advanced Java technical insights, daily work snippets, and the power of persistent effort.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
