管她前浪,還是后浪?
能浪的浪,才是好浪!
每天 10:33 更新文章,每天掉億點(diǎn)點(diǎn)頭發(fā)...
源碼精品專(zhuān)欄
最近看到一個(gè) ORM 框架 Fluent Mybatis 挺有意思的,整個(gè)設計理念非常符合工程師思維。
我對官方文檔的部分內容進(jìn)行了簡(jiǎn)單整理,通過(guò)這篇文章帶你看看這個(gè)新晉 ORM 框架。
官方文檔:https://gitee.com/fluent-mybatis/fluent-mybatis/wikis
提前聲明一下:對于這類(lèi)個(gè)人維護和開(kāi)發(fā)的框架,如果沒(méi)有充分的了解,一定一定一定不要用在正式的項目上!不然后續遇到問(wèn)題會(huì )很麻煩的?。?!我目前對于 Fluent Mybatis 這個(gè)框架也僅僅是感興趣,想要學(xué)習一下它的內部設計。
何為 Fluent Mybatis? Fluent Mybatis, 是一款 Mybatis 語(yǔ)法增強框架, 綜合了 Mybatis Plus, Dynamic SQL, JPA 等框架特性和優(yōu)點(diǎn), 利用 annotation processor 生成代碼。
Fluent Mybatis 有什么亮點(diǎn)? 使用 Fluent Mybatis 可以不用寫(xiě)具體的 XML 文件,通過(guò) Java API 可以構造出比較復雜的業(yè)務(wù) SQL 語(yǔ)句,做到代碼邏輯和 SQL 邏輯的合一。不再需要在 Dao 中組裝查詢(xún)或更新操作,在 XML 或 Mapper 中再組裝參數。
項目地址:https://gitee.com/fluent-mybatis/fluent-mybatis
系列文章:https://juejin.cn/column/7033388011911921678

基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現的后臺管理系統 + 用戶(hù)小程序,支持 RBAC 動(dòng)態(tài)權限、多租戶(hù)、數據權限、工作流、三方登錄、支付、短信、商城等功能。
項目地址:https://github.com/YunaiV/ruoyi-vue-pro
基于微服務(wù)的思想,構建在 B2C 電商場(chǎng)景下的項目實(shí)戰。核心技術(shù)棧,是 Spring Boot + Dubbo 。未來(lái),會(huì )重構成 Spring Cloud Alibaba 。
項目地址:https://github.com/YunaiV/onemall
對比原生 Mybatis, Mybatis Plus 或者其他框架,FluentMybatis 提供了哪些便利呢?
我們通過(guò)一個(gè)比較典型的業(yè)務(wù)需求來(lái)具體實(shí)現和對比下,假如有學(xué)生成績(jì)表結構如下:
create table `student_score`
(
id bigint auto_increment comment '主鍵ID' primary key,
student_id bigint not null comment '學(xué)號',
gender_man tinyint default 0 not null comment '性別, 0:女; 1:男',
school_term int null comment '學(xué)期',
subject varchar(30) null comment '學(xué)科',
score int null comment '成績(jì)',
gmt_create datetime not null comment '記錄創(chuàng )建時(shí)間',
gmt_modified datetime not null comment '記錄最后修改時(shí)間',
is_deleted tinyint default 0 not null comment '邏輯刪除標識'
) engine = InnoDB default charset=utf8;
現在有需求: 統計 2000 年三門(mén)學(xué)科('英語(yǔ)', '數學(xué)', '語(yǔ)文')及格分數按學(xué)期,學(xué)科統計最低分,最高分和平均分, 且樣本數需要大于 1 條,統計結果按學(xué)期和學(xué)科排序
我們可以寫(xiě) SQL 語(yǔ)句如下:
select school_term,
subject,
count(score) as count,
min(score) as min_score,
max(score) as max_score,
avg(score) as max_score
from student_score
where school_term >= 2000
and subject in ('英語(yǔ)', '數學(xué)', '語(yǔ)文')
and score >= 60
and is_deleted = 0
group by school_term, subject
having count(score) > 1
order by school_term, subject;
那上面的需求,分別用 Fluent Mybatis, 原生 Mybatis 和 Mybatis Plus 來(lái)實(shí)現一番。
使用 Fluent Mybatis 來(lái)實(shí)現上面的功能 :

