Article Directory
website
- Official website: https://mybatis-flex.com/
- Gitee address: https://gitee.com/mybatis-flex/mybatis-flex
- "Function" comparison between MyBatis-Flex and similar frameworks: https://mybatis-flex.com/zh/intro/comparison.html
- "Performance" comparison between MyBatis-Flex and similar frameworks: https://mybatis-flex.com/zh/intro/benchmark.html
What is MyBatis-Flex
MyBatis-Flex is an elegant MyBatis enhanced framework, which is very lightweight, has high performance and flexibility at the same time. We can easily use Mybaits-Flex to link any database, and its built-in QueryWrapper helps us greatly reduce the work of SQL writing and reduce the possibility of errors.
All in all, MyBatis-Flex can greatly improve our development efficiency and development experience, allowing us to have more time to focus on our own things.
feature
1. Very lightweight
The entire framework of MyBatis-Flex only depends on MyBatis, and no other third-party dependencies.
2. Only enhance
MyBatis-Flex supports CRUD, paging query, multi-table query, and batch operations without losing any of the original functions of MyBatis.
3. High performance
MyBatis-Flex adopts a unique technical architecture. Compared with many similar frameworks, MyBatis-Flex's performance in terms of addition, deletion, modification and query is 5-10 times or more.
4. More agile
MyBatis-Flex supports multiple primary keys, multi-table queries, logical deletion, optimistic locking, data desensitization, data encryption, multiple data sources, sub-database sub-tables, field permissions, field encryption, multi-tenancy, transaction management, SQL auditing,
etc. etc. All this, free and flexible.
quick start
- quick start
- Example 1: Mybatis-Flex native (non-Spring)
- Example 2: Mybatis-Flex with Spring
- Example 3: Mybatis-Flex with Spring boot
- Example 4: Db + Row
hello world (native)
Step 1: Write the Entity entity class
@Table("tb_account")
public class Account {
@Id(keyType = KeyType.Auto)
private Long id;
private String userName;
private Date birthday;
private int sex;
//getter setter
}
Step 2: Start querying data
Example 1: Query 1 piece of data
class HelloWorld {
public static void main(String... args) {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/mybatis-flex");
dataSource.setUsername("username");
dataSource.setPassword("password");
MybatisFlexBootstrap.getInstance()
.setDataSource(dataSource)
.addMapper(AccountMapper.class)
.start();
AccountMapper mapper = MybatisFlexBootstrap.getInstance()
.getMapper(AccountMapper.class);
//示例1:查询 id=100 条数据
Account account = mapper.selectOneById(100);
}
}
The above
AccountMapper.class
is automatically generated by Mybatis-Flex through APT, without manual coding. You can also turn off the automatic generation function and write AccountMapper manually. See the APT documentation for more information.
Example 2: Query List
//示例2:通过 QueryWrapper 构建条件查询数据列表
QueryWrapper query = QueryWrapper.create()
.select()
.from(ACCOUNT) // 单表查询时表名可省略,自动使用Mapper泛型对应的表
.where(ACCOUNT.ID.ge(100))
.and(ACCOUNT.USER_NAME.like("张").or(ACCOUNT.USER_NAME.like("李")));
// 执行 SQL:
// SELECT * FROM tb_account
// WHERE tb_account.id >= 100
// AND (tb_account.user_name LIKE '%张%' OR tb_account.user_name LIKE '%李%' )
List<Account> accounts = accountMapper.selectListByQuery(query);
Example 3: Paging query
// 示例3:分页查询
// 查询第 5 页,每页 10 条数据,通过 QueryWrapper 构建条件查询
QueryWrapper query=QueryWrapper.create()
.select()
.from(ACCOUNT)
.where(ACCOUNT.ID.ge(100))
.and(ACCOUNT.USER_NAME.like("张").or(ACCOUNT.USER_NAME.like("李")))
.orderBy(ACCOUNT.ID.desc());
// 执行 SQL:
// SELECT * FROM tb_account
// WHERE id >= 100
// AND (user_name LIKE '%张%' OR user_name LIKE '%李%' )
// ORDER BY `id` DESC
// LIMIT 40,10
Page<Account> accounts = mapper.paginate(5, 10, query);
QueryWrapper example
select *
QueryWrapper query = new QueryWrapper();
query.select().from(ACCOUNT);
// SQL:
// SELECT * FROM tb_account
It can also be abbreviated into the following two forms through the static method, and the effect is exactly the same:
// 方式1
QueryWrapper query = QueryWrapper.create()
.select().from(ACCOUNT);
// 方式2
QueryWrapper query = select().from(ACCOUNT);
// SQL:
// SELECT * FROM tb_account
select columns
Simple example:
QueryWrapper query = new QueryWrapper();
query.select(ACCOUNT.ID, ACCOUNT.USER_NAME)
.from(ACCOUNT);
// SQL:
// SELECT id, user_name
// FROM tb_account
Multi-table query (also demonstrates powerful as
capabilities):
QueryWrapper query = new QueryWrapper()
.select(ACCOUNT.ID
, ACCOUNT.USER_NAME
, ARTICLE.ID.as("articleId")
, ARTICLE.TITLE)
.from(ACCOUNT.as("a"), ARTICLE.as("b"))
.where(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID));
// SQL:
// SELECT a.id, a.user_name, b.id AS articleId, b.title
// FROM tb_account AS a, tb_article AS b
// WHERE a.id = b.account_id
select functions
QueryWrapper query = new QueryWrapper()
.select(
ACCOUNT.ID,
ACCOUNT.USER_NAME,
max(ACCOUNT.BIRTHDAY),
avg(ACCOUNT.SEX).as("sex_avg")
).from(ACCOUNT);
// SQL:
// SELECT id, user_name,
// MAX(birthday),
// AVG(sex) AS sex_avg
// FROM tb_account
where
Integer num = 100;
String userName = "michael";
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.from(ACCOUNT)
.where(ACCOUNT.ID.ge(100))
.and(ACCOUNT.USER_NAME.like("michael"));
// SQL:
// SELECT * FROM tb_account
// WHERE id >= ?
// AND user_name LIKE ?
where dynamic condition 1
boolean flag = false;
QueryWrapper queryWrapper = QueryWrapper.create()
.select().from(ACCOUNT)
.where(flag ? ACCOUNT.ID.ge(100) : noCondition())
.and(ACCOUNT.USER_NAME.like("michael"));
// SQL:
// SELECT * FROM tb_account
// WHERE user_name LIKE ?
where dynamic condition 2
boolean flag = false;
QueryWrapper queryWrapper = QueryWrapper.create()
.select().from(ACCOUNT)
.where(ACCOUNT.ID.ge(100).when(flag))
.and(ACCOUNT.USER_NAME.like("michael"));
// SQL:
// SELECT * FROM tb_account
// WHERE user_name LIKE ?
where automatically ignores null values
When the value of the condition is null, the condition will be ignored automatically and will not be spliced into SQL
Integer num = null;
String userName = "michael";
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.from(ACCOUNT)
.where(ACCOUNT.ID.ge(num))
.and(ACCOUNT.USER_NAME.like(userName));
// SQL:
// SELECT * FROM tb_account
// WHERE user_name LIKE '%michael%'
where select
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.from(ACCOUNT)
.where(ACCOUNT.ID.ge(
select(ARTICLE.ACCOUNT_ID).from(ARTICLE).where(ARTICLE.ID.ge(100))
));
// SQL:
// SELECT * FROM tb_account
// WHERE id >=
// (SELECT account_id FROM tb_article WHERE id >= ? )
exists, not exists
QueryWrapper queryWrapper=QueryWrapper.create()
.select()
.from(ACCOUNT)
.where(ACCOUNT.ID.ge(100))
.and(
exists( // or notExists(...)
selectOne().from(ARTICLE).where(ARTICLE.ID.ge(100))
)
);
// SQL:
// SELECT * FROM tb_account
// WHERE id >= ?
// AND EXIST (
// SELECT 1 FROM tb_article WHERE id >= ?
// )
and (…) or (…)
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.from(ACCOUNT)
.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")));
// SQL:
// SELECT * FROM tb_account
// WHERE id >= ?
// AND (sex = ? OR sex = ? )
// OR (age IN (?,?,?) AND user_name LIKE ? )
group by
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.from(ACCOUNT)
.groupBy(ACCOUNT.USER_NAME);
// SQL:
// SELECT * FROM tb_account
// GROUP BY user_name
having
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.from(ACCOUNT)
.groupBy(ACCOUNT.USER_NAME)
.having(ACCOUNT.AGE.between(18,25));
// SQL:
// SELECT * FROM tb_account
// GROUP BY user_name
// HAVING age BETWEEN ? AND ?
orderBy
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.from(ACCOUNT)
.orderBy(ACCOUNT.AGE.asc()
, ACCOUNT.USER_NAME.desc().nullsLast());
// SQL:
// SELECT * FROM tb_account
// ORDER BY age ASC, user_name DESC NULLS LAST
join
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.from(ACCOUNT)
.leftJoin(ARTICLE).on(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID))
.innerJoin(ARTICLE).on(ACCOUNT.ID.eq(ARTICLE.ACCOUNT_ID))
.where(ACCOUNT.AGE.ge(10));
// SQL:
// SELECT * FROM tb_account
// LEFT JOIN tb_article ON tb_account.id = tb_article.account_id
// INNER JOIN tb_article ON tb_account.id = tb_article.account_id
// WHERE tb_account.age >= ?
limit… offset
QueryWrapper queryWrapper = QueryWrapper.create()
.select()
.from(ACCOUNT)
.orderBy(ACCOUNT.ID.desc())
.limit(10)
.offset(20);
// MySql:
// SELECT * FROM `tb_account` ORDER BY `id` DESC LIMIT 20, 10
// PostgreSQL:
// SELECT * FROM "tb_account" ORDER BY "id" DESC LIMIT 20 OFFSET 10
// Informix:
// SELECT SKIP 20 FIRST 10 * FROM "tb_account" ORDER BY "id" DESC
// Oracle:
// SELECT * FROM (SELECT TEMP_DATAS.*,
// ROWNUM RN FROM (
// SELECT * FROM "tb_account" ORDER BY "id" DESC)
// TEMP_DATAS WHERE ROWNUM <=30)
// WHERE RN >20
// Db2:
// SELECT * FROM "tb_account" ORDER BY "id" DESC
// OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
// Sybase:
// SELECT TOP 10 START AT 21 * FROM "tb_account" ORDER BY "id" DESC
// Firebird:
// SELECT * FROM "tb_account" ORDER BY "id" DESC ROWS 20 TO 30
In the above "limit...offset" example, Mybatis-Flex can automatically identify the current database and generate different SQL. Users can also easily register
DialectFactory
(add or rewrite) their own implementation dialects.
Have questions?
Question 1: Can QueryWrapper be transmitted via RPC in a distributed project?
Answer: Yes.
Question 2: How to generate the "ACCOUNT" class required by QueryWrapper through the entity class Account.java?
Answer: Mybatis-Flex uses APT (Annotation Processing Tool) technology. When the project is compiled, it will automatically generate the "ACCOUNT" class and the Mapper class corresponding to the Entity class according to the fields defined by the Entity class. Build the project through the development tool (as
follows Figure), or execute the maven compilation command: mvn clean package
can be automatically generated. This principle is consistent with lombok.
Db + Row tool class
The Db + Row tool class provides database operation capabilities beyond the Entity entity class. When using Db + Row, there is no need to map the database table. Row is a subclass of HashMap, which is equivalent to a general Entity. Here are some examples of Db + Row:
//使用原生 SQL 插入数据
String sql="insert into tb_account(id,name) value (?, ?)";
Db.insertBySql(sql, 1, "michael");
//使用 Row 插入数据
Row account = new Row();
account.set("id", 100);
account.set("name", "Michael");
Db.insert("tb_account", account);
//根据主键查询数据
Row row = Db.selectOneById("tb_account", "id", 1);
//Row 可以直接转换为 Entity 实体类,且性能极高
Account account = row.toEntity(Account.class);
//查询所有大于 18 岁的用户
String listsql = "select * from tb_account where age > ?"
List<Row> rows = Db.selectListBySql(sql, 18);
//分页查询:每页 10 条数据,查询第 3 页的年龄大于 18 的用户
QueryWrapper query = QueryWrapper.create()
.where(ACCOUNT.AGE.ge(18));
Page<Row> rowPage = Db.paginate("tb_account", 3, 10, query);
The Db tool class also provides more methods for adding, deleting, modifying, checking, and paging queries.
Specific reference: Db.java .
Entity partial field update
Compared with other frameworks on the market, this part of the function should also be regarded as one of the highlights of MyBatis-Flex. In BaseMapper, Mybatis-Flex provides the following methods:
update(T entity)
In some scenarios, we may want to update only a few fields, and some of them need to be updated to null. You need to use UpdateEntity
the tool class at this time, the following is the sample code:
Account account = UpdateEntity.of(Account.class);
account.setId(100);
account.setUserName(null);
account.setSex(1);
accountMapper.update(account);
In the above example, the user_name field in the data whose id is 100 will be updated to null, the sex field will be updated to 1, and other fields will not be updated. That is to say, the UpdateEntity
created object will only update the field whose setter method is called. If the setter method is not called, no matter what the value of the attribute in this object is, it will not be updated to the database.
The generated sql content is as follows:
update tb_account
set user_name = ?, sex = ? where id = ?
#params: null,1,100
Custom TypeHandler
Use the @Column annotation:
@Table("tb_account")
public class Account {
@Id(keyType = KeyType.Auto)
private Long id;
private String userName;
@Column(typeHandler = Fastjson2TypeHandler.class)
private Map<String, Object> options;
//getter setter
public void addOption(String key, Object value) {
if (options == null) {
options = new HashMap<>();
}
options.put(key, value);
}
}
Insert data:
Account account = new Account();
account.setUserName("test");
account.addOption("c1", 11);
account.addOption("c2", "zhang");
account.addOption("c3", new Date());
mybatis log:
==> Preparing: INSERT INTO tb_account (user_name, options) VALUES (?, ?)
==> Parameters: test(String), {"c3":"2023-03-17 09:10:16.546","c1":11,"c2":"zhang"}(String)
multiple primary keys
@Id
Mybatis-Flex multi-primary key is nothing more than multiple annotations in the Entity class , such as:
@Table("tb_account")
public class Account {
@Id(keyType = KeyType.Auto)
private Long id;
@Id(keyType = KeyType.Generator, value = "uuid")
private String otherId;
//getter setter
}
When we save the data, the id primary key of Account is self-incrementing, while the otherId primary key is generated by uuid.
custom primary key generator
Step 1: Write a class that implements IKeyGenerator
the interface, for example:
public class UUIDKeyGenerator implements IKeyGenerator {
@Override
public Object generate(Object entity, String keyColumn) {
return UUID.randomUUID().toString().replace("-", "");
}
}
Step 2: Register UUIDKeyGenerator
KeyGeneratorFactory.register("myUUID", new UUIDKeyGenerator());
Step 3: Use the "myUUID" generator in Entity:
@Table("tb_account")
public class Account {
@Id(keyType = KeyType.Generator, value = "myUUID")
private String otherId;
//getter setter
}
Generated using database Sequence
@Table("tb_account")
public class Account {
@Id(keyType = KeyType.Sequence, value = "select SEQ_USER_ID.nextval as id from dual")
private Long id;
}
Spring Boot integrates MyBatis-Flex
Maven dependencies
Scenarios where Spring is not used, scenarios where Spring is used, and scenarios where Spring Boot is used, the dependencies added by the three are different.
Create a Spring Boot project and add Maven dependencies
You can use Spring Initializer to quickly initialize a Spring Boot project.
Examples of major Maven dependencies that need to be added:
<dependencies>
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-spring-boot-starter</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
</dependencies>
Code generator
In the module of mybatis-flex mybatis-flex-codegen
, it provides the function of generating Entity class and Mapper class through database table. After we design the database table, we can use it to quickly generate the java classes of Entity and Mapper.
mybatis-flex-codegen
Add the Maven dependencies before using :
<dependency>
<groupId>com.mybatis-flex</groupId>
<artifactId>mybatis-flex-codegen</artifactId>
<version>1.5.6</version>
</dependency>
Then, write an arbitrary class with a main method like this:
package com.chh.config;
import com.mybatisflex.codegen.Generator;
import com.mybatisflex.codegen.config.ColumnConfig;
import com.mybatisflex.codegen.config.GlobalConfig;
import com.zaxxer.hikari.HikariDataSource;
public class CodegenConfig {
public static void main(String[] args) {
//配置数据源
HikariDataSource dataSource = new HikariDataSource();
// MySQL
// dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/your-database?characterEncoding=utf-8");
// PostgreSQL
dataSource.setJdbcUrl("jdbc:postgresql://localhost:5432/your-database");
dataSource.setUsername("root");
dataSource.setPassword("******");
//创建配置内容,两种风格都可以。
GlobalConfig globalConfig = createGlobalConfigUseStyle1();
//GlobalConfig globalConfig = createGlobalConfigUseStyle2();
//通过 datasource 和 globalConfig 创建代码生成器
Generator generator = new Generator(dataSource, globalConfig);
//生成代码
generator.generate();
}
public static GlobalConfig createGlobalConfigUseStyle1() {
//创建配置内容
GlobalConfig globalConfig = new GlobalConfig();
//设置根包
globalConfig.setBasePackage("com.test");
//设置表前缀和只生成哪些表
globalConfig.setTablePrefix("tb_");
globalConfig.setGenerateTable("tb_account", "tb_account_session");
//设置生成 entity 并启用 Lombok
globalConfig.setEntityGenerateEnable(true);
globalConfig.setEntityWithLombok(true);
//设置生成 mapper
globalConfig.setMapperGenerateEnable(true);
//可以单独配置某个列
ColumnConfig columnConfig = new ColumnConfig();
columnConfig.setColumnName("tenant_id");
columnConfig.setLarge(true);
columnConfig.setVersion(true);
globalConfig.setColumnConfig("tb_account", columnConfig);
return globalConfig;
}
public static GlobalConfig createGlobalConfigUseStyle2() {
//创建配置内容
GlobalConfig globalConfig = new GlobalConfig();
//设置根包
globalConfig.getPackageConfig()
.setBasePackage("com.test");
//设置表前缀和只生成哪些表,setGenerateTable 未配置时,生成所有表
globalConfig.getStrategyConfig()
.setTablePrefix("tb_")
.setGenerateTable("tb_account", "tb_account_session");
//设置生成 entity 并启用 Lombok
globalConfig.enableEntity()
.setWithLombok(true);
//设置生成 mapper
globalConfig.enableMapper();
//可以单独配置某个列
ColumnConfig columnConfig = new ColumnConfig();
columnConfig.setColumnName("tenant_id");
columnConfig.setLarge(true);
columnConfig.setVersion(true);
globalConfig.getStrategyConfig()
.setColumnConfig("tb_account", columnConfig);
return globalConfig;
}
}
For details, please see MyBatis-Flex Code Generator
Note: Since the APT function of MyBatis-Flex will automatically generate the Java class of Mapper for us, if we choose to generate Mapper in the code generator, it is recommended to turn off the Mapper generation function of APT, otherwise there will be two copies in the system Mapper with the same function.
To disable APT's Mapper class file generation, please refer to: APT Settings Chapter
code generation plugin
install Mybatis-Flex-Helper
plugin
Connect to the database, select the database table, right-click to generate code
Set build configuration
remove table prefix
Custom generated file extension
generate result