【发布时间】:2012-03-21 11:47:54
【问题描述】:
我想知道如何使用 EclipseLink 在 JPA 2 中实现 temporal tables。我所说的时间是指定义有效期的表格。
我面临的一个问题是引用表不能再对被引用表(临时表)有外键约束,因为被引用表的性质现在它们的主键包括有效期。
- 如何映射我的实体之间的关系?
- 这是否意味着我的实体不能再与那些有效时间实体建立关系?
- 现在应该由我在某种服务或专门的 DAO 中手动执行初始化这些关系的职责吗?
我发现的唯一东西是一个名为 DAO Fusion 的框架,它可以处理这个问题。
- 还有其他方法可以解决这个问题吗?
- 您能否提供有关此主题的示例或资源(带有时态数据库的 JPA)?
这是一个数据模型及其类的虚构示例。它从一个不需要处理时间方面的简单模型开始:
第一个场景:非时间模型
数据模型:
团队:
@Entity
public class Team implements Serializable {
private Long id;
private String name;
private Integer wins = 0;
private Integer losses = 0;
private Integer draws = 0;
private List<Player> players = new ArrayList<Player>();
public Team() {
}
public Team(String name) {
this.name = name;
}
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQTEAMID")
@SequenceGenerator(name="SEQTEAMID", sequenceName="SEQTEAMID", allocationSize=1)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Column(unique=true, nullable=false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getWins() {
return wins;
}
public void setWins(Integer wins) {
this.wins = wins;
}
public Integer getLosses() {
return losses;
}
public void setLosses(Integer losses) {
this.losses = losses;
}
public Integer getDraws() {
return draws;
}
public void setDraws(Integer draws) {
this.draws = draws;
}
@OneToMany(mappedBy="team", cascade=CascadeType.ALL)
public List<Player> getPlayers() {
return players;
}
public void setPlayers(List<Player> players) {
this.players = players;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Team other = (Team) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}
播放器:
@Entity
@Table(uniqueConstraints={@UniqueConstraint(columnNames={"team_id","number"})})
public class Player implements Serializable {
private Long id;
private Team team;
private Integer number;
private String name;
public Player() {
}
public Player(Team team, Integer number) {
this.team = team;
this.number = number;
}
@Id
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SEQPLAYERID")
@SequenceGenerator(name="SEQPLAYERID", sequenceName="SEQPLAYERID", allocationSize=1)
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@ManyToOne
@JoinColumn(nullable=false)
public Team getTeam() {
return team;
}
public void setTeam(Team team) {
this.team = team;
}
@Column(nullable=false)
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
@Column(unique=true, nullable=false)
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((number == null) ? 0 : number.hashCode());
result = prime * result + ((team == null) ? 0 : team.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Player other = (Player) obj;
if (number == null) {
if (other.number != null)
return false;
} else if (!number.equals(other.number))
return false;
if (team == null) {
if (other.team != null)
return false;
} else if (!team.equals(other.team))
return false;
return true;
}
}
测试类:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/META-INF/application-context-root.xml"})
@Transactional
public class TestingDao {
@PersistenceContext
private EntityManager entityManager;
private Team team;
@Before
public void setUp() {
team = new Team();
team.setName("The Goods");
team.setLosses(0);
team.setWins(0);
team.setDraws(0);
Player player = new Player();
player.setTeam(team);
player.setNumber(1);
player.setName("Alfredo");
team.getPlayers().add(player);
player = new Player();
player.setTeam(team);
player.setNumber(2);
player.setName("Jorge");
team.getPlayers().add(player);
entityManager.persist(team);
entityManager.flush();
}
@Test
public void testPersistence() {
String strQuery = "select t from Team t where t.name = :name";
TypedQuery<Team> query = entityManager.createQuery(strQuery, Team.class);
query.setParameter("name", team.getName());
Team persistedTeam = query.getSingleResult();
assertEquals(2, persistedTeam.getPlayers().size());
//Change the player number
Player p = null;
for (Player player : persistedTeam.getPlayers()) {
if (player.getName().equals("Alfredo")) {
p = player;
break;
}
}
p.setNumber(10);
}
}
现在,您需要保留团队和玩家在某个时间点的历史记录,因此您需要为每张要跟踪的牌桌添加一段时间。所以让我们添加这些时间列。我们将从 Player 开始。
第二个场景:时间模型
数据模型:
如您所见,我们必须删除主键并定义另一个包含日期(句点)的主键。我们还必须删除唯一约束,因为现在它们可以在表中重复。现在该表可以包含当前条目以及历史记录。
如果我们还必须让 Team 临时化,事情会变得非常糟糕,在这种情况下,我们需要将 Player 表必须的外键约束删除到 Team。问题是如何在 Java 和 JPA 中对其进行建模。
请注意,ID 是代理键。但现在代理键必须包含日期,因为如果不包含日期,则不允许存储同一实体的多个“version”(在时间轴期间)。
【问题讨论】:
-
1) 您是用什么工具绘制图表的? 2) 一个时间维度足以满足您的要求,DAOFusion 模式和我的答案(基于这些模式)在我看来都过大了 3) 您是否更喜欢仅将时间方面添加到 Player 的解决方案,或者您更喜欢它两个表 4)你的最后一段是错误的。代理键永远不会包含其他字段。在这种情况下,您将有两个代理键。
-
@ChrLipp 1) Sparx Enterprise Architect 2) 我同意。 3)我需要一个解决方案,为两个表添加时间。 4)我不同意这不是代理键。我认为它是一个代理键,因为: 1. 在添加临时列之前,它是一个代理键,它是一个没有业务意义的键。例如 Player 的业务键是“team_id”和“number”,而 Team 的业务键是“name”。当它们没有时间列时,它们都有自己的代理键“id”。问题是当我添加不再起作用的时间列时。同一个条目可以在同一个表中出现多次。
-
这就是为什么代理键“id”本身不能再只有一列了,因为它是同一个条目,但在不同的时间线中跟踪,所以为了让同一个条目出现更多不止一次,我可以将以下内容添加为主键“id+validstart”或“id+validend”或“id+validstart+validend”。为了方便起见,我在 Java 映射中选择了最后一个选项,其中我有一个定义句点的“间隔”对象,因此为了在 JPA 中映射它,我将“间隔”作为 EmbeddedId 添加到 Id。
-
你可以在en.wikipedia.org/wiki/Surrogate_key看到这个"由于数据库中可能有多个对象对应一个代理,我们不能使用代理作为主键;需要另一个属性,除了代理,以唯一标识每个对象。”
-
明白你的意思。错误是我的英语不好,我认为代理键是“生成的而不是自然的键”。现在我知道它是实体键而不是主键。所以我的回答是:你不需要它。检查(并评论)我更新的答案。只需提供一个生成的密钥,它是没有validstart 或validend 的主键。
标签: java jpa-2.0 eclipselink temporal-database