Fluent MyBatis使用入門

引言

Java中常用的ORM框架主要是mybatis, hibernate, JPA等框架。

國內(nèi)又以Mybatis用的多,基于mybatis上的增強(qiáng)框架,又有mybatis plus和TK mybatis等。

今天我們介紹一個新的mybatis增強(qiáng)框架 fluent mybatis,

那既然JDBC --> Mybatis或Mybatis Plus無疑簡化了開發(fā)者的工作,而今天我們所講的 Fluent MyBatis又起到什么作用呢?

初識Fluent MyBatis

Fluent MyBatis是一個 MyBatis 的增強(qiáng)工具,他只做了mybatis的語法糖封裝,沒有對mybatis做任何修改。

通過編譯手段,提供了一系列輔助類來幫助開發(fā)簡化開發(fā)、提高效率。

入門初體驗(yàn)

創(chuàng)建一個示例的數(shù)據(jù)庫表

```sql

DROP TABLE IF EXISTS `your_table`;

create table `your_table`

(

? ? id bigint auto_increment comment '主鍵ID' primary key,

? ? name varchar(30) charset utf8 null comment '姓名',

? ? age int null comment '年齡',

? ? email varchar(50) charset utf8 null comment '郵箱',

? ? gmt_create datetime null comment '記錄創(chuàng)建時間',

? ? gmt_modified datetime null comment '記錄最后修改時間',

? ? is_deleted tinyint(2) default 0 null comment '邏輯刪除標(biāo)識'

);

```

初始化 SpringBoot 項(xiàng)目

設(shè)置項(xiàng)目依賴

1. spring boot: 基于spring boot開發(fā),肯定是必須的

2. lombok: 省略get, set, toString代碼的神器,個人比較喜歡;你也可以手動生成get set方法

3. mysql-connector-java: 數(shù)據(jù)庫驅(qū)動

4. fluent-mybatis: fluent-mybatis運(yùn)行時依賴

5. fluent-mybatis-processor: fluent-mybatis編譯時依賴

6. fluent-mybatis-generator: fluent-mybatis代碼生成依賴

6. 測試依賴的jar包: spring-test, junit

[maven pom具體配置]

配置數(shù)據(jù)庫信息

```properties

spring.datasource.username=root

spring.datasource.password=password

spring.datasource.url=jdbc:mysql://localhost:3306/fluent_mybatis_demo?useSSL=false&useUnicode=true&characterEncoding=utf-8

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

```

[properties具體配置]

創(chuàng)建(生成)實(shí)體類

可以手工創(chuàng)建Entity類,或者任何手段創(chuàng)建的Entity類,然后加上注解

1. 在Entity類上加上 @FluentMybatis注解

2. 在主鍵字段加 @TableId注解

3. 在一般字段加 @TableField注解

這里直接使用fluent mybatis提供的工具類生成代碼

```java

public class AppEntityGenerator {

? ? static final String url = "jdbc:mysql://localhost:3306/fluent_mybatis_demo?useSSL=false&useUnicode=true&characterEncoding=utf-8";

? ? public static void main(String[] args) {

? ? ? ? FileGenerator.build(Abc.class);

? ? }

? ? @Tables(

? ? ? ? /** 數(shù)據(jù)庫連接信息 **/

? ? ? ? url = url, username = "root", password = "password",

? ? ? ? /** Entity類parent package路徑 **/

? ? ? ? basePack = "cn.org.fluent.mybatis.springboot.demo",

? ? ? ? /** Entity代碼源目錄 **/

? ? ? ? srcDir = "spring-boot-demo/src/main/java",

? ? ? ? /** Dao代碼源目錄 **/

? ? ? ? daoDir = "spring-boot-demo/src/main/java",

? ? ? ? /** 如果表定義記錄創(chuàng)建,記錄修改,邏輯刪除字段 **/

? ? ? ? gmtCreated = "gmt_create", gmtModified = "gmt_modified", logicDeleted = "is_deleted",

? ? ? ? /** 需要生成文件的表 **/

? ? ? ? tables = @Table(value = {"your_table"})

? ? )

? ? static class Abc {

? ? }

}

```

[具體代碼]

這里有3個特殊字段

1. gmt_create, 記錄創(chuàng)建時間,會設(shè)置記錄插入的默認(rèn)值,對應(yīng)生成Entity字段上的注解 @TableField(insert="now()")

2. gmt_modified, 記錄最后更新時間,會設(shè)置記錄插入和更新默認(rèn)值,對應(yīng)生成代碼Entity字段上注解? @TableField(insert="now()", update="now()")

3. is_deleted, 記錄邏輯刪除標(biāo)識,字段類型為Boolean,且設(shè)置記錄插入的默認(rèn)值,對應(yīng)注解 @TableField(insert="0")

執(zhí)行生成代碼main函數(shù), 在工程main/src/java目錄下產(chǎn)出 Entity, DaoIntf, DaoImpl文件;

觀察YourEntity的主鍵 id, gmt_create, gmt_modified, is_deleted這幾個字段的注解

```java

@Data

@Accessors(chain = true)

@FluentMybatis(table = "your_table")

public class YourEntity implements IEntity{

? ? private static final long serialVersionUID = 1L;

? ? @TableId(value = "id")

? ? private Long id;

? ? @TableField(value = "gmt_create", insert = "now()")

? ? private Date gmtCreate;

? ? @TableField(value = "gmt_modified", insert = "now()", update = "now()")

? ? private Date gmtModified;

? ? @TableField(value = "is_deleted", insert = "0")

? ? private Boolean isDeleted;

? ? @TableField(value = "age")

? ? private Integer age;

? ? @TableField(value = "email")

? ? private String email;

? ? @TableField(value = "name")

? ? private String name;

? ? @Override

? ? public Serializable findPk() {

? ? ? ? return id;

? ? }

}

```

生成的Dao文件,引用到了YourTableBaseDao類,這個類需要根據(jù)Entity類編譯生成,在重新編譯前會有編譯錯誤,所以生成代碼后需要重新Rebuild下

