Java 用于保存复杂游戏状态的JPA设计
假设我有一个带有地图和单位的游戏,我想使用JPA2.2在服务器上提供一个保存选项。以下是简化的类来表示它:Java 用于保存复杂游戏状态的JPA设计,java,jpa,jakarta-ee,database-design,Java,Jpa,Jakarta Ee,Database Design,假设我有一个带有地图和单位的游戏,我想使用JPA2.2在服务器上提供一个保存选项。以下是简化的类来表示它: @Entity class GameState { @Id long gameId; Map<Point, Tile> map; Map<Long, Unit> units; // maps from the id to the unit Map<Long, Soldier> soldiers; // map
@Entity
class GameState {
@Id
long gameId;
Map<Point, Tile> map;
Map<Long, Unit> units; // maps from the id to the unit
Map<Long, Soldier> soldiers; // maps from the id to the soldier
}
所以每个瓷砖上都有一个单位,每个单位都有一些士兵
我有几个问题:
单位
士兵
和平铺
的实体ID
不是唯一的。每个游戏中可以有一个id为1的士兵。我不确定是否可以/应该使用@Embedded
,或者是否可以使用由实体id和游戏id组成的复杂id
点
的适当注释是什么?它是不变的
@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "unit_mapping",
joinColumns = {@JoinColumn(name = "unit_id", referencedColumnName = "entityId")},
inverseJoinColumns = {@JoinColumn(name = "unit_id", referencedColumnName = "entityId")})
@MapKey(name = "id")
@JoinColumn
而不是@JoinTable
(我相信,如果您计划将游戏
id作为单位
或士兵
的主键的一部分,这是唯一可行的映射):
点的适当注释是什么?它是不变的
由于点
没有标识,我建议您将其设置为@可嵌入的
如何使用游戏ID和实体ID生成复杂ID
首先,如果我是你的话,我真的要挑战我自己,弄清楚能有多少士兵和部队。一个long
是一个真正的huuuge数,正如您将要看到的,使用复合键映射变得相当复杂
但是,如果您最终决定需要复合密钥,则需要调整映射,使用共享密钥将单向@OneToMany
更改为双向。结果如下:
public class UnitKey implements Serializable {
private long entityId;
private long gameId;
//getters, setters, equals, hashCode
}
@Entity
@IdClass(UnitKey.class) // in principle, you could use an EmbeddedId instead, but IdClass is a little more convenient with PKs made partly of FKs IMHO
public class Unit {
@Id
private long entityId;
@JoinColumn(name = "game_id")
@MapsId("gameId")
private Game game;
}
@Entity
public class Game {
@OneToMany(mappedBy = "game", cascadeType = ALL)
@MapKey(name = "entityId")
private Map<Long, Unit> units;
}
public类UnitKey实现可序列化{
私有长实体ID;
私有长配子体;
//getter、setter、equals、hashCode
}
@实体
@IdClass(UnitKey.class)//原则上,您可以使用EmbeddedId,但是IdClass对于部分由FKs IMHO组成的PKs来说更方便一些
公营课组{
@身份证
私有长实体ID;
@JoinColumn(name=“game\u id”)
@MapsId(“配子ID”)
私人游戏;
}
@实体
公开课游戏{
@OneToMany(mappedBy=“game”,cascadeType=ALL)
@映射键(name=“entityId”)
私人地图单位;
}
请注意,Unit
不是关联的拥有方。这意味着对关联的任何更改都必须通过调整Unit.game
属性来完成。特别是,要将新的单位
添加到游戏
,仅将单位
添加到游戏.单位
地图是不够的-您还需要手动设置单位.Game
属性以指向所讨论的游戏
注意,我还没有测试上面的映射。虽然原则上它应该可以工作,但我从未尝试过将地图的一面与复合键的另一面结合起来,因此-没有给出任何保证。为什么你需要
游戏
在单元
中,而不仅仅是长游戏ID
?好吧,你可以尝试用游戏ID
来映射它,但是,我不能100%肯定JPA在您同时坚持游戏
和单元
时是否能够正确处理插入顺序。你得检查一下。请记住,GAME\u ID
列将映射两次,一次作为@JoinColumn
,另一次作为@ID
。其中一个映射必须被标记为insertable=false,updateable=false
如果您想尝试这样的映射,我会首先尝试将insertable=false,updateable=false
放在@JoinColumn
上,并手动将游戏id分配给每个Unit.gameId
。
@Entity
class Soldier {
@id
long entityId;
@ManyToOne(fetch = FetchType.LAZY)
Unit unit;
int health;
}
final class Point {
final int x, y;
}
@OneToMany(cascade = CascadeType.ALL)
@JoinTable(name = "unit_mapping",
joinColumns = {@JoinColumn(name = "unit_id", referencedColumnName = "entityId")},
inverseJoinColumns = {@JoinColumn(name = "unit_id", referencedColumnName = "entityId")})
@MapKey(name = "id")
@OneToMany(cascadeType = ALL)
@JoinColumn(name = "game_id")
@MapKey(name = "entityId")
private Map<Long, Unit> units;
@OneToMany(cascadeType = ALL)
@JoinTable(name = "unit_mapping",
joinColumns = @JoinColumn(name = "game_id"),
inverseJoinColumns = @JoinColumn(name = "unit_id")
)
@MapKey(name = "entityId")
private Map<Long, Unit> units;
public class UnitKey implements Serializable {
private long entityId;
private long gameId;
//getters, setters, equals, hashCode
}
@Entity
@IdClass(UnitKey.class) // in principle, you could use an EmbeddedId instead, but IdClass is a little more convenient with PKs made partly of FKs IMHO
public class Unit {
@Id
private long entityId;
@JoinColumn(name = "game_id")
@MapsId("gameId")
private Game game;
}
@Entity
public class Game {
@OneToMany(mappedBy = "game", cascadeType = ALL)
@MapKey(name = "entityId")
private Map<Long, Unit> units;
}