引言
Java中常用的ORM框架主要是mybatis, hibernate, JPA等框架帕棉。
國內(nèi)又以Mybatis用的多剿牺,基于mybatis上的增強框架,又有mybatis plus和TK mybatis等制跟。
今天我們介紹一個新的mybatis增強框架 fluent mybatis,
那既然JDBC --> Mybatis或Mybatis Plus無疑簡化了開發(fā)者的工作骆莹,而今天我們所講的 Fluent MyBatis又起到什么作用呢?
初識Fluent MyBatis
Fluent MyBatis是一個 MyBatis 的增強工具靶草,他只做了mybatis的語法糖封裝蹄胰,沒有對mybatis做任何修改。
通過編譯手段奕翔,提供了一系列輔助類來幫助開發(fā)簡化開發(fā)裕寨、提高效率。
入門初體驗
創(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 '邏輯刪除標識'
);
```
初始化 SpringBoot 項目
設(shè)置項目依賴
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運行時依賴
5. fluent-mybatis-processor: fluent-mybatis編譯時依賴
6. fluent-mybatis-generator: fluent-mybatis代碼生成依賴
6. 測試依賴的jar包: spring-test, junit
配置數(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
```
創(chuàng)建(生成)實體類
可以手工創(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è)置記錄插入的默認值嘁字,對應(yīng)生成Entity字段上的注解 @TableField(insert="now()")
2. gmt_modified, 記錄最后更新時間,會設(shè)置記錄插入和更新默認值杉畜,對應(yīng)生成代碼Entity字段上注解? @TableField(insert="now()", update="now()")
3. is_deleted, 記錄邏輯刪除標識纪蜒,字段類型為Boolean,且設(shè)置記錄插入的默認值寻行,對應(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測試,驗證效果
這時工程已經(jīng)具備fluent mybatis強大的增刪改查功能了举娩。我們創(chuàng)建一個測試類來驗證一下析校,在測試類中注入 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ù)庫中插入幾條記錄芙代,驗證一下效果吊奢。
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. 一個驗證測試例子
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="默認值")的3個字段:gmt_crate, gmt_modified, is_deleted做了特殊判斷。
- 編寫insert test驗證下
```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()默認值是true片挂。
2. fluent mybatis根據(jù)@TableId注解生成的Mapper類上@Options注解如下:
``` java
@Options(
? useGeneratedKeys = true,
? keyProperty = "id",
? keyColumn = "id"
)
```
3. gmt_created, gmt_modified, is_deleted 默認值插入處理
我們先看一下Entity上這3個字段的@TableField注解, 他們都定義了一個屬性方法insert幻林,設(shè)置了insert的默認值(即程序編碼insert時贞盯,如果沒有設(shè)置該字段,則使用默認值)
``` 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;
```
在測試例子中沪饺,gmt_created和gmt_modified在初始化Entity時邻悬,沒有設(shè)置任何值; is_deleted設(shè)置了值false。
在構(gòu)建sql是随闽,gmt_created, gmt_modified直接使用默認值 "now()", is_deleted使用預(yù)編譯變量(?)設(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不為空攘烛,則把默認值賦值給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)遍歷實體列表,構(gòu)造出下列SQL語句, 其中對有insert默認值屬性處理方式同單條insert一樣, 這里就不再重復(fù)坟漱。
```sql
INSERT INTO your_table ('Entity對應(yīng)的字段列表') VALUES ('實例1值'), ('實例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}
- 寫個測試實際使用下
```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列表批量查詢實例
- 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]})
- 寫測試驗證下
```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è)置,則取默認拼裝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 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ù)大于一條叭披,情況會怎樣呢,我們再寫一個測試實驗一下秀鞭。
- 如果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);
? ? }
}
```
因為數(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驗證下
```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è)置,則取默認拼裝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對象
- 寫個測試驗證下
```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一樣嚷堡,但只返回查詢對象的第一列,其余列被舍棄艇棕。
- 驗證例子
```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);
}
```
- 驗證示例
```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è)置了)
- 驗證示例
```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默認值')借杰,
則entity屬性是空的情況下,會使用默認值代替进泼,比如上面gmtModified屬性
``` java
if (entity.getGmtModified() != null) {
? ? sets.add("gmt_modified = #{et.gmtModified}");
} else {
? ? sets.add("gmt_modified = now()");
}
```
where條件部分則比較簡單: id = #{et.id}
- 演示驗證例子
```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默認值')字段(這里是gmtModified)做了單獨判斷,
如果條件中不包含gmtModified洋措,則追加默認值更新济蝉。
- 寫個例子驗證
```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()更新默認值部分
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
- 直接寫個測試驗證下
``` 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ù)
- 驗證示例
```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定義強大的動態(tài)SQL語句。
文章中提到示例驗證例子可以在 [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)