```java

@Repository

public class YourDaoImpl extends YourBaseDao implements YourDao {

? ? // 在這里添加你自己的業(yè)務(wù)邏輯代碼

}

```


在Rebuild后,會在target目錄下就會多出幾個文件, 重新刷新一下工程把target/generated-sources加到源目錄上即可。


啟動SpringBoot測試,驗(yàn)證效果

這時工程已經(jīng)具備fluent mybatis強(qiáng)大的增刪改查功能了。我們創(chuàng)建一個測試類來驗(yàn)證一下,在測試類中注入 YourMapper,這里演示一個查詢所有的方法,所以使用了 listEntity ,其參數(shù)是一個Query對象。

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void contextLoads() {

? ? ? ? List<YourEntity> list = yourMapper.listEntity(yourMapper.query());

? ? ? ? for (YourEntity entity : list) {

? ? ? ? ? ? System.out.println(entity);

? ? ? ? }

? ? }

}

```

你可以手工往數(shù)據(jù)庫中插入幾條記錄,驗(yàn)證一下效果。

Entity對應(yīng)的Mapper提供的數(shù)據(jù)操作方法

下面我們分別介紹FluentMybatis提供的insert, select, update和delete方法,內(nèi)容的介紹基本按4部分解析

1. 方法的Mapper定義(**編譯生成的代碼**)

2. Mapper對應(yīng)的動態(tài)SQL組裝SQLProvider(**編譯生成的代碼**)

3. 一個驗(yàn)證測試?yán)?/p>

4. 根據(jù)例子打印的SQL語句和信息輸出,對照查看

FluentMybatis提供的insert方法

insert:單條插入操作

-Mapper方法

```java

public interface YourMapper extends IEntityMapper<YourEntity> {

? /**

? * 插入一條記錄

? *

? * @param entity

? * @return

? */

? @Override

? @InsertProvider(

? ? ? type = YourSqlProvider.class,

? ? ? method = "insert"

? )

? @Options(

? ? ? useGeneratedKeys = true,

? ? ? keyProperty = "id",

? ? ? keyColumn = "id"

? )

? int insert(YourEntity entity);

}

```

- 動態(tài)SQL組裝

```java

public class YourSqlProvider {

? ? public String insert(YourEntity entity) {

? ? ? ? assertNotNull("entity", entity);

? ? ? ? MapperSql sql = new MapperSql();

? ? ? ? sql.INSERT_INTO("your_table");

? ? ? ? List<String> columns = new ArrayList<>();

? ? ? ? List<String> values = new ArrayList<>();

? ? ? ? if (entity.getId() != null) {

? ? ? ? ? ? columns.add("id");

? ? ? ? ? ? values.add("#{id}");

? ? ? ? }

? ? ? ? columns.add("gmt_create");

? ? ? ? if (entity.getGmtCreate() != null) {

? ? ? ? ? ? values.add("#{gmtCreate}");

? ? ? ? } else {

? ? ? ? ? ? values.add("now()");

? ? ? ? }

? ? ? ? columns.add("gmt_modified");

? ? ? ? if (entity.getGmtModified() != null) {

? ? ? ? ? ? values.add("#{gmtModified}");

? ? ? ? } else {

? ? ? ? ? ? values.add("now()");

? ? ? ? }

? ? ? ? columns.add("is_deleted");

? ? ? ? if (entity.getIsDeleted() != null) {

? ? ? ? ? ? values.add("#{isDeleted}");

? ? ? ? } else {

? ? ? ? ? ? values.add("0");

? ? ? ? }

? ? ? ? if (entity.getAge() != null) {

? ? ? ? ? ? columns.add("age");

? ? ? ? ? ? values.add("#{age}");

? ? ? ? }

? ? ? ? if (entity.getEmail() != null) {

? ? ? ? ? ? columns.add("email");

? ? ? ? ? ? values.add("#{email}");

? ? ? ? }

? ? ? ? if (entity.getName() != null) {

? ? ? ? ? ? columns.add("name");

? ? ? ? ? ? values.add("#{name}");

? ? ? ? }

? ? ? ? sql.INSERT_COLUMNS(columns);

? ? ? ? sql.VALUES();

? ? ? ? sql.INSERT_VALUES(values);

? ? ? ? return sql.toString();

? ? }

}

```

組裝過程中,對對應(yīng)了 @TableField(insert="默認(rèn)值")的3個字段:gmt_crate, gmt_modified, is_deleted做了特殊判斷。

- 編寫insert test驗(yàn)證下

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void insert() {

? ? ? ? // 構(gòu)造一個對象

? ? ? ? YourEntity entity = new YourEntity();

? ? ? ? entity.setName("Fluent Mybatis");

? ? ? ? entity.setAge(1);

? ? ? ? entity.setEmail("darui.wu@163.com");

? ? ? ? entity.setIsDeleted(false);

? ? ? ? // 插入操作

? ? ? ? int count = yourMapper.insert(entity);

? ? ? ? System.out.println("count:" + count);

? ? ? ? System.out.println("entity:" + entity);

? ? }

}

```

- 執(zhí)行insert測試方法, 查看控制臺輸出log信息

```text

DEBUG - ==>? Preparing: INSERT INTO your_table(gmt_create, gmt_modified, is_deleted, age, email, name) VALUES (now(), now(), ?, ?, ?, ?)?

DEBUG - ==> Parameters: false(Boolean), 1(Integer), darui.wu@163.com(String), Fluent Mybatis(String)

DEBUG - <==? ? Updates: 1

count:1

entity:YourEntity(id=18, gmtCreate=null, gmtModified=null, isDeleted=false, age=1, email=darui.wu@163.com, name=Fluent Mybatis)

```

- 這里有幾個需要注意的地方

1. Entity主鍵值的自增和回寫

根據(jù)控制臺輸出,可以看到Entity的id屬性已經(jīng)是根據(jù)數(shù)據(jù)庫自增主鍵值回寫過的。

