Spring Boot with hibernate:创建复合键和多对多关系

Posted

技术标签:

【中文标题】Spring Boot with hibernate:创建复合键和多对多关系【英文标题】:Spring Boot with hibernate: Creating Composite Key and Many-to-many relations 【发布时间】:2021-03-04 18:21:12 【问题描述】:

我正在尝试创建一个非常简单的 Spring Boot 应用程序来存储运动数据。

它有两个实体:玩家和锦标赛。但是,我想存储每个玩家在每个锦标赛中的排名。

为此,我创建了以下 ER 图。联结表PlayerPlacement_map 包含一个关系属性placement,用于存储玩家在锦标赛中的位置:

我已按照本指南了解如何映射 Players 和 Tournament 之间的关系,并将这两个表中的 Id 作为连接表中的复合键,称为 PlayerPlacementMap:https://www.baeldung.com/jpa-many-to-many

这给了我以下课程:

Player.javaTournament.java 遵循类似的模式 - 为简洁起见)

@Entity
@Table(name = "players")
public class Player 

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "Id", updatable = false, nullable = false)
    private long id;
                 

    @Column(name = "Name")
    private String name;

    @ManyToMany
    @JoinTable(
        name = "PlayerTournament_map",
        joinColumns = @JoinColumn(name = "Player_Id"),
        inverseJoinColumns = @JoinColumn(name = "Tournament_Id"))
    Set<Tournament> attendedTournaments;

    @OneToMany(mappedBy = "player")
    Set<PlayerPlacement> placements;

    //getters, constructors - left out for brevity

PlayerPlacementKey.java(制作可嵌入的复合键类)

@Embeddable
public class PlayerPlacementKey implements Serializable 

    @Column(name = "Player_Id")
    long playerId;

    @Column(name = "Tournament_Id")
    long tournamentId;

    //getters, setters, constructors, equals, hashcode - left out for brevity

PlayerPlacement.java(联结表)

@Entity
@Table(name = "PlayerPlacement_map")
public class PlayerPlacement 

    @EmbeddedId
    PlayerPlacementKey id;

    @ManyToOne
    @MapsId("PlayerId")
    @JoinColumn(name = "Player_Id")
    Player player;

    @ManyToOne
    @MapsId("TournamentId")
    @JoinColumn(name = "Tournament_Id")
    Tournament tournament;

    int placement;

    //constructors - left out for brevity

我有存储库用于玩家、锦标赛和玩家位置。玩家和锦标赛存储库独立运行良好,我可以通过 @RestController 对 MSSQL 数据库执行 CRUD 操作。

这是玩家放置的存储库

@Repository
public interface PlayerPlacementRepository extends JpaRepository<PlayerPlacement, PlayerPlacementKey> 

对于联结表,我做了一个PlayerPlacementController

@RestController
public class PlayerPlacementController 

    @Autowired
    private PlayerPlacementRepository playerPlacementRepository;

    @PostMapping("/playerplacement")
    public PlayerPlacement addPlayerPlacement(@RequestBody PlayerPlacement playerPlacement) 
    return playerPlacementRepository.save(playerPlacement);
    

最后,问题来了:当我调用/"playerplacement" 端点时,我收到以下错误

org.hibernate.id.IdentifierGenerationException: null id generated for:class com.testproject.learning.model.PlayerPlacement

我认为我已经很好地迁移了数据库,但为了安全起见,这是我对联结表的迁移

CREATE TABLE PlayerPlacement_map (

PlayerId Bigint NOT NULL,
TournamentId Bigint NOT NULL,
Placement int

CONSTRAINT PK_PlayerPlacement NOT NULL IDENTITY(1,1) PRIMARY KEY
(
    PlayerId,
    TournamentId
)

FOREIGN KEY (PlayerId) REFERENCES Players (Id),
FOREIGN KEY (TournamentId) REFERENCES Tournaments (Id)
);

我一直无法弄清楚我做错了什么。感谢我收到的所有帮助和指点 - 在此先感谢。

编辑: 在用户 Alex V. 的帮助下取得了进展,但我现在遇到了一个新问题。

我进行以下 JSON 调用:


    "player": 
    
        "name":"John Winther"
    ,
    "tournament": 
    
        "name":"Spring Boot Cup 2020"
    ,
    "placement":3

我自己没有设置id(复合键)——我猜它应该由框架的某些东西来处理?无论如何,当我在调试模式下进行此调用时,我会在端点中设置以下 调试变量

但是,它会导致以下错误

java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "o" is null,这可能是指我PlayerPlacementKey.java中的equals-method(?):

@Override
public boolean equals(Object o) 
    if (this == o) return true;
    if (!(o instanceof PlayerPlacementKey)) return false;
    PlayerPlacementKey that = (PlayerPlacementKey) o;
    return playerId == that.playerId &&
            tournamentId == that.tournamentId;

希望有人能再一次把我推向正确的方向。

【问题讨论】:

你应该在equals方法中添加:if(null == o) return false; 嘿@alexvaluiskyi 非常感谢。我试图将它添加到equals-方法的第一行,但不幸的是它返回了相同的错误消息。不过,我真的很感谢您的帮助,非常感谢。 据我了解,您尝试持久化PlayerPlacement 包含PlayerTournament 没有持久化到数据库?如果是这样,您必须为每个关系添加适当的级联参数:howtodoinjava.com/hibernate/hibernate-jpa-cascade-types/…。 @alexvaluiskyi 再次感谢 Alex。我将彻底查看文档。感谢您给予我的所有帮助 【参考方案1】:

您的实体具有注释为@Column(name = "Player_Id")playerId 字段,但数据库表包含PlayerId 列而不是Player_IdtournamentIdplayertournament 字段的情况相同。

@MapsId("TournamentId") 必须包含 @EmbededId 类字段名称 tournamentId 而不是 TournamentId。这意味着该字段由嵌入的id字段tournamentId映射。

@ManyToOne
@MapsId("tournamentId")
@JoinColumn(name = "tournamentId")
Tournament tournament;

同样的问题@MapsId("PlayerId")

【讨论】:

您好,alex,抱歉回复晚了,我一直没空。对于您的答案的第一部分,我理解在每个带有名称的列中,例如“tournament_id”或“player_id”,我将其更改为匹配数据库,不带下划线,例如“比赛ID”?对于您问题的第二部分,您的意思是我应该将 @EmbeddedId("tournamentId") 添加到 PlayerPlacement.java 中的 Tournament 和 Player 字段中。在这种情况下,我每个都有四个注释。而且,它在执行此操作时给我一个错误。我想知道您是否有任何可以参考或类似的文档?谢谢。 另外,在 Player.java 中,我定义了一个 @OneToMany 映射命名为展示位置。我什至需要这个,因为我已经在其上方制作了@ManyToMany 地图? 我已经在第二部分编辑了答案添加示例。 嗨,Alex,非常感谢您回复我并添加更多信息。它把我推向了正确的方向!现在我可以正确调用端点并设置一些变量。但是,我面临一个新问题:我在原始帖子中添加了描述,从添加 EDIT -tag 的旧帖子底部开始。我希望你可能愿意再看看。到目前为止,我非常感谢您的帮助,并希望再次收到您的来信。

以上是关于Spring Boot with hibernate:创建复合键和多对多关系的主要内容,如果未能解决你的问题,请参考以下文章

《Pro Spring Boot 2》第四章:Web Applications with Spring Boot

grails 3(spring-boot) - 如何配置hibernate二级缓存

《Pro Spring Boot 2》第五章:Data Access with Spring Boot

Spring Boot with Docker

Spring Boot with Spring Data JPA - Concept

Microservices with Spring Boot