给出以下三个表格,描述一个可以是玩具、食物或未知的物品:
CREATE TABLE item (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
type VARCHAR(255) NOT NULL CHECK (type IN ('TOY', 'FOOD', 'UNKNOWN'))
);
CREATE TABLE item_toy (
item INTEGER NOT NULL REFERENCES item(id),
color VARCHAR(255) NOT NULL,
material VARCHAR(255) NOT NULL
);
CREATE TABLE item_food (
item INTEGER NOT NULL REFERENCES item(id),
smell VARCHAR(255) NOT NULL,
taste VARCHAR(255) NOT NULL
);
玩具物品将只有玩具属性,食品物品只有食品属性,而未知的物品两者都没有。
我们可以使用Spring Data JDBC在Java中对这些表进行建模,方法如下:
public class Item {
private int id;
private String name;
private ItemType type;
private ItemToy toyProperties;
private ItemFood foodProperties;
}
public enum ItemType {
TOY,
FOOD,
UNKNOWN
}
public class ItemToy {
private String color;
private String material;
}
public class ItemFood {
private String smell;
private String taste;
}
public class ItemRepository extends CrudRepository<Item, Integer> {
// ...
}
我在这个实现中遇到的问题是,字段toy
和food
可能是null
,也可能不是,而不管type
的值是什么:理想情况下,我希望未知物品没有任何属性,玩具物品只有toyProperties
,食品物品只有foodProperties
。
有没有一种更优雅的方式来建模这种关系,通过改变数据库结构和/或Java类,使用继承或组合?在这种情况下,存储库类看起来会是什么样子?
1条答案
按热度按时间x7rlezfr1#
这里的根本问题是,您的模型中存在冗余信息,因此存在不同表示相互矛盾的风险。
type
实际上是由不同属性的存在或不存在决定的。因此,我建议将其从数据库和Java模型中完全删除。然后将一对一关系更改为@Embedded
关系,这样您就只有一个包含所有列的表。如果你需要数据库中的类型,如果你的数据库支持的话,你可以把它添加为function based virtual column。或者将其作为普通列并使用触发器填充。
如果你需要Java中的类型,你可以创建一个getter来计算它,或者把它保存在一个
@Transient
字段中。如果这对你不起作用,不管是什么原因,你在这里留下了两个问题。数据库级的一致性和应用程序/Java端的一致性。
数据库
让我们从数据库端开始。我有三个选择:
1.您可以将模型更改为在单个表中具有所有属性,并且将引用标记为
@Embedded
而不是一对一关系,然后可以在数据库中创建检查约束,以确保仅创建有效的组合。1.如果您不想将这些表合并为一个表,则可以基于这些表创建一个物化视图,并对其施加约束。要使其按预期工作,您需要自动更新的实体化视图。我知道Oracle支持这些,也知道至少有些数据库不支持。
1.最后一种方法是使用触发器来验证约束。
由于它最容易实现,适用于所有数据库,并且可以更简单/更快地阅读和写入,因此我推荐第一种方法。
Java
对于Java端,您可以简单地依靠标准Java代码来检查约束。为了使这个失败、保存和简单,我建议使你的模型不可变。它允许您检查构造函数中的约束并完成它。
如果这导致API难以在应用程序中使用,您可以创建一个构建器,在构建示例之前收集示例的所有数据。