自增主鍵的設(shè)置是通過 @TableId 注解來的,其屬性方法auto()默認(rèn)值是true。

2. fluent mybatis根據(jù)@TableId注解生成的Mapper類上@Options注解如下:

``` java

@Options(

? useGeneratedKeys = true,

? keyProperty = "id",

? keyColumn = "id"

)

```

3. gmt_created, gmt_modified, is_deleted 默認(rèn)值插入處理

我們先看一下Entity上這3個字段的@TableField注解, 他們都定義了一個屬性方法insert,設(shè)置了insert的默認(rèn)值(即程序編碼insert時,如果沒有設(shè)置該字段,則使用默認(rèn)值)

``` java

? ? @TableField(value = "gmt_create", insert = "now()")

? ? private Date gmtCreate;

? ? @TableField(value = "gmt_modified", insert = "now()", update = "now()")

? ? private Date gmtModified;

? ? @TableField(value = "is_deleted", insert = "0")

? ? private Boolean isDeleted;

```

在測試?yán)又?,gmt_created和gmt_modified在初始化Entity時,沒有設(shè)置任何值; is_deleted設(shè)置了值false。

在構(gòu)建sql是,gmt_created, gmt_modified直接使用默認(rèn)值 "now()", is_deleted使用預(yù)編譯變量(?)設(shè)置(實(shí)際值false)。

```sql

INSERT INTO your_table

(gmt_create, gmt_modified, is_deleted, age, email, name)

VALUES

(now(), now(), ?, ?, ?, ?)

```

我們再看一下對應(yīng)的SQLProvider的SQL構(gòu)造, 我們只看著3個字段的構(gòu)造

```java

public class YourSqlProvider {

? ? public String insert(YourEntity entity) {

? ? ? ? List<String> columns = new ArrayList<>();

? ? ? ? List<String> values = new ArrayList<>();

? ? ? ? // 省略 ... ...

? ? ? ? columns.add("gmt_create");

? ? ? ? if (entity.getGmtCreate() != null) {

? ? ? ? ? ? values.add("#{gmtCreate}");

? ? ? ? } else {

? ? ? ? ? ? values.add("now()");

? ? ? ? }

? ? ? ? columns.add("gmt_modified");

? ? ? ? if (entity.getGmtModified() != null) {

? ? ? ? ? ? values.add("#{gmtModified}");

? ? ? ? } else {

? ? ? ? ? ? values.add("now()");

? ? ? ? }

? ? ? ? columns.add("is_deleted");

? ? ? ? if (entity.getIsDeleted() != null) {

? ? ? ? ? ? values.add("#{isDeleted}");

? ? ? ? } else {

? ? ? ? ? ? values.add("0");

? ? ? ? }

? ? ? ? if (entity.getAge() != null) {

? ? ? ? ? ? columns.add("age");

? ? ? ? ? ? values.add("#{age}");

? ? ? ? }

? ? ? ? // 省略... ...

? ? ? ? return sql.toString();

? ? }

}

```

我們看到,沒有 insert屬性的字段,只判斷了是否為空; 有insert屬性的字段,如果entity不為空,則把默認(rèn)值賦值給sql語句。

insertBatch:批量插入

-? 查看Mapper對應(yīng)的SqlProvider中insertBatch動態(tài)SQL的構(gòu)造

```java

public class YourSqlProvider {

? ? public String insertBatch(Map map) {

? ? ? ? assertNotEmpty("map", map);

? ? ? ? MapperSql sql = new MapperSql();

? ? ? ? List<YourEntity> entities = getParas(map, "list");

? ? ? ? sql.INSERT_INTO("your_table");

? ? ? ? sql.INSERT_COLUMNS(ALL_ENTITY_FIELDS);

? ? ? ? sql.VALUES();

? ? ? ? for (int index = 0; index < entities.size(); index++) {

? ? ? ? ? ? if (index > 0) {

? ? ? ? ? ? ? ? sql.APPEND(", ");

? ? ? ? ? ? }

? ? ? ? ? ? sql.INSERT_VALUES(

? ? ? ? ? ? ? ? "#{list[" + index + "].id}",

? ? ? ? ? ? ? ? entities.get(index).getGmtCreate() == null ? "now()" : "#{list[" + index + "].gmtCreate}",

? ? ? ? ? ? ? ? entities.get(index).getGmtModified() == null ? "now()" : "#{list[" + index + "].gmtModified}",

? ? ? ? ? ? ? ? entities.get(index).getIsDeleted() == null ? "0" : "#{list[" + index + "].isDeleted}",

? ? ? ? ? ? ? ? "#{list[" + index + "].age}",

? ? ? ? ? ? ? ? "#{list[" + index + "].email}",

? ? ? ? ? ? ? ? "#{list[" + index + "].name}"

? ? ? ? ? ? );

? ? ? ? }

? ? ? ? return sql.toString();

? ? }

}

```

SQL構(gòu)造語句是通過一個for循環(huán)遍歷實(shí)體列表,構(gòu)造出下列SQL語句, 其中對有insert默認(rèn)值屬性處理方式同單條insert一樣, 這里就不再重復(fù)。

```sql

INSERT INTO your_table ('Entity對應(yīng)的字段列表') VALUES ('實(shí)例1值'), ('實(shí)例2值')

```

- 寫個測試看看具體效果

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;


? ? void insertBatch(){

? ? ? ? List<YourEntity> entities = new ArrayList<>();

? ? ? ? entities.add(new YourEntity().setName("Fluent Mybatis").setEmail("darui.wu@163.com"));

? ? ? ? entities.add(new YourEntity().setName("Fluent Mybatis Demo").setEmail("darui.wu@163.com"));

? ? ? ? entities.add(new YourEntity().setName("Test4J").setEmail("darui.wu@163.com"));

? ? ? ? int count = yourMapper.insertBatch(entities);

? ? ? ? System.out.println("count:" + count);

? ? ? ? System.out.println("entity:" + entities);

? ? }

}

```

- 執(zhí)行測試,查看控制臺輸出

```text

