Xbatis:SpringBoot 數據管理框架
Xbatis 是一個 SpringBoot 應用環境中使用的數據管理框架,它基於 MyBatis 實現,支援 MySQL,可以使用更加 Java 的方式實現業務邏輯中的 CRUD 操作。
安裝
下載源碼
git clone //github.com/njdi/durian.git
編譯源碼
cd durian/
切換至最新版本(Tag),如:0.4,
git checkout 0.4
編譯安裝至本地 Maven 倉庫:
mvn clean package
添加依賴
SpringBoot 項目使用 Xbatis 時,需要在 Maven pom.xml 中添加:
<dependency>
<groupId>io.njdi</groupId>
<artifactId>durian-xbatis</artifactId>
<version>${version}</version>
</dependency>
${version} 替換為具體的版本號,如:0.4。
數據表
數據表 mytable 包含 3 個欄位:id、col1 和 col2,
CREATE TABLE `mytable` (
`id` int NOT NULL AUTO_INCREMENT,
`col1` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
`col2` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
其中,欄位 id 是主鍵,且支援自增。
Xbatis 中每一張數據表都要求必須包含一個整數類型的主鍵欄位,名稱為 id,且支援自增。
數據源
Xbatis 運行時需要使用 SpringBoot 提供的數據源(DataSource),需要在 application.yml 中添加:
spring:
application:
name: sample
datasource:
url: jdbc:mysql://${host}:${port}/${db}?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true
username: ${user}
password: ${passwd}
hikari:
keepaliveTime: 30000
maxLifetime: 600000
maximumPoolSize: 30
${…} 替換為具體的資料庫資訊:
- ${host}:MySQL 實例地址;
- ${port}:MySQL 實例埠;
- ${db}:資料庫名稱;
- ${user}:用戶名;
- ${passwd}:密碼;
Xbatis
Xbatis 是基於 MyBatis 實現的,運行時相關實例需要以 Bean 的形式注入到 Spring 容器,為保證 Spring 可以正常掃描且實例化這些 Bean,需要作如下配置:
Main
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan({"io.njdi.durian.sample.xbatis", "io.njdi.durian.xbatis"})
@MapperScan(basePackages = {"io.njdi.durian.xbatis.core"})
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}
Main 是 SpringBoot 應用的入口類,@ComponentScan 用於指定「去哪裡」掃描 Xbatis 的 Bean,「io.njdi.durian.sample.xbatis」 是示例項目的 Bean,「io.njdi.durian.xbatis」 是 Xbatis 項目的 Bean;@MapperScan 用於指定「去哪裡」掃描 MyBatis 的 Mapper。
application.xml
mybatis:
mapper-locations: classpath:xbatis.xml
mybatis.mapper-locations 用於指定「去哪裡」載入 MyBatis 的配置文件。
XbatisManager
XbatisManager 是數據管理器實例,用以完成 CRUD 相關的具體操作,可以在 Service 或 Dao 層注入此實例:
@Autowired
private XbatisManager xbatisManager;
Database/Table/Column
Xbatis 中也有資料庫(Database)、數據表(Table)和數據列(Column)的概念,它們是 業務邏輯 中的庫/表/列,相較於 MySQL 中的 物理 庫/表/列,它們擁有更多的業務屬性。
Column
Column 用於定義 列,它對應著 MySQL 中的一列,包含以下屬性:
name
列名稱,指 業務邏輯 中列的名稱;如果 業務邏輯 中列的名稱與 MySQL 中對應列的名稱不一致,則需要使用列別名(alias)指定。
alias
列別名,指 MySQL 中列的名稱;僅列名稱與列別名不一致時需要指定。
type
列類型,指 業務邏輯 中列的數據類型,支援數值和字元串類型,默認為字元串類型(String)。
implicit
列默認查詢標識,查詢數據表時,如果沒有指定需要查詢具體哪些列,會使用默認查詢列替代,默認為 true。
create
列允許插入標識,指列是否支援插入,默認為 true。
select
列允許查詢標識,指列是否支援查詢,默認為 true。
update
列允許更新標識,指列是否支援更新,默認為 false。
Column 實例支援通過 Builder 模式進行創建:
Column id = Column.builder().name(COLUMN_MYTABLE_ID).type(Integer.class).create(false).build();
Column colOne = Column.builder().name("colOne").alias("col1").build();
Column colTwo = Column.builder().name("colTwo").alias("col2").build();
其中,id 可使用 type() 指定類型為整數,使用 create() 指定不允許插入;colOne 和 colTwo 可使用 alias() 分別指定別名 col1 和 col2。
Table
Table 用於定義 表,它對應著 MySQL 中的一張表,包含以下屬性:
name
表名稱,指 業務邏輯 中表的名稱;如果 業務邏輯 中表的名稱與 MySQL 中對應表的名稱不一致,則需要使用表別名(alias)指定。
alias
表別名,指 MySQL 中表的名稱;僅表名稱與表別名不一致時需要指定。
columns
列集合,指 某表的多個列(Column)。
limit
查詢某表時,最多可以返回的記錄數目,默認為整數最大值(Integer.MAX_VALUE),即不限制。
create
表允許插入標識,默認為 true。
delete
表允許刪除標識,默認為 true。
page
表允許(分頁)查詢標識,默認為 true。
update
表允許更新標識,默認為 true。
deleteMustHaveWhere
表刪除時,必須指定過濾條件標識,默認為 true。
pageMustHaveWhere
表查詢時,必須指定過濾條件標識,默認為 true。
updateMustHaveWhere
表更新時,必須指定過濾條件標識,默認為 true。
Table 實例支援通過 Builder 模式進行創建:
Table myTable = Table.builder()
.name("myTable")
.alias("mytable")
.column(id)
.column(colOne)
.column(colTwo)
.build();
使用 alias() 可指定表別名(注意 myTable 和 mytable 的區別);使用 column() 可添加多個列。
Database
Database 用於定義 庫,它對應著 MySQL 中的一個資料庫,僅包含一個屬性:
tables
表集合,指庫的多張表(Table)。
Database 實例支援通過 Builder 模式進行創建:
Database database = Database.builder()
.table(myTable)
.build();
使用 table() 可添加多張表。
Database(庫) 實例可以包含多個 Table(表) 實例;Table 實例可以包含多個 Column(列)實例。其中,Database 實例創建完成以後,需要以 Bean 的形式注入 Spring 容器:
import io.njdi.durian.xbatis.model.schema.Column;
import io.njdi.durian.xbatis.model.schema.Database;
import io.njdi.durian.xbatis.model.schema.Table;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DbConfigurer {
public static final String TABLE_MYTABLE = "myTable";
public static final String COLUMN_MYTABLE_ID = "id";
public static final String COLUMN_MYTABLE_COL_ONE = "colOne";
public static final String COLUMN_MYTABLE_COL_TWO = "colTwo";
public Table createTable() {
Column id = Column.builder().name(COLUMN_MYTABLE_ID).type(Integer.class).create(false).build();
Column colOne = Column.builder().name(COLUMN_MYTABLE_COL_ONE).alias("col1").build();
Column colTwo = Column.builder().name(COLUMN_MYTABLE_COL_TWO).alias("col2").build();
return Table.builder()
.name(TABLE_MYTABLE)
.alias("mytable")
.column(id)
.column(colOne)
.column(colTwo)
.build();
}
@Bean
public Database createDatabase() {
Table myTable = createTable();
return Database.builder()
.table(myTable)
.build();
}
}
注意 @Configuration 和 @Bean 兩個註解的使用。其中,業務邏輯 中的表名和列名 均使用 公共靜態常量(public/static/final) 進行聲明,方便業務程式碼引用。
Create/Creates
Create 用於描述 插入 操作,每一個 Create 實例對應著一條 Insert 語句,如下:
INSERT INTO mytable (col1, col2) VALUES('a', 'b')
Insert 語句包含兩部分重要內容:鍵值對和表名。鍵值對即列名和列值,如:
col1 -> a
col2 -> b
Pair
在 Xbatis 中鍵值對是使用 Pair 表示的,每一個 Pair 實例表示一組列名和列值;其中,列名使用屬性 name 表示,列值使用屬性 value 表示,如:
Pair<String> colOne = Pair.<String>builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_ONE)
.value("a")
.build();
Pair<String> colTwo = Pair.<String>builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_TWO)
.value("b")
.build();
創建一個 Create 實例表示一條 Insert 語句:
String table = DbConfigurer.TABLE_MYTABLE;
Pair<String> colOne = Pair.<String>builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_ONE)
.value("a")
.build();
Pair<String> colTwo = Pair.<String>builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_TWO)
.value("b")
.build();
Create create = Create.builder()
.pair(colOne)
.pair(colTwo)
.table(table)
.build();
創建 Create 實例時,使用了 DbConfigurer 中表名和列名的公共靜態常量。
id
id 是 Create 中一個很重要的屬性,它要求 MySQL 數據表中的主鍵欄位名稱為 id,且必須是自增的;使用 XbatisManager 執行插入操作後,可以獲取已插入記錄的主鍵值:
int id = xbatisManager.create(create);
log.info("id: {}", id);
也可以通過 Create 實例獲取主鍵值:
log.info("id: {}", create.getId());
Creates
Creates 實例內部可以添加多個 Create 實例,用於表示多條記錄的插入操作。
Creates creates = Creates.builder()
.create(create)
.create(create2)
.build();
List<Integer> ids = xbatisManager.creates(creates);
log.info("ids: {}", ids);
Delete/Deletes
Delete 用於描述 刪除 操作,每一個 Delete 實例對應著一條 Delete 語句,如下:
DELETE FROM mytable WHERE col1 = 'a'
Delete 語句包含兩部分重要內容:表名和過濾條件(可選)。
Where
Xbatis 中過濾條件使用 Where 表示,每一個 Where 實例表示一個過濾條件。Where 有四種實現:
- Filter(基本過濾器)
- OrFilter(邏輯或過濾器)
- AndFilter(邏輯與過濾器)
- NotFilter(邏輯非過濾器)
Filter
Filter 是基本過濾器的實現,它含有三個屬性:
name
列名稱。
operator
操作符,支援:EQ(=), NE(!=), GT(>), LT(<), GE(>=), LE(<=), BETWEEN(between … and …), LIKE(like), IN(in), NOT_IN(not in), IS_NULL(is null), IS_NOT_NULL(is not null)。
values
值列表,根據操作符的不同,可能是零個、一個或多個值。
創建 Filter 實例:
// col1 = 'a'
Filter eq = Filter.builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_ONE)
.operator(Filter.Operator.EQ)
.value("a")
.build();
// id between 1 and 10
Filter between = Filter.builder()
.name(DbConfigurer.COLUMN_MYTABLE_ID)
.operator(Filter.Operator.BETWEEN)
.value(1)
.value(10)
.build();
// col2 is not null
Filter isNotNull = Filter.builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_TWO)
.operator(Filter.Operator.IS_NOT_NULL)
.build();
Delete、Page 和 Update 實例均可以添加多個 Filter(Where) 實例,多個 Filter 之間的邏輯關係是 And(與)。
OrFilter
OrFilter 邏輯非過濾器,用於表示多個 Filter 之間的邏輯或(Or)關係。
假設需要表示過濾:
col1 = 'a' || col2 is not null
使用 OrFilter:
// col1 = 'a'
Filter eq = Filter.builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_ONE)
.operator(Filter.Operator.EQ)
.value("a")
.build();
// col2 is not null
Filter isNotNull = Filter.builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_TWO)
.operator(Filter.Operator.IS_NOT_NULL)
.build();
// col1 = 'a' or col2 is not null
OrFilter orFilter = OrFilter.builder()
.filter(eq)
.filter(isNotNull)
.build();
AndFilter
AndFilter 邏輯與過濾器,用於表示多個 Filter 之間的邏輯與(And)關係,使用場景較少。
NotFilter
NotFilter 邏輯非過濾器,用於表示多個 Filter 之間的邏輯非(Not)關係,使用場景較少。
使用 Where 創建 Delete 實例:
String table = DbConfigurer.TABLE_MYTABLE;
Filter colOne = Filter.builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_ONE)
.operator(Filter.Operator.EQ)
.value("a")
.build();
Delete delete = Delete.builder()
.table(table)
.where(colOne)
.build();
可以使用 Delete where() 添加多個 Where 實例,它們之間的邏輯關係是與(And)。
執行刪除操作:
int rows = xbatisManager.delete(delete);
log.info("rows: {}", rows);
XbatisManager delete() 會返回執行刪除操作後所影響的記錄行數,也可以使用 Deletes 執行多個刪除操作。
Page
Page 用於描述 查詢 操作,每一個 Page 實例對應著一條 Select 語句,它是 Xbatis 中最複雜的結構,包含若干組成部分:
- Field(查詢欄位)
- Where(過濾)
- Group(分組)
- Having(分組過濾)
- Order(排序)
- limit/offset(分頁)
Field
Field 用於描述 查詢欄位 部分:
SELECT
select_expr [, select_expr] ...
FROM
table
select_expr 就是查詢欄位,每一個 Field 實例對應著一個 查詢欄位,它包含兩個屬性:
name
列名稱。
alias
列別名,如果需要為查詢的列起一個其它的名稱,可以使用列別名指定。
創建 Field 實例:
Field field = Field.builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_ONE)
.alias("mycol")
.build();
翻譯成 SQL 片段:
SELECT
col1 AS mycol
FROM
table
Where
SELECT
select_expr [, select_expr] ...
FROM
table
WHERE
where_condition
詳細內容參考 Delete Where。
Group
列名稱集合,每一個列名稱表示一個分組欄位,直接使用字元串表示。
SELECT
select_expr [, select_expr] ...
FROM
table
WHERE
where_condition
GROUP BY
col_name [, col_name] ...
Having
SELECT
select_expr [, select_expr] ...
FROM
table
WHERE
where_condition
GROUP BY
col_name [, col_name] ...
HAVING
where_condition
詳細內容參考 Delete Where。
Order
Order 用於描述 排序欄位 部分:
SELECT
select_expr [, select_expr] ...
FROM
table
WHERE
where_condition
GROUP BY
col_name [, col_name] ...
HAVING
where_condition
ORDER BY order_expr [, order_expr] ...
order_expr 就是排序欄位,每一個 Order 實例對應著一個 排序欄位,它包含兩個屬性:
name
列名稱。
sort
排序,升序(ASC)或降序(DESC)。
創建 Order 實例:
Order order = Order.builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_ONE)
.sort(Order.Sort.DESC)
.build();
翻譯成 SQL 片段:
ORDER BY col1 DESC
limit/offset
limit 用於限制查詢返回的記錄行數,offset 用於指定記錄偏移量,兩者結合可實現分頁。
SELECT
select_expr [, select_expr] ...
FROM
table
WHERE
where_condition
GROUP BY
col_name [, col_name] ...
HAVING
where_condition
ORDER BY order_expr [, order_expr] ...
LIMIT limit
OFFSET offset
創建 Page 實例:
Page page = Page.builder()
.field(...)
.table(table)
.where(...)
.group(...)
.having(...)
.order(...)
.limit(...)
.offset(...)
.build();
其中,field()、where()、group()、having()、order() 均可以使用多次。
執行查詢操作:
List<Map<String, Object>> rows = xbatisManager.page(page);
log.info("rows: {}", rows);
XbatisManager page() 會返回一個記錄集合,使用 List 表示;每一個記錄使用一個 Map 表示,Key(String)代表列名稱,Value(Object)代表列值。
XbatisManager page() 有一個重載方法,可以直接以 對象集合 的形式返回查詢結果:
public class MyRow {
private Integer id;
private String colOne;
private String colTwo;
}
List<MyRow> rows = xbatisManager.page(page, MyRow.class);
log.info("rows: {}", rows);
每一個 MyRow 實例表示一行記錄。注意,MyRow 屬性名稱及類型必須與列名稱及類型一一對應。
id/ids
使用 ID 查詢記錄是很常見的需求,Xbatis 直接支援這樣的查詢請求。ID 可以有兩種解讀:
- MySQL 數據表記錄中的自增 id
- 業務邏輯中的唯一記錄標識
對於第一種情況:
String table = DbConfigurer.TABLE_MYTABLE;
int id = 106;
MyRow myRow = xbatisManager.id(table, id, MyRow.class);
log.info("id: {}", myRow);
直接使用 id 值查詢對應的記錄。
對於第二種情況:
String table = DbConfigurer.TABLE_MYTABLE;
String name = "id";
Integer value = 106;
MyRow myRow = xbatisManager.id(table, name, value, MyRow.class);
log.info("id: {}", myRow);
需要特別指定唯一記錄標識列的名稱 name。
此外,id() 也支援返回 Map 類型的記錄。
ids() 可以查詢 ID 集合(多個ID)的記錄列表。如果 ID 集合中存在 重複 的 ID,返回的記錄列表也會包含 重複 的記錄。
Update/Updates
Update 用於描述 更新 操作,每一個 Update 實例對應著一條 Update 語句,如下:
UPDATE
mytable
SET
col2 = 'b'
WHERE
col1 = 'a'
Update 語句包含三部分重要內容:表名,鍵值對(Pair)和過濾條件(Where);其中,Pair 請參考 Create Pair,Where 請參考 Delete Where。
創建 Update 實例:
String table = DbConfigurer.TABLE_MYTABLE;
Filter filter = Filter.builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_ONE)
.operator(Filter.Operator.EQ)
.value("a")
.build();
Pair<String> pair = Pair.<String>builder()
.name(DbConfigurer.COLUMN_MYTABLE_COL_TWO)
.value("c")
.build();
Update update = Update.builder()
.table(table)
.pair(pair)
.where(filter)
.build();
執行更新操作:
int rows = xbatisManager.update(update);
log.info("rows: {}", rows);
XbatisManager update() 會返回執行更新操作後所影響的記錄行數,也可以使用 Updates 執行多個刪除操作。
自定義列名稱
一般情況下,Field.name、Filter.name 和 Order.name 只能使用已經定義的列名稱 Column.name。如果需要使用自定義的列名稱,則需要通過 expr 屬性額外指定。
以 Field 為例,假設需要查詢列 colOne 大寫之後的值:
Field field = Field.builder()
.name("UPPER(col1)")
.alias("colOne")
.expr(true)
.build();
注意,使用自定義列名稱時,只能引用 MySQL 數據表中的列名,如:col1。