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.java(Tournament.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
包含Player
和Tournament
没有持久化到数据库?如果是这样,您必须为每个关系添加适当的级联参数:howtodoinjava.com/hibernate/hibernate-jpa-cascade-types/…。
@alexvaluiskyi 再次感谢 Alex。我将彻底查看文档。感谢您给予我的所有帮助
【参考方案1】:
您的实体具有注释为@Column(name = "Player_Id")
的playerId
字段,但数据库表包含PlayerId
列而不是Player_Id
。 tournamentId
、player
、tournament
字段的情况相同。
@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