DEBUG - ==>? Preparing: INSERT INTO your_table(id, gmt_create, gmt_modified, is_deleted, age, email, name) VALUES (?, now(), now(), 0, ?, ?, ?) , (?, now(), now(), 0, ?, ?, ?) , (?, now(), now(), 0, ?, ?, ?)?

DEBUG - ==> Parameters: null, null, darui.wu@163.com(String), Fluent Mybatis(String), null, null, darui.wu@163.com(String), Fluent Mybatis Demo(String), null, null, darui.wu@163.com(String), Test4J(String)

DEBUG - <==? ? Updates: 3

count:3

entity:[YourEntity(id=null, gmtCreate=null, gmtModified=null, isDeleted=null, age=null, email=darui.wu@163.com, name=Fluent Mybatis), YourEntity(id=null, gmtCreate=null, gmtModified=null, isDeleted=null, age=null, email=darui.wu@163.com, name=Fluent Mybatis Demo), YourEntity(id=null, gmtCreate=null, gmtModified=null, isDeleted=null, age=null, email=darui.wu@163.com, name=Test4J)]

```

FluentMybatis提供的select查詢方法

findById:根據(jù)id查找單條數(shù)據(jù)

- 系統(tǒng)生成的Mapper方法定義

```java

public interface YourMapper extends IEntityMapper<YourEntity> {

? ? String ResultMap = "YourEntityResultMap";

? ? @SelectProvider(

? ? ? ? type = YourSqlProvider.class,

? ? ? ? method = "findById"

? ? )

? ? @Results(

? ? ? ? id = ResultMap,

? ? ? ? value = {

? ? ? ? ? ? @Result(column = "id", property = "id", javaType = Long.class, id = true),

? ? ? ? ? ? @Result(column = "gmt_create", property = "gmtCreate", javaType = Date.class),

? ? ? ? ? ? @Result(column = "gmt_modified", property = "gmtModified", javaType = Date.class),

? ? ? ? ? ? @Result(column = "is_deleted", property = "isDeleted", javaType = Boolean.class),

? ? ? ? ? ? @Result(column = "age", property = "age", javaType = Integer.class),

? ? ? ? ? ? @Result(column = "email", property = "email", javaType = String.class),

? ? ? ? ? ? @Result(column = "name", property = "name", javaType = String.class)

? ? ? ? }

? ? )

? ? YourEntity findById(Serializable id);

}

```

在findById上,除了定義了提供動態(tài)SQL語句的SQLProvider類和方法外,還定義的數(shù)據(jù)映射關(guān)系 @Results。

這個ResultMap映射在單個Mapper里是通用的,其他的查詢方法返回Entity對象時也會用到。

- 系統(tǒng)生成的動態(tài)sql構(gòu)造方法

```java

public class YourSqlProvider {

? ? public String findById(Serializable id) {

? ? ? ? assertNotNull("id", id);

? ? ? ? MapperSql sql = new MapperSql();

? ? ? ? sql.SELECT("your_table", ALL_ENTITY_FIELDS);

? ? ? ? sql.WHERE("id = #{id}");

? ? ? ? return sql.toString();

? ? }

}

```

這個SQL拼接比較簡單

1. 根據(jù)Entity字段拼接了查詢字段列表

2. 設(shè)置 id = #{id}

- 寫個測試實(shí)際使用下

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void findById(){

? ? ? ? YourEntity entity = yourMapper.findById(8L);

? ? ? ? System.out.println(entity);

? ? }

}

```

- 查看控制臺輸出log

```text

DEBUG - ==>? Preparing: SELECT id, gmt_create, gmt_modified, is_deleted, age, email, name FROM your_table WHERE id = ??

DEBUG - ==> Parameters: 8(Long)

DEBUG - <==? ? ? Total: 1

YourEntity(id=8, gmtCreate=null, gmtModified=null, isDeleted=false, age=1, email=darui.wu@163.com, name=Fluent Mybatis)

```

listByIds:根據(jù)id列表批量查詢實(shí)例

- Mapper定義

```java

public interface YourMapper extends IEntityMapper<YourEntity> {

? ? String ResultMap = "YourEntityResultMap";

? ? @Override

? ? @SelectProvider(

? ? ? ? type = YourSqlProvider.class,

? ? ? ? method = "listByIds"

? ? )

? ? @ResultMap(ResultMap)

? ? List<YourEntity> listByIds(@Param(Param_Coll) Collection ids);

}

```

輸入是一個id列表集合,返回是一個Entity列表, 數(shù)據(jù)的映射復(fù)用了findById中定義的ResultMap。

- 動態(tài)SQL提供方法

```java

public class YourSqlProvider {

? ? public String listByIds(Map map) {

? ? ? ? Collection ids = getParas(map, "coll");

? ? ? ? MapperSql sql = new MapperSql();

? ? ? ? sql.SELECT("your_table", ALL_ENTITY_FIELDS);

? ? ? ? sql.WHERE_PK_IN("id", ids.size());

? ? ? ? return sql.toString();

? ? }

}

```

1. 根據(jù)Entity字段拼接了查詢字段列表

2. 根據(jù)傳入的id數(shù)量(size), 設(shè)置 id IN (#{coll[0]}, ..., #{coll[size - 1]})

- 寫測試驗(yàn)證下

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void listByIds() {

? ? ? ? List<YourEntity> entities = yourMapper.listByIds(Arrays.asList(8L, 9L));

? ? ? ? System.out.println(entities);

? ? }

}

```

- 查看控制臺輸出

```text

DEBUG - ==>? Preparing: SELECT id, gmt_create, gmt_modified, is_deleted, age, email, name FROM your_table WHERE id IN (?, ?)?

DEBUG - ==> Parameters: 8(Long), 9(Long)

DEBUG - <==? ? ? Total: 2

[YourEntity(id=8, gmtCreate=null, gmtModified=null, isDeleted=false, age=1, email=darui.wu@163.com, name=Fluent Mybatis),

YourEntity(id=9, gmtCreate=null, gmtModified=null, isDeleted=false, age=1, email=darui.wu@163.com, name=Fluent Mybatis)]

