【发布时间】:2021-10-28 00:40:34
【问题描述】:
我正在开发一个排行榜系统作为一个爱好项目。我有三个实体:
-
Player代表排行榜上的玩家 -
StatKey代表排行榜上某个统计数据的键 -
PlayerStat代表给定玩家的特定统计数据,应该有一个由Player和StatKey组成的复合主键,以及一个将其映射到相关StatKey和父Player实体的外键。
我对整个 JPA 感到不知所措。在创建复合主键时,我仍然不确定@EmbeddedId 与@IdClass 是否是我最好的选择。根据我在网上看到的,似乎最好的方法是使用@EmbeddedId。
但是,这会导致一些非常奇怪的查询和表结构。我本来期望三个表:
- 玩家(
pk(id),username,total_level,total_experience,date_created,last_updated) - player_stat (
pk(player_id, stat_id),level,experience,date_created,last_updated) - statkey (
pk(id),key)
还有两个外键:
-
player_stat(player_id) 引用player -
player_stat(stat_id) 引用statkey
所有这些都按预期生成。但是,除了这三个表之外,还会生成第四个表:
-
player_stats(pk(player_id, stats_player_id, stats_stat_id))
以及以下外键:
-
player_stats(stats_player_id,stats_stat_id) 引用player_stat -
player_stats(player_id) 引用player
奇怪的第四张表不仅是 DDL 问题,而且在尝试查询数据库时也会用到。我不知所措。任何帮助将不胜感激!
代码:
@Entity
@Table(name = "player", uniqueConstraints = @UniqueConstraint(name = "player_uk_username", columnNames = "username"))
public class Player {
@Id
@Column(nullable = false)
private UUID id;
@Column(nullable = false)
private String username;
@Column(name = "total_level", nullable = false)
private int totalLevel;
@Column(name = "total_experience", nullable = false)
private long totalExperience;
@OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<PlayerStat> stats;
@Column(name = "date_created", nullable = false, updatable = false)
private OffsetDateTime dateCreated;
@Column(name = "last_updated", nullable = false)
private OffsetDateTime lastUpdated;
protected Player() {
}
public Player(final UUID id, final String username, final Set<PlayerStat> stats) {
this.id = id;
this.username = username;
this.stats = stats;
}
@PrePersist
public void prePersist() {
this.dateCreated = OffsetDateTime.now();
this.lastUpdated = this.dateCreated;
}
@PreUpdate
public void preUpdate() {
this.lastUpdated = OffsetDateTime.now();
}
}
@Entity
@Table(name = "statkey", uniqueConstraints = @UniqueConstraint(name = "statkey_uk_key", columnNames = "key"))
public class StatKey {
@Id
@Column(nullable = false)
private int id;
@Column(nullable = false)
private String key;
@Column(name = "date_created", nullable = false, updatable = false)
private OffsetDateTime dateCreated;
@Column(name = "last_updated", nullable = false)
private OffsetDateTime lastUpdated;
protected StatKey() {
}
public StatKey(final int id, final String key) {
this.id = id;
this.key = key;
}
@PrePersist
public void prePersist() {
this.dateCreated = OffsetDateTime.now();
this.lastUpdated = this.dateCreated;
}
@PreUpdate
public void preUpdate() {
this.lastUpdated = OffsetDateTime.now();
}
}
@Embeddable
public class PlayerStatKey implements Serializable {
@JsonUnwrapped(prefix = "player")
@ManyToOne
@JoinColumn(name = "player_id", referencedColumnName = "id", foreignKey = @ForeignKey(name = "player_stat_fk_player_id"))
private Player player;
@JsonUnwrapped(prefix = "stat")
@ManyToOne
@JoinColumn(name = "stat_id", referencedColumnName = "id", foreignKey = @ForeignKey(name = "player_stat_fk_statkey_id"))
private StatKey statKey;
protected PlayerStatKey() {
}
public PlayerStatKey(final Player player, final StatKey statKey) {
this.player = player;
this.statKey = statKey;
}
}
@Entity
@Table(name = "player_stat")
public class PlayerStat {
@EmbeddedId
@JsonUnwrapped
private PlayerStatKey key;
@Column(nullable = false)
private int level;
@Column(nullable = false)
private int experience;
@Column(name = "date_created", nullable = false, updatable = false)
private OffsetDateTime dateCreated;
@Column(name = "last_updated", nullable = false)
private OffsetDateTime lastUpdated;
protected PlayerStat() {
}
public PlayerStat(final PlayerStatKey key, final int level, final int experience) {
this.key = key;
this.level = level;
this.experience = experience;
}
@PrePersist
public void prePersist() {
this.dateCreated = OffsetDateTime.now();
this.lastUpdated = this.dateCreated;
}
@PreUpdate
public void preUpdate() {
this.lastUpdated = OffsetDateTime.now();
}
}
预期:
create table player (id binary not null, date_created timestamp not null, last_updated timestamp not null, total_experience bigint not null, total_level integer not null, username varchar(255) not null, primary key (id))
create table player_stat (date_created timestamp not null, experience integer not null, last_updated timestamp not null, level integer not null, player_id binary not null, stat_id integer not null, primary key (player_id, stat_id))
create table statkey (id integer not null, date_created timestamp not null, key varchar(255) not null, last_updated timestamp not null, primary key (id))
alter table player add constraint player_uk_username unique (username)
alter table statkey add constraint statkey_uk_key unique (key)
alter table player_stat add constraint player_stat_fk_player_id foreign key (player_id) references player
alter table player_stat add constraint player_stat_fk_statkey_id foreign key (stat_id) references statkey
现实:
create table player (id binary not null, date_created timestamp not null, last_updated timestamp not null, total_experience bigint not null, total_level integer not null, username varchar(255) not null, primary key (id))
create table player_stat (date_created timestamp not null, experience integer not null, last_updated timestamp not null, level integer not null, player_id binary not null, stat_id integer not null, primary key (player_id, stat_id))
create table player_stats (player_id binary not null, stats_player_id binary not null, stats_stat_id integer not null, primary key (player_id, stats_player_id, stats_stat_id))
create table statkey (id integer not null, date_created timestamp not null, key varchar(255) not null, last_updated timestamp not null, primary key (id))
alter table player add constraint player_uk_username unique (username)
alter table player_stats add constraint UK_9xeo0h5xwn53uq68knp5llr04 unique (stats_player_id, stats_stat_id)
alter table statkey add constraint statkey_uk_key unique (key)
alter table player_stat add constraint player_stat_fk_player_id foreign key (player_id) references player
alter table player_stat add constraint player_stat_fk_statkey_id foreign key (stat_id) references statkey
alter table player_stats add constraint FKrc4tc1jspyawwida0o2dtcp4j foreign key (stats_player_id, stats_stat_id) references player_stat
alter table player_stats add constraint FKfekdv3tvbrd0b8u6c2fuxk5fw foreign key (player_id) references player
【问题讨论】:
-
您对 PlayerStat 使用复合主键的计划。我不知道这是否是一个问题,但总的来说,我倾向于在表中使用一维主键。是的,这两个外键一起形成了一个复合键,但是想象一下需要添加第三个外键的情况。如果从那时起前两个可能会重复,那将包括对表的完全重组。此外,如果您的两个外键可能由于任何其他原因而重复,那么您再次需要完全重新配置您的表。
-
JPA 不知道您的内部项目相关逻辑,因此,从它的行为来看,它似乎在创建表(pk,fk1,fk2)时采取了安全第一的方法。为什么不尝试将 PlayerStat 定义为 PlayerStat(pk, fk1, fk2) 而不是以 (fk1, fk2) 为主键的 PlayerStat(fk1, fk2) ?如果您有一个超出内部数据库相关逻辑范围的数字自动递增主键,那么它将与 JPA 拼命强制执行的表定义相匹配。因此,也许修复就像以 JPA 生成附加表的方式生成表一样简单。