如何使用JPA实现时态表?

我想知道如何使用EclipseLink在JPA 2中实现时态表 。 时间上我指的是定义有效期的表。

我面临的一个问题是引用表不再具有对引用表(时态表)的外键约束,因为引用表的性质现在它们的主键包括有效期。

  • 我如何映射我的实体的关系?
  • 这是否意味着我的实体不再与那些有效时间实体建立关系?
  • 我是否应该在某种服务或专门的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 players = new ArrayList(); 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 getPlayers() { return players; } public void setPlayers(List 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 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是代理键。 但是现在代理键必须包括日期,因为如果它们不包含它将不允许存储同一实体的多个“ 版本 ”(在时间线期间)。

我对这个话题很感兴趣。 我在开发使用这些模式的应用程序方面已经工作了几年,这个想法来自德国文凭论文。

我不知道“DAO Fusion”框架,它们提供了有趣的信息和链接,感谢您提供这些信息。 特别是模式页面和方面页面都很棒!

对你的问题:不,我不能指出其他网站,例子或框架。 我担心您必须使用DAO Fusion框架或自己实现此function。 您必须区分您真正需要的function。 用“DAO Fusion”框架来说话:你是否需要“有效时间”和“记录时间”? 记录应用于数据库的更改时的时间状态(通常用于审计问题),在现实生活中发生更改时的有效时态或在现实生活中有效(由应用程序使用),这可能与记录时间不同。 在大多数情况下,一个维度就足够了,不需要第二个维度。

无论如何,时间function会对您的数据库产生影响。 如你所说: “现在它们的主键包括有效期” 。 那么如何模拟实体的身份? 我更喜欢使用代理键 。 在这种情况下,这意味着:

  • 实体的一个id
  • 数据库中对象的一个​​id(行)
  • 时间列

表的主键是对象ID。 每个实体在表中具有一个或多个(1-n)条目,由对象id标识。 表之间的链接基于实体ID。 由于时间条目乘以数据量,因此标准关系不起作用。 标准的1-n关系可能成为ax * 1-y * n关系。

你是如何解决这个问题的? 标准方法是引入映射表,但这不是一种自然的方法。 仅仅为了编辑一个表(例如,发生住所更改),您还必须更新/插入映射表,这对每个程序员来说都很奇怪。

另一种方法是不使用映射表。 在这种情况下,您不能使用参照完整性和外键,每个表都是隔离的,从一个表到其他表的链接必须手动实现,而不是JPAfunction。

初始化数据库对象的function应该在对象内(如在DAO Fusion框架中)。 我不会把它放在服务中。 如果您将它放入DAO或使用Active Record Pattern取决于您。

我知道我的答案没有为您提供“随时可用”的框架。 你处在一个非常复杂的领域,从我的经验资源到这种使用场景都很难找到。 谢谢你的提问! 但无论如何,我希望我帮助你完成你的设计。

在本回答中,您将找到参考书“在SQL中开发面向时间的数据库应用程序”,请参阅https://stackoverflow.com/a/800516/734687

更新:示例

  • 问题:假设我有一个PERSON表,其中有一个代理键,这是一个名为“id”的字段。 此时的每个引用表都将“ID”作为外键约束。 如果我现在添加时间列,我必须将主键更改为“id + from_date + to_date”。 在更改主键之前,我必须首先将每个引用表的每个外部约束都删除到此引用表(Person)。 我对吗? 我相信这就是你对代理钥匙的意思。 ID是可以由序列生成的生成密钥。 Person表的业务键是SSN。
  • 答:不完全是。 SSN将是一个自然的关键,我不会用于objcet身份。 “id + from_date + to_date”也是一个复合键 ,我也会避免使用它。 如果你看一下这个例子,你就会有两张桌子,人和住所,我们的例子就说我们与外国人居住地有一个1-n的关系。 现在我们在每个表上添加时间字段。 是的,我们放弃每个外键约束。 Person将获得2个ID,一个ID用于标识该行(称为ROW_ID),一个ID用于标识该人本身(称为ENTIDY_ID),并带有该ID的索引。 同样的人。 当然你的方法也会起作用,但在这种情况下,你会有更改ROW_ID的操作(当你关闭一个时间间隔时),我会避免。

扩展使用上述假设(2个表,1-n)实现的示例 :

  • 查询以显示数据库中的所有条目(包括所有有效性信息和记录 – 也包括技术信息):

     SELECT * FROM Person p, Residence r WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON // JOIN 
  • 用于隐藏记录的查询 – 也称为技术 – 信息。 这显示了实体的所有有效变更。

     SELECT * FROM Person p, Residence r WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON AND p.recordTo=[infinity] and r.recordTo=[infinity] // only current technical state 
  • 用于显示实际值的查询。

     SELECT * FROM Person p, Residence r WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON AND p.recordTo=[infinity] and r.recordTo=[infinity] AND p.validFrom <= [now] AND p.validTo > [now] AND // only current valid state person r.validFrom <= [now] AND r.validTo > [now] // only current valid state residence 

如你所见,我从不使用ROW_ID。 将[now]替换为时间戳,以便及时返回。

更新以反映您的更新
我会推荐以下数据模型:

介绍“PlaysInTeam”表:

  • ID
  • ID团队(团队的外键)
  • ID播放器(播放器的外键)
  • ValidFrom
  • 有效

当您列出团队的玩家时,您必须查询关系有效的日期,并且必须在[ValdFrom,ValidTo]中

为了使团队成为时间,我有两种方法;

方法1:引入一个“季节”表,模拟一个季节的有效性

  • ID
  • 季节名称(例如2011年夏季)
  • 来自(可能没有必要,因为每个人都知道什么时候这个季节)
  • (也许没有必要,因为每个人都知道什么时候这个季节)

拆分团队表。 您将拥有属于团队的字段,这些字段与时间无关(名称,地址,…)和与季节相关的时间字段(胜利,损失,……)。 在那种情况下,我会使用Team和TeamInSeason。 PlaysInTeam可以链接到TeamInSeason而不是Team(必须考虑 – 我会让它指向Team)

TeamInSeason

  • ID
  • ID团队
  • ID季节
  • 赢得
  • 失利

方法2:不要明确地模仿季节。 拆分团队表。 您将拥有属于团队的字段,这些字段与时间无关(名称,地址,…)和时间相关的字段(胜利,损失,……)。 在那种情况下,我会使用Team和TeamInterval。 TeamInterval将具有间隔“from”和“to”的字段。 PlaysInTeam可以链接到TeamInterval而不是Team(我会在团队中让它)

TeamInterval

  • ID
  • ID团队
  • 赢得
  • 失利

在这两种方法中:如果您不需要单独的团队表,没有时间相关字段,请不要拆分。

不完全确定你的意思,但EclipseLink完全支持历史。 您可以通过@DescriptorCustomizer在ClassDescriptor上启用HistoryPolicy 。

看来你不能用JPA来做,因为它假设table-name和整个架构是静态的。

最好的选择可能是通过JDBC(例如使用DAO模式)

如果性能是问题,除非我们谈论数以千万计的记录,否则我怀疑动态创建类并编译它然后加载它会更好。

另一种选择可能是使用视图(如果你必须使用JPA)可能以某种方式抽象表(映射@Entity(name =“myView”),那么你必须动态更新/替换视图,如在CREATE OR REPLACE中查看usernameView AS SELECT * FROM prefix_sessionId

例如,您可以写一个视图来说:

if (EVENT_TYPE = 'crear_tabla' AND ObjectType = 'tabla ' && ObjectName starts with 'userName') then CREATE OR REPLACE VIEW userNameView AS SELECT * FROM ObjectName //the generated table.

希望这会有所帮助(espero que te ayude)

在DAO Fusion中 ,通过BitemporalWrapper包装该实体来实现在两个时间轴(有效性和记录间隔)中跟踪实体。

双时态参考文档提供了一个示例,其中常规Order实体由BitemporalOrder实体包装。 BitemporalOrder映射到单独的数据库表,其中包含有效和记录间隔的列,以及对每个表行的Order (通过@ManyToOne )的外键引用。

文档还指出每个双时态包装器(例如BitemporalOrder )代表双时态记录链中的一个项目 。 因此,您需要一些包含双时态包装器集合的更高级实体,例如包含@OneToMany Collection orders Customer实体。

因此,如果您需要同时跟踪“逻辑子”实体(例如OrderPlayer ),并且其“逻辑父”实体(例如CustomerTeam )也要进行双点跟踪,则需要为两者提供双时态包装器。 你将拥有BitemporalPlayerBitemporalTeam@OneToMany Collection players可以宣布@OneToMany Collection players 。 但是如上所述,您需要一些更高级别的实体来包含@OneToMany Collection teams 。 例如,您可以创建一个包含BitemporalTeam集合的Game实体。

但是,如果您不需要记录间隔,并且您只需要有效间隔(例如,不是双时态,而是对您的实体进行单时间跟踪),那么您最好的选择是推出自己的自定义实现。