easy-mybatis
是一個對Mybatis
的增強框架(外掛)。在Spring
整合Mybatis
的基礎上,將專案開發中對資料庫的常用操作統一化。使用本框架可以很便捷的對資料庫進行操作,提高開發效率,減少機械行為。
專案主頁:https://mybatis.zuoyu.top
API文件地址:https://mybatis.zuoyu.top/doc/index.html
GitHub地址:https://github.com/zuoyuip/easy-mybatis
Gitee地址:https://gitee.com/zuoyuip/easy-mybatis
Maven依賴引入
<!-- https://mvnrepository.com/artifact/top.zuoyu.mybatis/easy-mybatis-spring-boot-starter -->
<dependency>
<groupId>top.zuoyu.mybatis</groupId>
<artifactId>easy-mybatis-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
Gradle依賴引入
// https://mvnrepository.com/artifact/top.zuoyu.mybatis/easy-mybatis-spring-boot-starter
implementation 'top.zuoyu.mybatis:easy-mybatis-spring-boot-starter:1.0.0'
框架初衷
這個框架的初衷是,減少Java程式設計師千篇一律的資料庫操作。
對於開發人員來說:
- 精力應該花費在業務邏輯上,而非機械式的“技術”上。
- 專案中減少無關痛癢的程式碼,從抽象的角度看實現。
- 各司其職,各勞其力,追求專案角度的服務流水線。
服務分離的時代
如今已很難看到單體架構的專案(感興趣的可以檢視我對架構演變的描述《淺談微服務》),目前的專案大都是通過RESTful
、MQ
、Socket
的方式(協議)進行資料傳輸。
這讓我開始質疑傳統JavaWeb
專案中的資料庫操作模式——即Model(DTO)
存在的意義。理論上,資料庫設計是不可能完全遵循檢視模型的,這就導致“正確”的做法是在專案中引入VO
,由多個DTO
來組裝。
那麼,為什麼不能用靈活的Map來替代呢?
對一個Map
的方法進行擴充,增加其對Json
的解析能力,那麼是不是就可以擺脫POJO
的各種麻煩組裝。
思考框架設計
我在思考如何設計這個框架的時候,被需要考慮的方方面面給阻擋住了。
因為一個資料庫框架需要考慮的東西實在太多了,比如:
- 事務機制
- 型別轉換
- 會話管理
···
思來想去,發現自己方向跑偏了,我只是希望統一資料庫操作的介面 + 擺脫Model,沒必要重新平地起牆,完全可以在一個現有的框架基礎上進行封裝。
那麼,對這個現有框架的選擇就尤為重要了。
現有框架的選擇
目前Java中主流的資料庫操作框架:
- Spring JDBC
- Spring Data JPA
- Mybatis
- Hibernate
選擇現有框架有一個原則——“統一資料庫操作的介面 + 擺脫Model”是對該框架的加強,而非變異;不能因為“統一資料庫操作的介面 + 擺脫Model”而無法使用原框架的部分功能。
“擺脫Model”這個特點,首先就要排除重度ORM
框架,也就是支援JPA
操作的資料庫——Spring Data JPA
、Hibernate
;原因很簡單,這兩個框架的強大之處恰恰就在它完全面向Model
操作。
剩下的就只有兩個框架了,Spring JDBC
和Mybatis
。其中,Spring JDBC
留給了開發人員大量的可操作空間,更加自由,但恰恰是這種自由使得它更加繁瑣。而Mybatis
是一個輕量ORM
框架,準確來說Mybatis
不能稱為ORM
框架,因為它並不是面向Model
運算元據庫,僅僅是將資料庫欄位與Model
欄位互相賦值,並沒有做到ORM
定義的關係對映。
抉擇
由以上各框架的特點,結合國內Java語言中資料庫操作框架的熱度,毫無疑問的選擇了Mybatis
。
考慮到SpringBoot
對Mybatis
優秀的支援級別,我決定基於mybatis-spring-boot-starter
開發這款框架,準備來說應該稱其為“外掛”。
框架特性
- 更便捷
摒棄傳統mybatis
的model
、xml
、dao
資料庫操作模式,避繁就簡,快速開發。
- 更高效
採用預編譯SQL
,拒絕執行期間反射生成SQL
,效能更高效。
- 無侵入
只是對Mybatis-Spring的增強外掛,對已有工程不做任何修改,仍可使用原生框架的功能,僅僅是簡化了開發階段對資料庫的操作。
- 統一操作介面
對資料庫的所有操作共用一個介面,降低使用門檻,輕鬆運算元據庫。
- 統一操作物件
使用JsonObject
為資料物件,提供一系列操作方法,方便從持久化物件組裝為檢視物件。
- 易上手
整個框架只提供了一個介面、一個註解、兩個物件,僅僅一行配置便可完成對資料庫進行常用操作。
- ...
安利
-
在
mybatis-spring-boot
環境下,使用該框架(外掛),可以減少傳統Mybatis
使用中對model
、xml
、dao
的機械式開發。 -
所有的資料庫操作均使用
MapperRepository
介面,通過註解@Magic("xxx")
標記介面的資料表歸屬,即可直接使用。 -
該框架(外掛)不妨礙同時使用傳統
Mybatis
中model
、xml
、dao
的資料庫開發方式。
快速上手
安裝
- 安裝
mybatis-spring-boot
環境
mybatis-spring-boot的Maven依賴
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis-spring-boot.version}</version>
</dependency>
</dependencies>
mybatis-spring-boot的Gradle依賴
plugins {
id 'org.springframework.boot' version '${springBootVersion}'
id 'io.spring.dependency-management' version '${springManagementVersion}'
id 'java'
}
dependencies {
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:${mybatisSpringVersion}'
}
- 安裝本框架(外掛)
Maven依賴引入
<!-- https://mvnrepository.com/artifact/top.zuoyu.mybatis/easy-mybatis-spring-boot-starter -->
<dependency>
<groupId>top.zuoyu.mybatis</groupId>
<artifactId>easy-mybatis-spring-boot-starter</artifactId>
<version>1.0.0</version>
</dependency>
Gradle依賴引入
// https://mvnrepository.com/artifact/top.zuoyu.mybatis/easy-mybatis-spring-boot-starter
implementation 'top.zuoyu.mybatis:easy-mybatis-spring-boot-starter:1.0.0'
配置
這裡以
MySQL
資料庫為例,Oracle
資料庫配置請參考配置說明
- 配置
spring-boot-jdbc
資料庫
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://172.0.0.1:3306/xxxx
username: xxxx
password: xxxx
關於springBoot
的配置,這裡不多贅述,更多移步springBoot官網。
- 配置
easy-mybatis
支援的表名(例子)
easy-mybatis:
table-names: teacher, student
這裡的table-names
配置,表示需要easy-mybatis
框架支援的資料表名,多個表名使用逗號隔開。
即可使用easy-mybatis
框架操作teacher
和student
兩個資料表,如果需要支援其他資料表,需要在此配置。
運算元據庫(例子)
@SpringBootTest
class DemoApplicationTests {
// 表示該介面用來操作名稱為'teacher'的資料表
@Magic("teacher")
private MapperRepository teacherRepository;
// 表示該介面用來操作名稱為'student'的資料表
@Magic("student")
private MapperRepository studentRepository;
// 查詢teacher表下所有資料
@Test
void teacherTest() {
teachertRepository.selectList().forEach(System.out::println);
}
// 查詢student表下符合特定條件的資料
@Test
void studentTest() {
studentRepository.selectListByExample(
new JsonObject().put("birthday", "2009/12/12 12:12:12")
).forEach(System.out::println);
}
}
使用MapperRepository
介面對資料庫進行操作,需要使用@Magic("表名稱")
標記該介面的資料表歸屬。
在本例中,@Magic("teacher")
表示該MapperRepository
為"teacher"
資料表的操作介面,可以通過teacherRepository
呼叫一系列方法完成對"teacher"
資料表的操作。
配置說明
該框架(外掛)的全部相關配置
#easy-mybatis的相關配置
easy-mybatis:
#表示需要載入的資料庫表
table-names: SYS_USER, SYS_DEPT
#預設的日期字串格式
data-format: yyyy-MM-dd HH:mm:ss
#當資料庫為Oracle時,資料表對應的主鍵SEQ名稱
sequences:
SYS_USER: SEQ_SYS_USER,
SYS_DEPT: SEQ_SYS_DEPT
#當資料庫為Oracle時,資料表的日期型別欄位格式
oracle-date-format: yyyy-mm-dd hh24:mi:ss
詳細說明
table-names
此配置表示需要載入的資料表,多個表名由逗號隔開,只有在此處配置了的資料表,才能在專案中使用@Magic("表名稱")
標記MapperRepository
介面的資料表歸屬。
data-format
此配置表示在JsonObject
中的日期存在格式,JsonObject
中對日期的儲存型別為String
型別,可通過此配置自定義儲存格式,預設格式為yyyy-MM-dd HH:mm:ss
。
注意
當data-format
的格式修改,請務必將oracle-date-format
的格式與之匹配。
sequences
此配置表示當資料庫為Oracle時,每個資料表的主鍵生成器名稱,以鍵值對的格式進行引數配置。若此處沒有對資料表的主鍵生成器進行配置,那麼在做該資料表的資料插入行為時需自定義填充主鍵數值。
oracle-date-format
此配置表示當資料庫為Oracle時,傳入日期格式欄位時的引數格式,即TO_DATE(field, '$dateFormat')
。
注意
當oracle-date-format
的格式修改,請務必將data-format
的格式與之匹配。
操作介面約定
本框架(外掛)的資料庫操作唯一介面——
top.zuoyu.mybatis.service.MapperRepository
。
- 以
ByExample
為結尾的方法
此類是根據JsonObject
型別的引數example
中鍵值對,對符合要求的資料進行操作,例如:
// 構建example,這裡指欄位"birthday"為"2009/12/12 12:12:12"
JsonObject example = new JsonObject().put("birthday", "2009/12/12 12:12:12");
// 查詢所有欄位"birthday"為"2009/12/12 12:12:12"的學生資料
List<JsonObject> studentList = studentRepository.selectListByExample(example);
這裡的selectListByExample
方法就是根據引數example
中鍵值對查詢符合要求的資料。
同理, countByExample
方法就是獲取符合引數example
中鍵值對的資料總量...
- 以
By
為結尾的方法
此類是根據String
型別的引數suffixSql
對方法追加引數中SQL語句的內容,例如:
// 查詢"is_delete"欄位為0的資料,並以年齡進行排序
List<JsonObject> teachers = teacherRepository.selectListBy("WHERE is_delete = 0 ORDER BY age");
// 將"birthday"欄位大於"1990/1/1 00:00:00"的資料,"salary"欄位改為20000。 (也就是出生日期在1990年之前的老師,工資改為2W)
JsonObject example = new JsonObject().put("salary", 20000);
int count = teacherRepository.updateBy(example, "WHERE birthday < '1990/1/1 00:00:00'");
這裡的selectListBy
方法就是查詢出符合引數中SQL語句要求的內容,updateBy
方法就是將符合引數中SQL語句要求的資料修改為example
定義的數值。
同理,countBy
方法就是獲取符合引數中SQL語句要求的資料總量...
insert
、insertBatch
方法
當資料庫為MySQL時,如果資料表主鍵自增,則無需定義主鍵數值;若主鍵為非自增,則需定義主鍵數值。
當資料庫為Oracle時,如果在配置中定義了資料表的主鍵生成器,則無需定義主鍵數值;反之,則需定義主鍵數值。
MapperRepository
提供了豐富的方法,以方便對資料庫的CURD操作,更多關於MapperRepository
的使用說明請移步MapperRepository說明、MapperRepository的API文件。
操作物件約定
資料庫介面的操作物件主要為
JsonObject
型別和JsonArray
型別。
- 其中
top.zuoyu.mybatis.json.JsonObject
為鍵值對容器,以鍵值的形式裝載,資料結構為:
{
'field1' : 'value1',
'field2' : 'value2',
'field3' : 'value3',
...
}
鍵的型別為java.lang.String
型別,值的型別可以為任何型別。
JsonObject
提供了豐富的方法,以方便操作鍵值對和型別轉換,更多關於JsonObject
的使用說明請移步JsonObject說明、JsonObject的API文件。
- 其中
top.zuoyu.mybatis.json.JsonArray
為陣列容器,以多個單位的形式裝載,資料結構為:
[
object1,
object2,
object3,
...
]
每個單位可以為任何型別,且各單位型別允許與其他單位型別不同。
JsonArray
提供了豐富的方法,以方便操作鍵值對和型別轉換,更多關於JsonArray
的使用說明請移步JsonArray說明、JsonArray的API文件。
具體方法可參考API文件。
MapperRepository說明
查詢所有selectList
/**
* 查詢所有
*
* @return 所有資料
*/
List<JsonObject> selectList();
查詢符合樣例的資料selectListByExample
/**
* 根據已有鍵值查詢
*
* @param example - 已有鍵值
* @return 符合要求的資料集合
*/
List<JsonObject> selectListByExample(JsonObject example);
根據主鍵查詢唯一物件selectByPrimaryKey
/**
* 根據主鍵查詢唯一物件
*
* @param primaryKey - 主鍵
* @return 唯一物件
*/
JsonObject selectByPrimaryKey(Serializable primaryKey);
查詢符合條件的資料selectListBy
/**
* 查詢符合條件的資料
*
* @param suffixSql - 條件語句(例如:where field = xxx)
* @return 資料數量
*/
List<JsonObject> selectListBy(String suffixSql);
查詢特定的欄位或結果selectFields
/**
* 查詢特定的欄位或結果
* (例如:"fieldA, fieldB, fieldC")
* (例如:"COUNT(field)")
*
* @param fields - 特定的欄位或結果
* @return 特定的欄位或結果
*/
JsonArray selectFields(String fields);
根據樣例查詢特定的欄位或結果selectFieldsByExample
/**
* 根據已有鍵值查詢特定的欄位或結果
* (例如:"fieldA, fieldB, fieldC")
* (例如:"COUNT(field)")
*
* @param fields - 特定的欄位或結果
* @param example - 已有鍵值
* @return 特定的欄位或結果
*/
JsonArray selectFieldsByExample(String fields, JsonObject example);
根據主鍵查詢特定的欄位或結果selectFieldsByPrimaryKey
/**
* 根據主鍵查詢特定的欄位或結果
*
* @param fields - 特定的欄位或結果(例如:"fieldA, fieldB, fieldC")
* @param primaryKey - 主鍵
* @return 特定的欄位或結果
*/
JsonArray selectFieldsByPrimaryKey(String fields, Serializable primaryKey);
根據主鍵集合查詢特定的欄位或結果selectFieldsByPrimaryKeys
/**
* 根據主鍵集合查詢特定的欄位或結果
*
* @param fields - 特定的欄位或結果(例如:"fieldA, fieldB, fieldC")
* @param primaryKeys - 主鍵集合
* @return 特定的欄位或結果
*/
JsonArray selectFieldsByPrimaryKeys(String fields, Serializable[] primaryKeys);
查詢符合條件的資料selectFieldsBy
/**
* 查詢符合條件的資料
*
* @param fields - 特定的欄位或結果(例如:"fieldA, fieldB, fieldC")
* @param suffixSql - 條件語句(例如:where field = xxx)
* @return 特定的欄位或結果
*/
JsonArray selectFieldsBy(String fields, String suffixSql);
查詢符合條件的資料數量countBy
/**
* 查詢符合條件的資料數量
*
* @param suffixSql - 條件語句(例如:where field = xxx)
* @return 資料數量
*/
long countBy(String suffixSql);
查詢是否存在符合樣例的資料數量countByExample
/**
* 根據已有鍵值查詢是否存在符合條件的資料數量
*
* @param example - 已有鍵值
* @return 資料數量
*/
long countByExample(JsonObject example);
是否存在符合條件的資料existsBy
/**
* 是否存在符合條件的資料
*
* @param suffixSql - 條件語句(例如:where field = xxx)
* @return 是否存在
*/
boolean existsBy(String suffixSql);
是否存在符合樣例的資料existsByExample
/**
* 根據已有鍵值查詢是否存在符合條件的資料
*
* @param example - 已有鍵值
* @return 是否存在
*/
boolean existsByExample(JsonObject example);
新增物件insert
/**
* 新增物件
*
* @param jsonObject - 物件鍵值
* @return 變動資料的數量
*/
int insert(JsonObject jsonObject);
批量新增物件insertBatch
/**
* 批量新增物件
*
* @param jsonObjects - 物件鍵值集合
* @return 變動資料的數量
*/
int insertBatch(List<JsonObject> jsonObjects);
根據主鍵修改物件屬性updateByPrimaryKey
/**
* 根據主鍵修改物件屬性
*
* @param jsonObject - 包含主鍵物件鍵值
* @return 變動資料的數量
*/
int updateByPrimaryKey(JsonObject jsonObject);
修改特定條件的物件屬性updateBy
/**
* 修改特定條件的物件屬性
*
* @param jsonObject - 要修改的鍵值
* @param suffixSql - 條件語句(例如:where field = xxx)
* @return 變動資料的數量
*/
int updateBy(JsonObject jsonObject, String suffixSql);
批量根據主鍵修改物件屬性updateByPrimaryKeyBatch
/**
* 批量根據主鍵修改物件屬性
*
* @param jsonObjects - 物件鍵值集合
* @return 變動資料的數量
*/
int updateByPrimaryKeyBatch(List<JsonObject> jsonObjects);
根據主鍵刪除物件deleteByPrimaryKey
/**
* 根據主鍵刪除物件
*
* @param primaryKey - 主鍵
* @return 變動資料的數量
*/
int deleteByPrimaryKey(Serializable primaryKey);
刪除符合條件的資料deleteBy
/**
* 刪除符合條件的資料
*
* @param suffixSql - 條件語句(例如:where field = xxx)
* @return 變動資料的數量
*/
int deleteBy(String suffixSql);
批量根據主鍵刪除物件deleteByPrimaryKeys
/**
* 批量根據主鍵刪除物件
*
* @param primaryKeys - 主鍵組
* @return 變動資料的數量
*/
int deleteByPrimaryKeys(Serializable[] primaryKeys);
根據樣例刪除物件deleteByExample
/**
* 根據已有鍵值刪除物件
*
* @param example - 已有鍵值
* @return 變動資料的數量
*/
int deleteByExample(JsonObject example);
JsonObject說明
top.zuoyu.mybatis.json.JsonObject
為鍵值對容器,以鍵值的形式裝載,資料結構為:
{
'field1' : 'value1',
'field2' : 'value2',
'field3' : 'value3',
...
}
鍵的型別為java.lang.String
型別,值的型別可以為任何型別。
型別轉換
內部預設已包含常規的型別轉換,預設支援以下型別:
java.math.BigInteger
java.math.BigDecimal
java.lang.Boolean
java.lang.Byte
java.lang.Character
java.util.Date
java.lang.Double
java.lang.Float
java.lang.Integer
java.lang.Long
java.lang.Short
java.lang.String
如果預設的轉換器無法滿足需求,或者需要轉換為其他型別,可通過自定義轉換器ConvertClass<T>
進行實現,介面方法:
public interface ConvertClass<T> {
/**
* 將給定的 value 物件轉換為 T 型別
*
* @param value - 給定物件
* @return 目標轉換型別的物件
*/
T convert(Object value);
/**
* 將給定的 value 物件轉換為 T 型別, 否則返回 defaultValue
*
* @param value - 給定物件
* @param defaultValue - 預設值
* @return 目標轉換型別的物件
*/
T convert(Object value, T defaultValue);
}
示例
@Test
void convertTest() {
// 使用JSON字串初始化物件
JsonObject student = new JsonObject(studentJSON);
// 獲取學生的性別,如果無法轉換則返回"未知"
String sex = student.getValue("sex", new SexConvert());
// 獲取學生的性別,如果無法轉換則返回"未錄入"
String sexOrDefault = student.getValue("sex", new SexConvert(), "未錄入");
}
static class SexConvert implements ConvertClass<String> {
@Override
public String convert(Object value) {
if (value instanceof Integer) {
if ((Integer) value == 1) {
return "男";
} else if ((Integer)value == 0) {
return "女";
}
}
return "未知";
}
@Override
public String convert(Object value, String defaultValue) {
if (value instanceof Integer) {
if ((Integer) value == 1) {
return "男";
} else if ((Integer)value == 0) {
return "女";
}
}
return defaultValue;
}
}
本示例也可使用StringConvert
轉換器,StringConvert
為ConvertClass<T>
的子介面。
也可以通過toClass
方法,將JsonObject
物件轉換為指定型別的實體類。
子介面:
BigDecimalConvert
BigIntegerConvert
BooleanConvert
ByteConvert
CharacterConvert
DateConvert
DoubleConvert
FloatConvert
IntegerConvert
LongConvert
ShortConvert
StringConvert
更多方法請移步JsonObject的API文件。
JsonArray說明
top.zuoyu.mybatis.json.JsonArray
為陣列容器,以多個單位的形式裝載,資料結構為:
[
object1,
object2,
object3,
...
]
每個單位可以為任何型別,且各單位型別允許與其他單位型別不同。
型別轉換
具體使用方法與JsonObject
在型別轉換操作一致,參考JsonObject的型別轉換操作
更多方法請移步JsonArray的API文件。
開源地址
專案主頁:https://mybatis.zuoyu.top
API文件地址:https://mybatis.zuoyu.top/doc/index.html