```

findOne:根據(jù)自定義條件查詢單條記錄

- Mapper方法定義

```java

public interface YourMapper extends IEntityMapper<YourEntity> {

? ? @SelectProvider(

? ? ? ? type = YourSqlProvider.class,

? ? ? ? method = "findOne"

? ? )

? ? @ResultMap(ResultMap)

? ? YourEntity findOne(@Param(Param_EW) IQuery query);

}

```

- 動態(tài)sql組裝

```java

public class YourSqlProvider {

? ? public String findOne(Map map) {

? ? ? ? WrapperData data = getWrapperData(map, "ew");

? ? ? ? MapperSql sql = new MapperSql();

? ? ? ? sql.SELECT("your_table", data, ALL_ENTITY_FIELDS);

? ? ? ? sql.WHERE_GROUP_ORDER_BY(data);

? ? ? ? return byPaged(DbType.MYSQL, data, sql.toString());

? ? }

}

```

動態(tài)SQL組裝做了以下幾件事:

1. 根據(jù)query是否顯式設(shè)置了查詢字段,設(shè)置select字段列表,如果未設(shè)置,則取默認(rèn)拼裝Entity全字段。

2. 根據(jù)query里面的where, group by, having by和order by設(shè)置查詢條件: sql.WHERE_GROUP_ORDER_BY(data)

3. 根據(jù)是否設(shè)置了分頁信息和數(shù)據(jù)庫類型,組裝分頁查詢語法: byPaged(DbType.MYSQL, data, sql.toString())

- 寫個測試驗(yàn)證下

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void findOne() {

? ? ? ? YourEntity entity = yourMapper.findOne(new YourQuery()

? ? ? ? ? ? .where.id().eq(4L).end()

? ? ? ? );

? ? }

}

```

查看控制臺的輸出:

```text

DEBUG - ==>? Preparing: SELECT id, gmt_create, gmt_modified, is_deleted, age, email, name FROM your_table WHERE id = ??

DEBUG - ==> Parameters: 4(Long)

DEBUG - <==? ? ? Total: 1

YourEntity(id=4, gmtCreate=null, gmtModified=null, isDeleted=false, age=1, email=darui.wu@163.com, name=Fluent Mybatis)

```

這種情況下,數(shù)據(jù)庫中滿足條件的數(shù)據(jù)有一條或0條;如果符合條件的數(shù)據(jù)大于一條,情況會怎樣呢,我們再寫一個測試實(shí)驗(yàn)一下。

- 如果findOne,符合條件數(shù)據(jù)大于2條

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void findOne2() {

? ? ? ? YourEntity entity = yourMapper.findOne(new YourQuery()

? ? ? ? ? ? .where.name().eq("Fluent Mybatis").end()

? ? ? ? );

? ? ? ? System.out.println(entity);

? ? }

}

```

因?yàn)閿?shù)據(jù)庫中有多條name='Fluent Mybatis'的數(shù)據(jù),調(diào)用這個方法會拋出異常

```text

DEBUG - ==>? Preparing: SELECT id, gmt_create, gmt_modified, is_deleted, age, email, name FROM your_table WHERE name = ??

DEBUG - ==> Parameters: Fluent Mybatis(String)

DEBUG - <==? ? ? Total: 14

org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions

.TooManyResultsException: Expected one result (or null) to be returned by selectOne(),

but found: 14

```

listByMap

- Mapper方法定義

```java

public interface YourMapper extends IEntityMapper<YourEntity> {

? ? String ResultMap = "YourEntityResultMap";

? ? @SelectProvider(

? ? ? ? type = YourSqlProvider.class,

? ? ? ? method = "listByMap"

? ? )

? ? @ResultMap(ResultMap)

? ? List<YourEntity> listByMap(@Param(Param_CM) Map<String, Object> columnMap);

}

```

入?yún)ap<String, Object>, 用來表示查詢數(shù)據(jù)的條件。具體條件是 key = value 的AND關(guān)系。

- 動態(tài)SQL拼接

```java

public class YourSqlProvider {

? ? public String listByMap(Map map) {

? ? ? ? Map<String, Object> where = getParas(map, "cm");

? ? ? ? MapperSql sql = new MapperSql();

? ? ? ? sql.SELECT("your_table", ALL_ENTITY_FIELDS);

? ? ? ? sql.WHERE("cm", where);

? ? ? ? return sql.toString();

? ? }

}

```

1. 查詢Entity所有字段

2. 組裝map條件, (key1 = value1) AND (key2 = value2)

- 寫個測試demo驗(yàn)證下

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void listByMap() {

? ? ? ? List<YourEntity> entities = yourMapper.listByMap(new HashMap<String, Object>() {

? ? ? ? ? ? {

? ? ? ? ? ? ? ? this.put("name", "Fluent Mybatis");

? ? ? ? ? ? ? ? this.put("is_deleted", false);

? ? ? ? ? ? }

? ? ? ? });

? ? ? ? System.out.println(entities);

? ? }

}

```

- 查看控制臺輸出

```text

DEBUG - ==>? Preparing: SELECT id, gmt_create, gmt_modified, is_deleted, age, email, name FROM your_table WHERE is_deleted = ? AND name = ??

DEBUG - ==> Parameters: false(Boolean), Fluent Mybatis(String)

DEBUG - <==? ? ? Total: 5

[YourEntity(id=4, gmtCreate=null, gmtModified=null, isDeleted=false, age=1, email=darui.wu@163.com, name=Fluent Mybatis),

YourEntity(id=5, gmtCreate=null, gmtModified=null, isDeleted=false, age=1, email=darui.wu@163.com, name=Fluent Mybatis),

YourEntity(id=6, gmtCreate=null, gmtModified=null, isDeleted=false, age=1, email=darui.wu@163.com, name=Fluent Mybatis),

YourEntity(id=7, gmtCreate=null, gmtModified=null, isDeleted=false, age=1, email=darui.wu@163.com, name=Fluent Mybatis),

YourEntity(id=8, gmtCreate=null, gmtModified=null, isDeleted=false, age=1, email=darui.wu@163.com, name=Fluent Mybatis)]

```