代碼地址:https://gitee.com/fluent-Mybatis/fluent-Mybatis-docs/tree/master/spring-boot-demo/
我們可以看到 fluent api 的能力,以及 IDE 對代碼的渲染效果。
換成 Mybatis 原生實(shí)現上面的功能 :
1、定義 Mapper 接口
public interface MyStudentScoreMapper {
List<Map<String, Object>> summaryScore(SummaryQuery paras);
}
2、定義接口需要用到的參數實(shí)體 SummaryQuery
@Data
@Accessors(chain = true)
public class SummaryQuery {
private Integer schoolTerm;
private List<String> subjects;
private Integer score;
private Integer minCount;
}
3、定義實(shí)現業(yè)務(wù)邏輯的 mapper xml 文件
<select id='summaryScore' resultType='map' parameterType='cn.org.fluent.Mybatis.springboot.demo.mapper.SummaryQuery'>
select school_term,
subject,
count(score) as count,
min(score) as min_score,
max(score) as max_score,
avg(score) as max_score
from student_score
where school_term >= #{schoolTerm}
and subject in
<foreach collection='subjects' item='item' open='(' close=')' separator=','>
#{item}
</foreach>
and score >= #{score}
and is_deleted = 0
group by school_term, subject
having count(score) > #{minCount}
order by school_term, subject
</select>
4、實(shí)現業(yè)務(wù)接口(這里是測試類(lèi), 實(shí)際應用中應該對應 Dao 類(lèi))
@RunWith(SpringRunner.class)
@SpringBootTest(classes = QuickStartApplication.class)
public class MybatisDemo {
@Autowired
private MyStudentScoreMapper mapper;
@Test
public void Mybatis_demo() {
// 構造查詢(xún)參數
SummaryQuery paras = new SummaryQuery()
.setSchoolTerm(2000)
.setSubjects(Arrays.asList('英語(yǔ)', '數學(xué)', '語(yǔ)文'))
.setScore(60)
.setMinCount(1);
List<Map<String, Object>> summary = mapper.summaryScore(paras);
System.out.println(summary);
}
}
總之,直接使用 Mybatis,實(shí)現步驟還是相當的繁瑣,效率太低。那換成 Mybatis Plus 的效果怎樣呢?
換成 Mybatis Plus 實(shí)現上面的功能 :
Mybatis Plus 的實(shí)現比 Mybatis 會(huì )簡(jiǎn)單比較多,實(shí)現效果如下

