用于保存复杂游戏状态的 JPA 设计
JPA design for saving a complex game state
假设我有一个带有地图和单位的游戏,我想使用 JPA 2.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; // maps from the id to the soldier
}
@Entity
class Tile {
@Id
long entityId;
@OneToOne(mappedBy = "tile")
Unit unit; // null if no unit on this tile
}
@Entity
class Unit {
@Id
long entityId;
@OneToOne
Tile tile; // null if the unit is not on a tile
@OneToMany(mappedBy = "unit", cascade = CascadeType.ALL, orphanRemoval = true)
List<Soldier> soldiers;
}
@Entity
class Soldier {
@id
long entityId;
@ManyToOne(fetch = FetchType.LAZY)
Unit unit;
int health;
}
final class Point {
final int x, y;
}
所以每个方格上都可以有一个单位,每个单位里面都有一些士兵。
我有几个问题:
因为可以有很多游戏,所以Unit
Soldier
和Tile
的entityId
不是唯一的。每场比赛可以有一个id为1的士兵。我不确定我是否 can/should 使用 @Embedded
或者我是否可以使用由实体 ID 和游戏 ID 组成的复杂 ID。
Point
的适当注释是什么?它是不可变的。
地图的适当注释是什么?我想
@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")
这是我的尝试,如果有更好的方法我想知道。以前我只是将整个状态序列化为一个 blob,但我希望能够查询更详细的状态。
What is the appropriate annotations for the maps?
最简单的方法是使用 @JoinColumn
而不是 @JoinTable
(如果您打算制作 Game
id 部分,我相信这是唯一可行的映射Unit
s 或 Soldier
s 的主键):
@OneToMany(cascadeType = ALL)
@JoinColumn(name = "game_id")
@MapKey(name = "entityId")
private Map<Long, Unit> units;
以上映射将在 UNIT
table 中创建一个 game_id
列。如果您坚持使用中间连接 tables,那么正确的映射应该是这样的:
@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;
What is the appropriate annotations for Point? It's immutable.
因为Point
没有身份,我建议你把它变成@Embeddable
how do i make the complex ID using the game id with the entity id?
首先,如果我是你,我真的会挑战自己,弄清楚可以有多少 Soldier
和 Unit
。 long
是一个非常大的数字,正如您即将看到的那样,使用复合键映射会变得非常复杂。
但是,如果您最终决定需要复合密钥,则需要调整映射,将单向 @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;
}
请注意,Unit
不是协会的拥有方。这意味着对关联的任何更改都必须通过调整 Unit.game
属性 来完成。特别是,要将新的 Unit
添加到 Game
,仅将 Unit
添加到 Game.units
地图是不够的 - 您还需要手动设置 Unit.game
属性 指向有问题的 Game
。
请注意,我尚未测试上述映射。虽然原则上它应该可以工作,但我从未尝试过将一侧的地图与另一侧的复合键结合使用,所以 - 不提供任何保证。
假设我有一个带有地图和单位的游戏,我想使用 JPA 2.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; // maps from the id to the soldier
}
@Entity
class Tile {
@Id
long entityId;
@OneToOne(mappedBy = "tile")
Unit unit; // null if no unit on this tile
}
@Entity
class Unit {
@Id
long entityId;
@OneToOne
Tile tile; // null if the unit is not on a tile
@OneToMany(mappedBy = "unit", cascade = CascadeType.ALL, orphanRemoval = true)
List<Soldier> soldiers;
}
@Entity
class Soldier {
@id
long entityId;
@ManyToOne(fetch = FetchType.LAZY)
Unit unit;
int health;
}
final class Point {
final int x, y;
}
所以每个方格上都可以有一个单位,每个单位里面都有一些士兵。
我有几个问题:
因为可以有很多游戏,所以
Unit
Soldier
和Tile
的entityId
不是唯一的。每场比赛可以有一个id为1的士兵。我不确定我是否 can/should 使用@Embedded
或者我是否可以使用由实体 ID 和游戏 ID 组成的复杂 ID。Point
的适当注释是什么?它是不可变的。地图的适当注释是什么?我想
@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")
这是我的尝试,如果有更好的方法我想知道。以前我只是将整个状态序列化为一个 blob,但我希望能够查询更详细的状态。
What is the appropriate annotations for the maps?
最简单的方法是使用 @JoinColumn
而不是 @JoinTable
(如果您打算制作 Game
id 部分,我相信这是唯一可行的映射Unit
s 或 Soldier
s 的主键):
@OneToMany(cascadeType = ALL)
@JoinColumn(name = "game_id")
@MapKey(name = "entityId")
private Map<Long, Unit> units;
以上映射将在 UNIT
table 中创建一个 game_id
列。如果您坚持使用中间连接 tables,那么正确的映射应该是这样的:
@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;
What is the appropriate annotations for Point? It's immutable.
因为Point
没有身份,我建议你把它变成@Embeddable
how do i make the complex ID using the game id with the entity id?
首先,如果我是你,我真的会挑战自己,弄清楚可以有多少 Soldier
和 Unit
。 long
是一个非常大的数字,正如您即将看到的那样,使用复合键映射会变得非常复杂。
但是,如果您最终决定需要复合密钥,则需要调整映射,将单向 @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;
}
请注意,Unit
不是协会的拥有方。这意味着对关联的任何更改都必须通过调整 Unit.game
属性 来完成。特别是,要将新的 Unit
添加到 Game
,仅将 Unit
添加到 Game.units
地图是不够的 - 您还需要手动设置 Unit.game
属性 指向有问题的 Game
。
请注意,我尚未测试上述映射。虽然原则上它应该可以工作,但我从未尝试过将一侧的地图与另一侧的复合键结合使用,所以 - 不提供任何保证。