?listEntity:根據(jù)自定義條件查詢數(shù)據(jù),并把數(shù)據(jù)映射為對應(yīng)的Entity類

- Mapper方法定義

```java

public interface YourMapper extends IEntityMapper<YourEntity> {

? ? @SelectProvider(

? ? ? ? type = YourSqlProvider.class,

? ? ? ? method = "listEntity"

? ? )

? ? @ResultMap(ResultMap)

? ? List<YourEntity> listEntity(@Param(Param_EW) IQuery query);

}

```

- 動態(tài)SQL組裝

```java

public class YourSqlProvider {

? ? public String listEntity(Map map) {

? ? ? ? WrapperData data = getWrapperData(map, "ew");

? ? ? ? MapperSql sql = new MapperSql();

? ? ? ? sql.SELECT("your_table", data, ALL_ENTITY_FIELDS);

? ? ? ? sql.WHERE_GROUP_ORDER_BY(data);

? ? ? ? return byPaged(DbType.MYSQL, data, sql.toString());

? ? }

}

```

同findOne方法, 動態(tài)SQL組裝做了下面幾件事:

1. 根據(jù)query是否顯式設(shè)置了查詢字段,設(shè)置select字段列表,如果未設(shè)置,則取默認(rèn)拼裝Entity全字段。

2. 根據(jù)query里面的where, group by, having by和order by設(shè)置查詢條件: sql.WHERE_GROUP_ORDER_BY(data)

3. 根據(jù)是否設(shè)置了分頁信息和數(shù)據(jù)庫類型,組裝分頁查詢語法: byPaged(DbType.MYSQL, data, sql.toString())

- 寫個測試看下效果

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void listEntity() {

? ? ? ? List<YourEntity> entities = yourMapper.listEntity(new YourQuery()

? ? ? ? ? ? .select.name().age().email().end()

? ? ? ? ? ? .where.id().lt(6L)

? ? ? ? ? ? .and.name().like("Fluent").end()

? ? ? ? ? ? .orderBy.id().desc().end()

? ? ? ? );

? ? ? ? System.out.println(entities);

? ? }

}

```

- 查看控制臺log

```text

DEBUG - ==>? Preparing: SELECT name, age, email FROM your_table WHERE id < ? AND name LIKE ? ORDER BY id DESC?

DEBUG - ==> Parameters: 6(Long), %Fluent%(String)

DEBUG - <==? ? ? Total: 2

[YourEntity(id=null, gmtCreate=null, gmtModified=null, isDeleted=null, age=1, email=darui.wu@163.com, name=Fluent Mybatis),

YourEntity(id=null, gmtCreate=null, gmtModified=null, isDeleted=null, age=1, email=darui.wu@163.com, name=Fluent Mybatis)]

```

自定義查詢定義了

1. 要查詢的字段: name, age, email3個字段

2. 定義了具體條件: id < ? AND name LIKE ?

3. 定義了按id倒序排

listMaps

listMaps參數(shù)構(gòu)造和listEntity一樣,不同的時返回時不映射為Entity,而且映射成Map對象

- 寫個測試驗(yàn)證下

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void listMaps() {

? ? ? ? List<Map<String,Object>> maps = yourMapper.listMaps(new YourQuery()

? ? ? ? ? ? .select.name().age().email().end()

? ? ? ? ? ? .where.id().lt(6L)

? ? ? ? ? ? .and.name().like("Fluent").end()

? ? ? ? ? ? .orderBy.id().desc().end()

? ? ? ? );

? ? ? ? System.out.println(maps);

? ? }

}

```

- 查看控制臺輸出信息

```text

DEBUG - ==>? Preparing: SELECT name, age, email AS EMail FROM your_table WHERE id < ? AND name LIKE ? ORDER BY id DESC?

DEBUG - ==> Parameters: 6(Long), %Fluent%(String)

DEBUG - <==? ? ? Total: 2

[{name=Fluent Mybatis, EMail=darui.wu@163.com},

{name=Fluent Mybatis, EMail=darui.wu@163.com}]

```

listObjs

listObjs查詢參數(shù)構(gòu)造和listEntity、listMaps一樣,但只返回查詢對象的第一列,其余列被舍棄。

- 驗(yàn)證例子

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void listObjs() {

? ? ? ? List<String> ids = yourMapper.listObjs(new YourQuery()

? ? ? ? ? ? .select.name().age().email().end()

? ? ? ? ? ? .where.id().lt(6L)

? ? ? ? ? ? .and.name().like("Fluent").end()

? ? ? ? ? ? .orderBy.id().desc().end()

? ? ? ? );

? ? ? ? System.out.println(ids);

? ? }

}

```

- 查看控制臺輸出信息

```text

DEBUG - ==>? Preparing: SELECT name, age, email AS EMail FROM your_table WHERE id < ? AND name LIKE ? ORDER BY id DESC?

DEBUG - ==> Parameters: 6(Long), %Fluent%(String)

DEBUG - <==? ? ? Total: 2

[Fluent Mybatis, Fluent Mybatis]

```

我們看到,控制臺只打印出了查詢字段的第一列name: [Fluent Mybatis, Fluent Mybatis]

?count

count, 返回符合條件的記錄數(shù)

- Mapper定義

```java

public interface YourMapper extends IEntityMapper<YourEntity> {

? @SelectProvider(

? ? ? type = YourSqlProvider.class,

? ? ? method = "count"

? )

? Integer count(@Param(Param_EW) IQuery query);

}

```

- 驗(yàn)證示例

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void count() {

? ? ? ? int count = yourMapper.count(new YourQuery()

? ? ? ? ? ? .where.id().lt(1000L)

? ? ? ? ? ? .and.name().like("Fluent").end()

? ? ? ? ? ? .limit(0, 10)

? ? ? ? );

? ? ? ? System.out.println(count);

? ? }

}

```