如紅框圈出的,寫(xiě) Mybatis Plus 實(shí)現用到了比較多字符串的硬編碼(可以用 Entity 的 get lambda 方法部分代替字符串編碼)。字符串的硬編碼,會(huì )給開(kāi)發(fā)同學(xué)造成不小的使用門(mén)檻,個(gè)人覺(jué)的主要有 2 點(diǎn):
其他框架,比如 TkMybatis 在封裝和易用性上比 Mybatis Plus 要弱,就不再比較了。
Fluent Mybatis 生成代碼設置 :
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(
/** 數據庫連接信息 **/
url = url, username = 'root', password = 'password',
/** Entity類(lèi)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',
/** 需要生成文件的表 ( 表名稱(chēng):對應的Entity名稱(chēng) ) **/
tables = @Table(value = {'student_score'})
)
static class Abc {
}
}
Mybatis Plus :
public class CodeGenerator {
static String dbUrl = 'jdbc:mysql://localhost:3306/fluent_Mybatis_demo?useSSL=false&useUnicode=true&characterEncoding=utf-8';
@Test
public void generateCode() {
GlobalConfig config = new GlobalConfig();
DataSourceConfig dataSourceConfig = new DataSourceConfig();
dataSourceConfig.setDbType(DbType.MYSQL)
.setUrl(dbUrl)
.setUsername('root')
.setPassword('password')
.setDriverName(Driver.class.getName());
StrategyConfig strategyConfig = new StrategyConfig();
strategyConfig
.setCapitalMode(true)
.setEntityLombokModel(false)
.setNaming(NamingStrategy.underline_to_camel)
.setColumnNaming(NamingStrategy.underline_to_camel)
.setEntityTableFieldAnnotationEnable(true)
.setFieldPrefix(new String[]{'test_'})
.setInclude(new String[]{'student_score'})
.setLogicDeleteFieldName('is_deleted')
.setTableFillList(Arrays.asList(
new TableFill('gmt_create', FieldFill.INSERT),
new TableFill('gmt_modified', FieldFill.INSERT_UPDATE)));
config
.setActiveRecord(false)
.setIdType(IdType.AUTO)
.setOutputDir(System.getProperty('user.dir') + '/src/main/java/')
.setFileOverride(true);
new AutoGenerator().setGlobalConfig(config)
.setDataSource(dataSourceConfig)
.setStrategy(strategyConfig)
.setPackageInfo(
new PackageConfig()
.setParent('com.mp.demo')
.setController('controller')
.setEntity('entity')
).execute();
}
}
看完 3 個(gè)框架對同一個(gè)功能點(diǎn)的實(shí)現, 各位看官肯定會(huì )有自己的判斷,筆者這里也總結了一份比較。
| - | Mybatis Plus | Fluent Mybatis |
|---|---|---|
| 代碼生成 | 生成 Entity | 生成 Entity, 再通過(guò)編譯生成 Mapper, Query, Update 和 SqlProvider |
| Generator 易用性 | 低 | 高 |
| 和 Mybatis 的共生關(guān)系 | 需替換原有的 SqlSessionFactoryBean | 對 Mybatis 沒(méi)有任何修改,原來(lái)怎么用還是怎么用 |
| 動(dòng)態(tài) SQL 構造方式 | 應用啟動(dòng)時(shí), 根據 Entity 注解信息構造動(dòng)態(tài) xml 片段,注入到 Mybatis 解析器 | 應用編譯時(shí),根據 Entity 注解,編譯生成對應方法的 SqlProvider,利用 Mybatis 的 Mapper 上 @InsertProvider 、@SelectProvider 、@UpdateProvider 注解關(guān)聯(lián) |
| 動(dòng)態(tài) SQL 結果是否容易 DEBUG 跟蹤 | 不容易 debug | 容易,直接定位到 SQLProvider 方法上,設置斷點(diǎn)即可 |
| 動(dòng)態(tài) SQL 構造 | 通過(guò)硬編碼字段名稱(chēng), 或者利用 Entity 的 get 方法的 lambda 表達式 | 通過(guò)編譯手段生成對應的方法名,直接調用方法即可 |
| 字段變更后的錯誤發(fā)現 | 通過(guò) get 方法的 lambda 表達的可以編譯發(fā)現,通過(guò)字段編碼的無(wú)法編譯發(fā)現 | 編譯時(shí)便可發(fā)現 |
| 不同字段動(dòng)態(tài) SQL 構造方法 | 通過(guò)接口參數方式 | 通過(guò)接口名稱(chēng)方式, Fluent API 的編碼效率更高 |
| 語(yǔ)法渲染特點(diǎn) | 無(wú) | 通過(guò)關(guān)鍵變量 select, update, set, and, or 可以利用 IDE 語(yǔ)法渲染, 可讀性更高 |
接下來(lái),我們來(lái)看看如何使用 Fluent Mybatis 來(lái)實(shí)現增刪改查。
新建 Maven 工程,設置項目編譯級別為 Java8 及以上,引入 Fluent Mybatis 依賴(lài)包。
<dependencies>
<!-- 引入fluent-mybatis 運行依賴(lài)包, scope為compile -->
<dependency>
<groupId>com.github.atool</groupId>
<artifactId>fluent-mybatis</artifactId>
<version>1.9.3</version>
</dependency>
<!-- 引入fluent-mybatis-processor, scope設置為provider 編譯需要,運行時(shí)不需要 -->
<dependency>
<groupId>com.github.atool</groupId>
<artifactId>fluent-mybatis-processor</artifactId>
<version>1.9.3</version>
</dependency>
</dependencies>
create schema fluent_mybatis;
create table hello_world
(
id bigint unsigned auto_increment primary key,
say_hello varchar(100) null,
your_name varchar(100) null,
gmt_created datetime DEFAULT NULL COMMENT '創(chuàng )建時(shí)間',
gmt_modified datetime DEFAULT NULL COMMENT '更新時(shí)間',
is_deleted tinyint(2) DEFAULT 0 COMMENT '是否邏輯刪除'
) ENGINE = InnoDB
CHARACTER SET = utf8 comment '簡(jiǎn)單演示表';
創(chuàng )建數據庫表對應的 Entity 類(lèi): HelloWorldEntity, 你只需要簡(jiǎn)單的做 3 個(gè)動(dòng)作:
Entity 類(lèi)和字段HelloWorldEntity 繼承 IEntity 接口類(lèi)HelloWorldEntity 類(lèi)上加注解 @FluentMybatis@FluentMybatis
public class HelloWorldEntity extends RichEntity {
private Long id;
private String sayHello;
private String yourName;
private Date gmtCreated;
private Date gmtModified;
private Boolean isDeleted;
// get, set, toString 方法
@Override
public Class<? extends IEntity> entityClass() {
return HelloWorldEntity.class;
}
}
執行編譯。
IDE 編譯:

Maven 編譯:mvn clean compile
Gradle 編譯:gradle clean compile
SqlSessionFactoryBean@ComponentScan(basePackages = 'cn.org.atool.fluent.mybatis.demo1')
@MapperScan('cn.org.atool.fluent.mybatis.demo1.entity.mapper')
@Configuration
public class HelloWorldConfig {
/**
* 設置dataSource屬性
*
* @return
*/
@Bean
public DataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName('com.mysql.jdbc.Driver');
dataSource.setUrl('jdbc:mysql://localhost:3306/fluent_mybatis?useUnicode=true&characterEncoding=utf8');
dataSource.setUsername('root');
dataSource.setPassword('password');
return dataSource;
}
/**
* 定義mybatis的SqlSessionFactoryBean
*
* @param dataSource
* @return
*/
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean;
}
@Bean
public MapperFactory mapperFactory() {
return new MapperFactory();
}
}
很簡(jiǎn)單吧,在這里,你即不需要配置任何 Mybatis xml 文件, 也不需要寫(xiě)任何 · 接口, 但你已經(jīng)擁有了強大的增刪改查的功能,并且是 Fluent API,讓我們寫(xiě)一個(gè)測試來(lái)見(jiàn)證一下 Fluent Mybatis 的魔法力量!
注入 HelloWorldEntity 對應的 Mapper 類(lèi): HelloWorldMapper, 這個(gè)類(lèi)是 Fluent Mybatis 編譯時(shí)生成的。
使用 HelloWorldMapper 進(jìn)行刪除、插入、查詢(xún)、修改操作。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = HelloWorldConfig.class)
public class HelloWorldTest {
/**
* Fluent Mybatis編譯時(shí)生成的Mapper類(lèi)
*/
@Autowired
HelloWorldMapper mapper;
@Test
public void testHelloWorld() {
/**
* 為了演示方便,先刪除數據
*/
mapper.delete(mapper.query()
.where.id().eq(1L).end());
/**
* 插入數據
*/
HelloWorldEntity entity = new HelloWorldEntity();
entity.setId(1L);
entity.setSayHello('hello world');
entity.setYourName('Fluent Mybatis');
entity.setIsDeleted(false);
mapper.insert(entity);
/**
* 查詢(xún) id = 1 的數據
*/
HelloWorldEntity result1 = mapper.findOne(mapper.query()
.where.id().eq(1L).end());
/**
* 控制臺直接打印出查詢(xún)結果
*/
System.out.println('1. HelloWorldEntity:' + result1.toString());
/**
* 更新id = 1的記錄
*/
mapper.updateBy(mapper.updater()
.set.sayHello().is('say hello, say hello!')
.set.yourName().is('Fluent Mybatis is powerful!').end()
.where.id().eq(1L).end()
);
/**
* 查詢(xún) id = 1 的數據
*/
HelloWorldEntity result2 = mapper.findOne(mapper.query()
.where.sayHello().like('hello')
.and.isDeleted().eq(false).end()
.limit(1)
);
/**
* 控制臺直接打印出查詢(xún)結果
*/
System.out.println('2. HelloWorldEntity:' + result2.toString());
}
}
輸出:
1. HelloWorldEntity:HelloWorldEntity{id=1, sayHello='hello world', yourName='Fluent Mybatis', gmtCreate=null, gmtModified=null, isDeleted=false}
2. HelloWorldEntity:HelloWorldEntity{id=1, sayHello='say hello, say hello!', yourName='Fluent Mybatis is powerful!', gmtCreate=null, gmtModified=null, isDeleted=false}
神奇吧!我們再到數據庫中查看一下結果

