Xbatis:SpringBoot 数据管理框架

语言: CN / TW / HK

Xbatis 是一个 SpringBoot 应用环境中使用的数据管理框架,它基于 MyBatis 实现,支持 MySQL,可以使用更加 Java 的方式实现业务逻辑中的 CRUD 操作。

安装

下载源码

git clone https://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>

替换为具体的版本号,如: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

{…} 替换为具体的数据库信息:

  • {port}:MySQL 实例端口;

  • {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。

示例

  • Main

  • DbConfigurer

  • MyRow