- 查看控制臺輸出信息

```text

DEBUG - ==>? Preparing: SELECT COUNT(*) FROM your_table WHERE id < ? AND name LIKE ? LIMIT ?, ??

DEBUG - ==> Parameters: 1000(Long), %Fluent%(String), 0(Integer), 10(Integer)

DEBUG - <==? ? ? Total: 1

5

```

countNoLimit

使用方法同count,只是SQL語句部分舍棄了limit設(shè)置(如果你設(shè)置了)

- 驗(yàn)證示例

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void countNoLimit() {

? ? ? ? int count = yourMapper.countNoLimit(new YourQuery()

? ? ? ? ? ? .where.id().lt(1000L)

? ? ? ? ? ? .and.name().like("Fluent").end()

? ? ? ? ? ? .limit(0, 10)

? ? ? ? );

? ? ? ? System.out.println(count);

? ? }

}

```

- 查看控制臺輸出

```text

DEBUG - ==>? Preparing: SELECT COUNT(*) FROM your_table WHERE id < ? AND name LIKE ??

DEBUG - ==> Parameters: 1000(Long), %Fluent%(String)

DEBUG - <==? ? ? Total: 1

5

```

我們看到打印出的SQL語句和count方法相比,少了limit部分。

FluentMybatis提供的update更新方法

updateById

updateById 根據(jù)Entity id值,更新Entity中非空屬性

- Mapper定義

```java

public interface YourMapper extends IEntityMapper<YourEntity> {

? @UpdateProvider(

? ? ? type = YourSqlProvider.class,

? ? ? method = "updateById"

? )

? int updateById(@Param(Param_ET) YourEntity entity);

}

```

入?yún)⑹荅ntity對象, 出參是更新記錄數(shù),這里返回值只可能是0: 不存在id記錄,更新失??;1: 更新id記錄成功。

- 動態(tài)SQL組裝

```java

public class YourSqlProvider {

? ? public String updateById(Map<String, Object> map) {

? ? ? ? YourEntity entity = getParas(map, "et");

? ? ? ? MapperSql sql = new MapperSql();

? ? ? ? sql.UPDATE("your_table");

? ? ? ? List<String> sets = new ArrayList<>();

? ? ? ? if (entity.getGmtCreate() != null) {

? ? ? ? ? ? sets.add("gmt_create = #{et.gmtCreate}");

? ? ? ? }

? ? ? ? if (entity.getGmtModified() != null) {

? ? ? ? ? ? sets.add("gmt_modified = #{et.gmtModified}");

? ? ? ? } else {

? ? ? ? ? ? sets.add("gmt_modified = now()");

? ? ? ? }

? ? ? ? if (entity.getIsDeleted() != null) {

? ? ? ? ? ? sets.add("is_deleted = #{et.isDeleted}");

? ? ? ? }

? ? ? ? if (entity.getAge() != null) {

? ? ? ? ? ? sets.add("age = #{et.age}");

? ? ? ? }

? ? ? ? if (entity.getEmail() != null) {

? ? ? ? ? ? sets.add("email = #{et.email}");

? ? ? ? }

? ? ? ? if (entity.getName() != null) {

? ? ? ? ? ? sets.add("name = #{et.name}");

? ? ? ? }

? ? ? ? sql.SET(sets);

? ? ? ? sql.WHERE("id = #{et.id}");

? ? ? ? return sql.toString();

? ? }

}

```

我們看到,在設(shè)置set時,會判斷entity對象是否為null;但如果在Entity對象上設(shè)置了 @TableField( update = 'update默認(rèn)值'),

則entity屬性是空的情況下,會使用默認(rèn)值代替,比如上面gmtModified屬性

``` java

if (entity.getGmtModified() != null) {

? ? sets.add("gmt_modified = #{et.gmtModified}");

} else {

? ? sets.add("gmt_modified = now()");

}

```

where條件部分則比較簡單: id = #{et.id}

- 演示驗(yàn)證例子

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;


? ? @Test

? ? void updateById() {

? ? ? ? int count = yourMapper.updateById(new YourEntity()

? ? ? ? ? ? .setId(2L)

? ? ? ? ? ? .setName("Powerful Fluent Mybatis")

? ? ? ? );

? ? ? ? System.out.println(count);

? ? }

}

```

- 查看控制臺輸出

```text

DEBUG - ==>? Preparing: UPDATE your_table SET gmt_modified = now(), name = ? WHERE id = ??

DEBUG - ==> Parameters: Powerful Fluent Mybatis(String), 2(Long)

DEBUG - <==? ? Updates: 1

1

```

我們看到update set部分,除了設(shè)置了name=?,還設(shè)置了 gmt_modified = now()

updateBy

updateBy, 根據(jù)自定義set語句,where條件執(zhí)行更新操作

- Mapper定義

```java

public interface YourMapper extends IEntityMapper<YourEntity> {

? @UpdateProvider(

? ? ? type = YourSqlProvider.class,

? ? ? method = "updateBy"

? )

? int updateBy(@Param(Param_EW) IUpdate update);

}

```

入?yún)⑹且粋€IUpdate對象,出參是更新成功的記錄數(shù)。

- 動態(tài)SQL構(gòu)造

```java

public class YourSqlProvider {

? ? public String updateBy(Map<String, Object> map) {

? ? ? ? WrapperData data = getWrapperData(map, "ew");

? ? ? ? MapperSql sql = new MapperSql();

? ? ? ? Map<String, String> updates = data.getUpdates();

? ? ? ? assertNotEmpty("updates", updates);

? ? ? ? sql.UPDATE("your_table");

? ? ? ? List<String> sets = new ArrayList<>();

? ? ? ? if (!updates.containsKey("gmtModified")) {

? ? ? ? ? ? sets.add("gmt_modified = now()");

? ? ? ? }

? ? ? ? sets.add(data.getUpdateStr());

? ? ? ? sql.SET(sets);

? ? ? ? sql.WHERE_GROUP_ORDER_BY(data);

? ? ? ? sql.LIMIT(data, true);

? ? ? ? return sql.toString();

? ? }

}

```