現在,我們已經(jīng)通過(guò)一個(gè)簡(jiǎn)單例子演示了 Fluent Mybatis 的強大功能, 在進(jìn)一步介紹 Fluent Mybatis 更強大功能前,我們揭示一下為啥我們只寫(xiě)了一個(gè)數據表對應的 Entity 類(lèi), 卻擁有了一系列增刪改查的數據庫操作方法。
Fluent Mybatis 根據 Entity 類(lèi)上 @FluentMybatis 注解在編譯時(shí), 會(huì )在 target 目錄 class 目錄下自動(dòng)編譯生成一系列文件:

這些文件的具體作用如下:
mapper/*Mapper : Mybatis 的 Mapper 定義接口, 定義了一系列通用的數據操作接口方法。dao/*BaseDao : Dao 實(shí)現基類(lèi), 所有的 DaoImpl 都繼承各自基類(lèi) 根據分層編碼的原則,我們不會(huì )在 Service 類(lèi)中直接使用 Mapper 類(lèi),而是引用 Dao 類(lèi)。我們在 Dao 實(shí)現類(lèi)中根據條件實(shí)現具體的數據操作方法。wrapper/*Query : Fluent Mybatis 核心類(lèi), 用來(lái)進(jìn)行動(dòng)態(tài) sql 的構造, 進(jìn)行條件查詢(xún)。wrapper/*Updater : Fluent Mybatis 核心類(lèi), 用來(lái)動(dòng)態(tài)構造 update 語(yǔ)句。helper/*Mapping : Entity 表字段和 Entity 屬性映射定義類(lèi)helper/*Segment: Query 和 Updater 具體功能實(shí)現, 包含幾個(gè)實(shí)現: select, where, group by, having by, order by, limitIEntityRelation : 處理 Entity 關(guān)聯(lián)(一對一, 一對多, 多對多)關(guān)系的接口Ref : 引用 Fluent Mybatis 生成的對象的快捷入口工具類(lèi)上面只是 Fluent Mybatis 常規實(shí)現增刪改查的方式,Fluent Mybatis 現在又推出了專(zhuān)門(mén)面向表單級的增刪改查,聲明即實(shí)現。官方說(shuō)明:https://juejin.cn/post/7033388050012962852 。
聯(lián)系客服