# 元数据模式

## 附录 A:元数据模式

### 概述

Spring 批处理元数据表与在 Java 中表示它们的域对象非常匹配。例如，`JobInstance`，`JobExecution`，`JobParameters`，和`StepExecution`分别映射到`BATCH_JOB_INSTANCE`，`BATCH_JOB_EXECUTION`，`BATCH_JOB_EXECUTION_PARAMS`和`BATCH_STEP_EXECUTION`。`ExecutionContext`映射到`BATCH_JOB_EXECUTION_CONTEXT`和`BATCH_STEP_EXECUTION_CONTEXT`。`JobRepository`负责将每个 Java 对象保存并存储到其正确的表中。本附录详细描述了元数据表，以及在创建元数据表时做出的许多设计决策。在查看下面的各种表创建语句时，重要的是要认识到所使用的数据类型是尽可能通用的。 Spring Batch 提供了许多模式作为示例，所有这些模式都具有不同的数据类型，这是由于各个数据库供应商处理数据类型的方式有所不同。下图显示了所有 6 个表及其相互关系的 ERD 模型：

![Spring Batch Meta-Data ERD](https://docs.spring.io/spring-batch/docs/current/reference/html/images/meta-data-erd.png)

图 1。 Spring 批处理元数据 ERD

#### 示例 DDL 脚本

Spring 批处理核心 JAR 文件包含用于为许多数据库平台创建关系表的示例脚本（反过来，这些平台由作业存储库工厂 Bean 或等效的名称空间自动检测）。这些脚本可以按原样使用，也可以根据需要修改附加的索引和约束。文件名的形式为`schema-*.sql`，其中“\*”是目标数据库平台的简称。脚本在包`org.springframework.batch.core`中。

#### 迁移 DDL 脚本

Spring Batch 提供了在升级版本时需要执行的迁移 DDL 脚本。这些脚本可以在`org/springframework/batch/core/migration`下的核心 JAR 文件中找到。迁移脚本被组织到与版本号对应的文件夹中，这些版本号被引入：

* `2.2`:如果你从`2.2`之前的版本迁移到`2.2`版本，则包含所需的脚本

* `4.1`:如果你从`4.1`之前的版本迁移到`4.1`版本，则包含所需的脚本

#### 版本

本附录中讨论的许多数据库表都包含一个版本列。这一列很重要，因为 Spring 批处理在处理数据库更新时采用了乐观的锁定策略。这意味着每次“触摸”（更新）记录时，Version 列中的值都会增加一个。当存储库返回以保存该值时，如果版本号发生了更改，它将抛出一个`OptimisticLockingFailureException`，表示在并发访问中出现了错误。这种检查是必要的，因为即使不同的批处理作业可能在不同的机器中运行，它们都使用相同的数据库表。

#### 恒等式

`BATCH_JOB_INSTANCE`、`BATCH_JOB_EXECUTION`和`BATCH_STEP_EXECUTION`都包含以`_ID`结尾的列。这些字段充当各自表的主键。然而，它们不是数据库生成的密钥。相反，它们是由单独的序列生成的。这是必要的，因为在将一个域对象插入到数据库中之后，需要在实际对象上设置给定的键，以便在 Java 中对它们进行唯一标识。较新的数据库驱动程序（JDBC3.0 及以上版本）通过数据库生成的键支持此功能。然而，使用的是序列，而不是要求该功能。模式的每个变体都包含以下语句的某种形式：

```
CREATE SEQUENCE BATCH_STEP_EXECUTION_SEQ;
CREATE SEQUENCE BATCH_JOB_EXECUTION_SEQ;
CREATE SEQUENCE BATCH_JOB_SEQ;
```

许多数据库供应商不支持序列。在这些情况下，使用了变通方法，例如 MySQL 的以下语句：

```
CREATE TABLE BATCH_STEP_EXECUTION_SEQ (ID BIGINT NOT NULL) type=InnoDB;
INSERT INTO BATCH_STEP_EXECUTION_SEQ values(0);
CREATE TABLE BATCH_JOB_EXECUTION_SEQ (ID BIGINT NOT NULL) type=InnoDB;
INSERT INTO BATCH_JOB_EXECUTION_SEQ values(0);
CREATE TABLE BATCH_JOB_SEQ (ID BIGINT NOT NULL) type=InnoDB;
INSERT INTO BATCH_JOB_SEQ values(0);
```

在前一种情况下，用一个表来代替每个序列。 Spring 核心类`MySQLMaxValueIncrementer`然后在这个序列中增加一列，以便提供类似的功能。

### `BATCH_JOB_INSTANCE`

`BATCH_JOB_INSTANCE`表保存了与`JobInstance`相关的所有信息，并作为整个层次结构的顶部。下面的通用 DDL 语句用于创建它：

```
CREATE TABLE BATCH_JOB_INSTANCE  (
  JOB_INSTANCE_ID BIGINT  PRIMARY KEY ,
  VERSION BIGINT,
  JOB_NAME VARCHAR(100) NOT NULL ,
  JOB_KEY VARCHAR(2500)
);
```

下面的列表描述了表中的每个列：

* `JOB_INSTANCE_ID`:标识实例的唯一 ID。这也是主要的关键。可以通过调用`JobInstance`上的`getId`方法获得此列的值。

* `VERSION`:见[Version](#metaDataVersion)。

* `JOB_NAME`:从`Job`对象获得的作业的名称。因为需要它来标识实例，所以它不能是空的。

* `JOB_KEY`:`JobParameters`的序列化，该序列化唯一地标识同一作业的不同实例。（具有相同工作名称的`JobInstances`必须有不同的`JobParameters`，因此，不同的`JOB_KEY`值）。

### `BATCH_JOB_EXECUTION_PARAMS`

`BATCH_JOB_EXECUTION_PARAMS`表包含与`JobParameters`对象相关的所有信息。它包含传递给`Job`的 0 个或更多个键/值对，并用作运行作业的参数的记录。对于每个有助于生成作业标识的参数，`IDENTIFYING`标志被设置为 true。请注意，该表已被非规范化。不是为每个类型创建一个单独的表，而是有一个表，其中有一列指示类型，如下面的清单所示：

```
CREATE TABLE BATCH_JOB_EXECUTION_PARAMS  (
	JOB_EXECUTION_ID BIGINT NOT NULL ,
	TYPE_CD VARCHAR(6) NOT NULL ,
	KEY_NAME VARCHAR(100) NOT NULL ,
	STRING_VAL VARCHAR(250) ,
	DATE_VAL DATETIME DEFAULT NULL ,
	LONG_VAL BIGINT ,
	DOUBLE_VAL DOUBLE PRECISION ,
	IDENTIFYING CHAR(1) NOT NULL ,
	constraint JOB_EXEC_PARAMS_FK foreign key (JOB_EXECUTION_ID)
	references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
);
```

下面的列表描述了每一列：

* `JOB_EXECUTION_ID`:来自`BATCH_JOB_EXECUTION`表的外键，该外键指示参数项所属的作业执行。请注意，每个执行都可能存在多个行（即键/值对）。

* type\_cd:存储的值的类型的字符串表示形式，可以是字符串、日期、长值或双值。因为类型必须是已知的，所以它不能是空的。

* key\_name:参数键。

* string\_val:参数值，如果类型是 string。

* date\_val:参数值，如果类型是 date。

* long\_val:参数值，如果类型是 long。

* double\_val:参数值，如果类型是 double。

* 标识：标志，指示参数是否有助于相关的`JobInstance`的标识。

请注意，此表没有主键。这是因为该框架不需要一个框架，因此不需要它。如果需要，可以添加主键，也可以添加与数据库生成的键，而不会对框架本身造成任何问题。

### `BATCH_JOB_EXECUTION`

`BATCH_JOB_EXECUTION`表包含与`JobExecution`对象相关的所有信息。每次运行`Job`时，总会有一个新的`JobExecution`，并在此表中有一个新的行。下面的清单显示了`BATCH_JOB_EXECUTION`表的定义：

```
CREATE TABLE BATCH_JOB_EXECUTION  (
  JOB_EXECUTION_ID BIGINT  PRIMARY KEY ,
  VERSION BIGINT,
  JOB_INSTANCE_ID BIGINT NOT NULL,
  CREATE_TIME TIMESTAMP NOT NULL,
  START_TIME TIMESTAMP DEFAULT NULL,
  END_TIME TIMESTAMP DEFAULT NULL,
  STATUS VARCHAR(10),
  EXIT_CODE VARCHAR(20),
  EXIT_MESSAGE VARCHAR(2500),
  LAST_UPDATED TIMESTAMP,
  JOB_CONFIGURATION_LOCATION VARCHAR(2500) NULL,
  constraint JOB_INSTANCE_EXECUTION_FK foreign key (JOB_INSTANCE_ID)
  references BATCH_JOB_INSTANCE(JOB_INSTANCE_ID)
) ;
```

下面的列表描述了每一列：

* `JOB_EXECUTION_ID`:唯一标识此执行的主键。通过调用`JobExecution`对象的`getId`方法，可以获得此列的值。

* `VERSION`:见[Version](#metaDataVersion)。

* `JOB_INSTANCE_ID`:来自`BATCH_JOB_INSTANCE`表的外键。它指示此执行所属的实例。每个实例可能有多个执行。

* `CREATE_TIME`:表示创建执行时间的时间戳。

* `START_TIME`:表示开始执行的时间的时间戳。

* `END_TIME`:时间戳表示执行完成的时间，无论成功还是失败。当作业当前未运行时，此列中的空值表示发生了某种类型的错误，框架在失败之前无法执行最后一次保存。

* `STATUS`:表示执行状态的字符串。这可能是`COMPLETED`，`STARTED`等。此列的对象表示是`BatchStatus`枚举。

* `EXIT_CODE`:表示执行的退出代码的字符串。在命令行作业的情况下，可以将其转换为数字。

* `EXIT_MESSAGE`:表示作业如何退出的更详细描述的字符串。在失败的情况下，这可能包括尽可能多的堆栈跟踪。

* `LAST_UPDATED`:时间戳表示此执行最后一次被持久化的时间。

### `BATCH_STEP_EXECUTION`

批处理 \_step\_execution 表保存与`StepExecution`对象相关的所有信息。该表在许多方面与`BATCH_JOB_EXECUTION`表类似，并且对于每个创建的`JobExecution`，每个`Step`总是至少有一个条目。下面的清单显示了`BATCH_STEP_EXECUTION`表的定义：

```
CREATE TABLE BATCH_STEP_EXECUTION  (
  STEP_EXECUTION_ID BIGINT  PRIMARY KEY ,
  VERSION BIGINT NOT NULL,
  STEP_NAME VARCHAR(100) NOT NULL,
  JOB_EXECUTION_ID BIGINT NOT NULL,
  START_TIME TIMESTAMP NOT NULL ,
  END_TIME TIMESTAMP DEFAULT NULL,
  STATUS VARCHAR(10),
  COMMIT_COUNT BIGINT ,
  READ_COUNT BIGINT ,
  FILTER_COUNT BIGINT ,
  WRITE_COUNT BIGINT ,
  READ_SKIP_COUNT BIGINT ,
  WRITE_SKIP_COUNT BIGINT ,
  PROCESS_SKIP_COUNT BIGINT ,
  ROLLBACK_COUNT BIGINT ,
  EXIT_CODE VARCHAR(20) ,
  EXIT_MESSAGE VARCHAR(2500) ,
  LAST_UPDATED TIMESTAMP,
  constraint JOB_EXECUTION_STEP_FK foreign key (JOB_EXECUTION_ID)
  references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;
```

下面的列表描述了每个列：

* `STEP_EXECUTION_ID`:唯一标识此执行的主键。可以通过调用`StepExecution`对象的`getId`方法获得此列的值。

* `VERSION`:见[Version](#metaDataVersion)。

* `STEP_NAME`:此执行所属的步骤的名称。

* `JOB_EXECUTION_ID`:来自`BATCH_JOB_EXECUTION`表的外键。它指示了`JobExecution`这个`StepExecution`所属的`JobExecution`。对于给定的`JobExecution`名称，可能只有一个`StepExecution`。

* `START_TIME`:表示开始执行的时间的时间戳。

* `END_TIME`:时间戳表示执行完成的时间，无论成功还是失败。此列中的空值（即使作业当前未运行）表示存在某种类型的错误，并且框架在失败之前无法执行最后一次保存。

* `STATUS`:表示执行状态的字符串。这可能是`COMPLETED`，`STARTED`等。此列的对象表示是`BatchStatus`枚举。

* `COMMIT_COUNT`:在执行过程中，步骤提交事务的次数。

* `READ_COUNT`:执行过程中读取的项数。

* `FILTER_COUNT`:从该执行中筛选出的项的数量。

* `WRITE_COUNT`:在执行过程中写入和提交的项的数量。

* `READ_SKIP_COUNT`:在执行过程中读时跳过的项的数量。

* `WRITE_SKIP_COUNT`:在执行过程中在写入时跳过的项数。

* `PROCESS_SKIP_COUNT`:在此执行过程中处理过程中跳过的项的数量。

* `ROLLBACK_COUNT`:执行过程中的回滚次数。请注意，此计数包括每次发生回滚时，包括用于重试的回滚和在跳过恢复过程中的回滚。

* `EXIT_CODE`:表示执行的退出代码的字符串。在命令行作业的情况下，可以将其转换为数字。

* `EXIT_MESSAGE`:表示作业如何退出的更详细描述的字符串。在失败的情况下，这可能包括尽可能多的堆栈跟踪。

* `LAST_UPDATED`:时间戳表示此执行最后一次被持久化的时间。

### `BATCH_JOB_EXECUTION_CONTEXT`

`BATCH_JOB_EXECUTION_CONTEXT`表包含与`Job`的`ExecutionContext`相关的所有信息。这里正好有一个`Job``ExecutionContext`per`JobExecution`，它包含特定作业执行所需的所有作业级别数据。该数据通常表示故障后必须检索的状态，因此`JobInstance`可以“从它停止的地方开始”。下面的清单显示了`BATCH_JOB_EXECUTION_CONTEXT`表的定义：

```
CREATE TABLE BATCH_JOB_EXECUTION_CONTEXT  (
  JOB_EXECUTION_ID BIGINT PRIMARY KEY,
  SHORT_CONTEXT VARCHAR(2500) NOT NULL,
  SERIALIZED_CONTEXT CLOB,
  constraint JOB_EXEC_CTX_FK foreign key (JOB_EXECUTION_ID)
  references BATCH_JOB_EXECUTION(JOB_EXECUTION_ID)
) ;
```

下面的列表描述了每一列：

* `JOB_EXECUTION_ID`:表示上下文所属的`JobExecution`的外键。与给定的执行相关联的行可能不止一个。

* `SHORT_CONTEXT`:`SERIALIZED_CONTEXT`的字符串版本。

* `SERIALIZED_CONTEXT`:整个上下文，序列化。

### `BATCH_STEP_EXECUTION_CONTEXT`

`BATCH_STEP_EXECUTION_CONTEXT`表包含与`Step`的`ExecutionContext`相关的所有信息。每`StepExecution`正好有一个`ExecutionContext`，它包含了为执行特定步骤而需要持久化的所有数据。该数据通常表示故障后必须检索的状态，这样`JobInstance`就可以“从它停止的地方开始”。下面的清单显示了`BATCH_STEP_EXECUTION_CONTEXT`表的定义：

```
CREATE TABLE BATCH_STEP_EXECUTION_CONTEXT  (
  STEP_EXECUTION_ID BIGINT PRIMARY KEY,
  SHORT_CONTEXT VARCHAR(2500) NOT NULL,
  SERIALIZED_CONTEXT CLOB,
  constraint STEP_EXEC_CTX_FK foreign key (STEP_EXECUTION_ID)
  references BATCH_STEP_EXECUTION(STEP_EXECUTION_ID)
) ;
```

下面的列表描述了每一列：

* `STEP_EXECUTION_ID`:表示上下文所属的`StepExecution`的外键。可能有多个行与给定的执行相关联。

* `SHORT_CONTEXT`:`SERIALIZED_CONTEXT`的字符串版本。

* `SERIALIZED_CONTEXT`:整个上下文，序列化。

### 存档

由于每次运行批处理作业时，多个表中都有条目，因此通常需要为元数据表创建归档策略。这些表本身旨在显示过去发生的事情的记录，并且通常不会影响任何作业的运行，只有一些与重新启动有关的明显例外：

* 该框架使用元数据表来确定特定的`JobInstance`是否已经运行过。如果作业已经运行，并且不能重新启动，那么将抛出一个异常。

* 如果`JobInstance`的条目在未成功完成的情况下被删除，则框架认为该作业是新的，而不是重新启动。

* 如果重新启动了一个作业，框架将使用已持久化到`ExecutionContext`的任何数据来恢复`Job’s`状态。因此，如果作业没有成功完成，从该表中删除任何条目，将阻止它们在再次运行时从正确的点开始。

### 国际和多字节字符

如果你在业务处理中使用多字节字符集（例如中文或西里尔），那么这些字符可能需要在 Spring 批处理模式中持久化。许多用户发现，只需将模式更改为`VARCHAR`列的长度的两倍就足够了。其他人更喜欢将[JobRepository](job.html#configuringJobRepository)配置为`max-varchar-length`列长度的一半。一些用户还报告说，他们在模式定义中使用`NVARCHAR`代替`VARCHAR`。最佳结果取决于数据库平台和本地配置数据库服务器的方式。

### 建立元数据表索引的建议

Spring 批处理为几个常见的数据库平台的核心 JAR 文件中的元数据表提供了 DDL 示例。索引声明不包含在该 DDL 中，因为用户可能希望索引的方式有太多的变化，这取决于他们的精确平台、本地约定以及作业如何操作的业务需求。下面的内容提供了一些指示，说明 Spring Batch 提供的 DAO 实现将在`WHERE`子句中使用哪些列，以及它们可能被使用的频率，以便各个项目可以就索引做出自己的决定：

|  Default Table Name  |              Where Clause               |频率|
|----------------------|-----------------------------------------|-------------------------------------------------------------------|
| BATCH\_JOB\_INSTANCE |     JOB\_NAME = ? and JOB\_KEY = ?      |每次有工作要做的时候|
|BATCH\_JOB\_EXECUTION |          JOB\_INSTANCE\_ID = ?          |每次重新启动作业时|
|BATCH\_STEP\_EXECUTION|               VERSION = ?               |在提交间隔上，a.k.a.chunk（以及<br/>步骤的开始和结束）|
|BATCH\_STEP\_EXECUTION|STEP\_NAME = ? and JOB\_EXECUTION\_ID = ?|在每一步执行之前|