動態(tài)構(gòu)造語句中對 @TableField( update = 'update默認(rèn)值')字段(這里是gmtModified)做了單獨(dú)判斷,

如果條件中不包含gmtModified,則追加默認(rèn)值更新。

- 寫個例子驗(yàn)證

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void updateBy() {

? ? ? ? int count = yourMapper.updateBy(new YourUpdate()

? ? ? ? ? ? .update.name().is("Powerful Fluent mybatis")

? ? ? ? ? ? .set.email().is("darui.wu@163.com")

? ? ? ? ? ? .set.age().is(1).end()

? ? ? ? ? ? .where.id().eq(2).end()

? ? ? ? );

? ? ? ? System.out.println(count);

? ? }

}

```

- 查看控制臺輸出

```text

DEBUG - ==>? Preparing: UPDATE your_table SET gmt_modified = now(), name = ?, email = ?, age = ? WHERE id = ??

DEBUG - ==> Parameters: Powerful Fluent mybatis(String), darui.wu@163.com(String), 1(Integer), 2(Integer)

DEBUG - <==? ? Updates: 1

1

```

注意 gmt_modified = now()更新默認(rèn)值部分

FluentMybatis提供的delete方法

deleteById:根據(jù)主鍵Id物理刪除記錄

- 查看deleteById對應(yīng)的SqlProvider語句構(gòu)造方法

```java

public class YourSqlProvider {

? ? public String deleteById(Serializable id) {

? ? ? ? MapperSql sql = new MapperSql();

? ? ? ? sql.DELETE_FROM("your_table");

? ? ? ? sql.WHERE("id = #{id}");

? ? ? ? return sql.toString();

? ? }

}

```

- deleteById的SQL構(gòu)造比較簡單,我們直接看測試演示例子

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void deleteById(){

? ? ? ? int count = yourMapper.deleteById(3L);

? ? ? ? System.out.println("count:" + count);

? ? }

}

```

- 查看控制臺輸出log:

```text

DEBUG - ==>? Preparing: DELETE FROM your_table WHERE id = ??

DEBUG - ==> Parameters: 3(Long)

DEBUG - <==? ? Updates: 1

count:1

```

deleteByIds:按id列表批量刪除, 用法同deleteById

- 直接寫個測試驗(yàn)證下

``` java

@Test

void deleteByIds() {

? ? int count = yourMapper.deleteByIds(Arrays.asList(1L, 2L, 3L));

? ? System.out.println("count:" + count);

}

```

- 控制臺輸出

```text

DEBUG - ==>? Preparing: DELETE FROM your_table WHERE id IN (?, ?, ?)?

DEBUG - ==> Parameters: 1(Long), 2(Long), 3(Long)

```

#### delete

delete, 按自定義Query條件刪除記錄

- Mapper定義

```java

public interface YourMapper extends IEntityMapper<YourEntity> {

? ? @DeleteProvider(

? ? ? ? type = YourSqlProvider.class,

? ? ? ? method = "delete"

? ? )

? ? int delete(@Param(Param_EW) IQuery wrapper);

}

```

入?yún)⑹且粋€IQuery對象,出參是刪除記錄數(shù)

- 驗(yàn)證示例

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void delete() {

? ? ? ? int count = yourMapper.delete(new YourQuery()

? ? ? ? ? ? .where.id().in(new int[]{1, 2, 3}).end()

? ? ? ? );

? ? ? ? System.out.println("count:" + count);

? ? }

}

```

- 查看控制臺輸出

```text

DEBUG - ==>? Preparing: DELETE FROM your_table WHERE id IN (?, ?, ?)?

DEBUG - ==> Parameters: 1(Integer), 2(Integer), 3(Integer)

DEBUG - <==? ? Updates: 3

count:3

```

deleteByMap: 根據(jù)map中key=value條件集更新記錄

- Mapper定義

```java

public interface YourMapper extends IEntityMapper<YourEntity> {

? ? @DeleteProvider(

? ? ? ? type = YourSqlProvider.class,

? ? ? ? method = "deleteByMap"

? ? )

? ? int deleteByMap(@Param(Param_CM) Map<String, Object> cm);

}

```

- 測試演示例子

```java

@SpringBootTest(classes = QuickStartApplication.class)

public class FluentMybatisApplicationTest {

? ? @Autowired

? ? private YourMapper yourMapper;

? ? @Test

? ? void deleteByMap() {

? ? ? ? int count = yourMapper.deleteByMap(new HashMap<String, Object>() {

? ? ? ? ? ? {

? ? ? ? ? ? ? ? this.put("name", "Fluent Mybatis");

? ? ? ? ? ? ? ? this.put("email", "darui.wu@163.com");

? ? ? ? ? ? }

? ? ? ? });

? ? ? ? System.out.println("count:" + count);

? ? }

}

```

- 查看控制臺輸出

```text

DEBUG - ==>? Preparing: DELETE FROM your_table WHERE name = ? AND email = ??

DEBUG - ==> Parameters: Fluent Mybatis(String), darui.wu@163.com(String)

DEBUG - <==? ? Updates: 2

count:2

```

總結(jié)

本篇文章介紹完FluentMuybatis提供Mapper內(nèi)置方法,我們后面接著介紹如何通過IQuery和IUpdate定義強(qiáng)大的動態(tài)SQL語句。

文章中提到示例驗(yàn)證例子可以在 [FluentMybatis gitee docs上找到](https://gitee.com/fluent-mybatis/fluent-mybatis-docs/blob/master/spring-boot-demo/src/test/java/cn/org/fluent/mybatis/springboot/demo/FluentMybatisApplicationTest.java)

[Fluent Mybatis介紹系列]

[Fluent Mybatis文檔&示例]

[Fluent Mybatis源碼, github]

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容