MyBatis-Flex An elegant MyBatis enhancement framework


website

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

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.classis 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 ascapabilities):

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 packagecan be automatically generated. This principle is consistent with lombok.

insert image description here

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

@IdMybatis-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 IKeyGeneratorthe 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-codegenAdd 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-Helperplugin

insert image description here

Connect to the database, select the database table, right-click to generate code

insert image description here

Set build configuration

insert image description here
remove table prefix

insert image description here

Custom generated file extension

insert image description here
generate result

insert image description here

Guess you like

Origin blog.csdn.net/xiaohuihui1400/article/details/132146263