领先一步
VMware 提供培训和认证,助您快速进步。
了解更多这是关于如何解决使用 Spring Data JDBC 时可能遇到的各种挑战系列文章的第五篇。该系列包括:
Spring Data JDBC - 如何为我的领域模型生成 Schema?(本文)
如果您刚接触 Spring Data JDBC,建议先阅读其介绍和这篇解释聚合在 Spring Data JDBC 中的相关性的文章,以理解基本概念。
使用任何对象关系映射器 (ORM),您都必须创建两样东西,并且它们必须相互匹配:
从3.2.0-M1 版本 Spring Data Relational 开始将帮助您完成此操作。本文将向您展示如何实现它。
首先要做的是找到一个地方来放置生成 schema 的代码。我们建议为此使用一个测试。您可以从中利用主应用程序的配置,并且它不会在生产环境中意外运行。
接下来要做的是获取一个RelationalMappingContext
。这个类是 Spring Data Relational 的核心,Spring Data Relational 是 Spring Data JDBC 和 Spring Data R2DBC 的父项目。一旦完全初始化,这个类就会保存关于您的聚合的所有映射元信息。但是这种初始化是延迟发生的,所以您必须自己注册您的聚合根。
然后您需要从中创建一个LiquibaseChangeSetWriter
,并使用它来写入一个 Liquibase 变更集。
// context is a RelationalMappingContext that you autowire in your test.
context.setInitialEntitySet(Collections.singleton(Minion.class));
LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(context);
writer.writeChangeSet(new FileSystemResource("cs-minimum.yaml"));
为了使其工作,您的依赖中需要包含 Liquibase
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
</dependency>
注意:如果您使用 Spring Boot,Liquibase 依赖将触发使用 Liquibase 的 schema 初始化,这将失败,因为它找不到任何变更集。您可以通过在 application.properties
中添加此行轻松禁用它。
spring.liquibase.enabled=false
如果您运行此测试,应该能在项目根文件夹中找到一个名为 cs-minimum.yaml
的文件。
databaseChangeLog:
- changeSet:
id: '1692728224754'
author: Spring Data Relational
objectQuotingStrategy: LEGACY
changes:
- createTable:
columns:
- column:
autoIncrement: true
constraints:
nullable: true
primaryKey: true
name: id
type: BIGINT
- column:
constraints:
nullable: true
name: name
type: VARCHAR(255 BYTE)
tableName: minion
您应该审查此文件,按需修改,并将其放置在 Liquibase 可以找到的正确位置。如果您之前禁用了它,现在请启用 Liquibase 的 schema 初始化,以便实际使用此变更集。
对于您应用程序的第二个版本,您可能需要对数据库 schema 进行一些更新。Spring Data JDBC 也可以帮助您完成这些工作。
为了创建这种增量 schema 更新,我们需要提供数据库的当前状态。这可以通过一个 liquibase.database.Database
实例来实现,您可以从 DataSource
创建它。
@Autowired
DataSource ds;
// ...
context.setInitialEntitySet(Collections.singleton(Minion.class));
LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(context);
try (Database db = new HsqlDatabase()) {
db.setConnection(new JdbcConnection(ds.getConnection()));
writer.writeChangeSet(new FileSystemResource("cs-diff.yaml"), db);
} catch (IOException | SQLException | LiquibaseException e) {
throw new RuntimeException("Changeset generation failed", e);
}
上边的例子使用了 HsqlDatabase
。您应该使用与您实际数据库匹配的实现。
默认情况下,变更集永远不会从您的 schema 中删除列或表。仅仅因为它们没有在领域模型中建模,并不意味着您不需要它们,对吗?但是,如果您确实想删除 Java 领域模型中不存在的部分或全部表和列,可以注册一个 DropTableFilter
或 DropColumnFilter
,就像下面的例子一样,它会删除所有未映射的列,但名为 special
的列除外。
writer.setDropColumnFilter((table, column) -> !column.equalsIgnoreCase("special"));
Spring Data JDBC 没有用于指定列精确数据库类型的注解。但它提供了一个钩子来使用您想要的类型。您可以向 LiquibaseChangeSetWriter
提供一个SqlTypeMapping
。
writer.setSqlTypeMapping(((SqlTypeMapping) property -> {
if (property.getName().equalsIgnoreCase("name")) {
return "VARCHAR(500)";
}
return null;
}).and(new DefaultSqlTypeMapping()));
您只需要实现该接口的一个方法:String getColumnType(RelationalPersistentProperty property)
。在您只想修改某些情况下的类型的可能性较大时,您可以将其与一个DefaultSqlTypeMapping
结合使用,当您的实现返回 null
时,后者将用于所有其他情况,如示例所示。
RelationalPersistentProperty
提供了一些非常有用的方法,例如 findAnnotation
,用于访问属性或其所属实体上的注解(包括元注解)。您可以利用此功能使用自己的注解和元注解来控制您的领域模型使用的数据库类型。
例如,您可以创建一层指定数据库级别类型的注解,然后使用第一层注解创建另一层领域特定的注解集合,如下面的代码片段所示:
@Retention(RetentionPolicy.RUNTIME)
public @interface Varchar {
/**
* the size of the varchar.
*/
int value();
}
@Varchar(20)
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
}
然后您可以使用此注解来注解您的领域模型中的属性,并使用匹配的 SqlTypeMapping
。
@Name
String name;
writer.setSqlTypeMapping(((SqlTypeMapping) property -> {
if (!property.getType().equals(String.class)) {
return null;
}
// findAnnotation will find meta annotations
Varchar varchar = property.findAnnotation(Varchar.class);
int value = varchar.value();
if (varchar == null) {
return null;
}
return "VARCHAR(" +
varchar.value() +
")";
}).and(new DefaultSqlTypeMapping()));
目前 Schema 生成不支持引用。这些引用目前将被静默忽略。当然,我们将来会改进这一点。
如果您来自 JPA/Hibernate,您可能习惯于通过简单的配置直接在数据库中生成 schema,并且将 schema 信息作为映射注解的一部分。很自然会问,我们为什么选择了不同的方式。
有几个原因可以回答这个问题:
您很容易做一些只有通过应用数据库备份才能恢复的操作。我们认为让开发者在不真正看到(更不用说思考)他们应用的更改的情况下就习惯于这样做不是一件好事。这就是为什么我们创建更改,但将应用更改留作一个单独的步骤。
这就是我们选择 Liquibase 来创建和管理更改的原因。
因此,这类信息不应作为 Spring Data JDBC 使用的映射注解的一部分。相反,这类信息应该以一种真正独立于 Spring Data JDBC 的方式从您的模型中派生出来。我们认为所示范的元注解方法是实现这一目标的好方法。
凭借当前的里程碑版本和即将发布的 GA 版本,Spring Data JDBC 提供了一种灵活且强大的方式,可以从您的领域模型生成数据库迁移脚本。我们期待听到您对此的意见和经验。