有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java为什么Hibernate希望复合主键有一个单独的表?(@EmbeddedId)

我正在做一个排行榜系统作为一个爱好项目。我有三个实体:

  • Player在排行榜上代表一名球员
  • StatKey代表排行榜上某个统计数据的键
  • PlayerStat代表给定玩家的特定属性,应该有一个由{}和{}组成的复合主键,以及一个将其映射到相关{}和父Player实体的外键

我对JPA的整体印象非常深刻。在创建复合主键时,我仍然有点不确定@EmbeddedId@IdClass是否是我的最佳选择。从我在网上读到的内容来看,似乎最好的方法是使用@EmbeddedId

然而,这会导致一些非常奇怪的查询和表结构。我希望有三张桌子:

  • 玩家(pk(id)usernametotal_leveltotal_experiencedate_createdlast_updated
  • 玩家统计(pk(player_id, stat_id)levelexperiencedate_createdlast_updated
  • statkey(pk(id)key

以及两个外键:

  • player_statplayer_id)引用player
  • player_statstat_id)引用statkey

所有这些都按预期生成。但是,除了这三个表之外,还生成了第四个表:

  • player_statspk(player_id, stats_player_id, stats_stat_id)

以及以下外键:

  • player_statsstats_player_idstats_stat_id)引用player_stat
  • player_statsplayer_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

共 (2) 个答案

  1. # 1 楼答案

    问题似乎是缺少从父实体到子实体的映射

    解决方案是在StatKey中添加以下内容:

    @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "primaryKey.statKey")
    private Set<PlayerStat> stats;
    

    以及在Player中的@OneToMany注释中添加mappedBy = "primaryKey.player"

  2. # 2 楼答案

    除了oa54所说的:

    1. 这与Spring数据JPA无关。如果SQL是自动生成的,我假设您(无意中)使用了JPA提供程序的SQL生成功能,可能是Hibernate。我将相应地更新问题标题

    2. 我强烈建议您使用专用的SQL迁移工具(Flyway或Liquibase)来编写定义数据结构的SQL,因为在某个时候您必须使用类似的工具。一旦你的类需要新的字段,你就需要迁移数据,改变表,而不是重新创建它们等等