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 相關的具體操作,可以在 ServiceDao 層注入此實例:

@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() 可指定表別名(注意 myTablemytable 的區別);使用 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.nameFilter.nameOrder.name 只能使用已經定義的列名稱 Column.name。如果需要使用自定義的列名稱,則需要通過 expr 屬性額外指定。

以 Field 為例,假設需要查詢列 colOne 大寫之後的值:

Field field = Field.builder()
        .name("UPPER(col1)")
        .alias("colOne")
        .expr(true)
        .build();

注意,使用自定義列名稱時,只能引用 MySQL 數據表中的列名,如:col1。

示例