我想知道如何使用EclipseLink在JPA 2中实现temporal tables。时间上我指的是定义有效期的表。
我面临的一个问题是引用表不再对引用的表(时态表)具有外键约束,因为引用表的性质现在它们的主键包括有效期。
我发现的唯一一个名为DAO Fusion的框架可以解决这个问题。
这是一个数据模型及其类的虚构示例。它起初是一个简单的模型,不需要处理时间方面:
第一种情景:非时间模型
数据模型:
团队:
@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是代理键。但现在代理键必须包含日期,因为如果它们不包含,则不允许存储同一实体的多个“版本”(在时间线期间)。
答案 0 :(得分:7)
我对这个话题非常感兴趣。我在开发使用这些模式的应用程序方面已经工作了几年,我们的案例来自德国文凭论文。
我不知道&#34; DAO Fusion&#34;框架,它们提供有趣的信息和链接,感谢您提供此信息。特别是pattern page和aspects page很棒!
您的问题:不,我不能指出其他网站,示例或框架。我担心您必须使用DAO Fusion框架或自己实现此功能。您必须区分您真正需要的功能。用&#34; DAO Fusion&#34;来说话框架:你需要两个&#34;有效的时间&#34;和&#34;记录时间&#34;?记录应用于数据库的更改时的时间状态(通常用于审计问题),在现实生活中发生更改时的有效时态或在现实生活中有效(由应用程序使用),这可能与记录时间不同。在大多数情况下,一个维度就足够了,不需要第二个维度。
无论如何,时间功能会对您的数据库产生影响。正如您所说:&#34;现在他们的主键包括有效期&#34; 。那么如何模拟实体的身份?我更喜欢使用surrogate keys。在这种情况下,这意味着:
表的主键是对象ID。每个实体在表中具有一个或多个(1-n)条目,由对象id标识。表之间的链接基于实体ID。由于时间条目会增加数据量,因此标准关系不起作用。标准的1-n关系可能会成为x * 1-y * n关系。
你是如何解决这个问题的?标准方法是引入映射表,但这不是一种自然的方法。仅仅为了编辑一个表(例如,发生住宅更改),您还必须更新/插入映射表,这对每个程序员来说都很奇怪。
另一种方法是不使用映射表。在这种情况下,您不能使用参照完整性和外键,每个表都是隔离的,从一个表到其他表的链接必须手动实现,而不是JPA功能。
初始化数据库对象的功能应该在对象内(如在DAO Fusion框架中)。我不会把它放在服务中。如果您将它放入DAO或使用Active Record Pattern取决于您。
我知道我的回答并没有为你提供一个随时可用的&#34;框架。你处在一个非常复杂的领域,从我的经验资源到这种使用场景都很难找到。谢谢你的提问!但无论如何,我希望我帮助你完成你的设计。
在本回答中,您将找到参考书&#34;在SQL&#34;中开发面向时间的数据库应用程序,请参阅https://stackoverflow.com/a/800516/734687
更新:示例
扩展使用上述假设(2表,1-n)实施的example:
显示数据库中所有条目的查询(包括所有有效性信息和记录 - 也包括技术信息):
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]替换为时间戳,以便及时返回。
更新以反映您的更新
我建议使用以下数据模型:
介绍&#34; PlaysInTeam&#34;表:
当您列出团队的玩家时,您必须查询关系有效的日期,并且必须在[ValdFrom,ValidTo]中
为了使团队成为时间,我有两种方法;
方法1: 介绍一个&#34; Season&#34;模拟季节有效性的表格
拆分团队表。您将拥有属于团队的字段,这些字段与时间无关(名称,地址,...)和与季节相关的时间字段(胜利,损失,......)。在那种情况下,我会使用Team和TeamInSeason。 PlaysInTeam可以链接到TeamInSeason而不是Team(必须考虑 - 我会让它指向团队)
TeamInSeason
方法2: 不要明确地模仿季节。拆分团队表。您将拥有属于团队的字段,这些字段与时间无关(名称,地址,...)和时间相关的字段(胜利,损失,......)。在那种情况下,我会使用Team和TeamInterval。 TeamInterval会有&#34;来自&#34;和&#34;到&#34;为间隔。 PlaysInTeam可以链接到TeamInterval而不是Team(我会让它在Team上)
TeamInterval
在这两种方法中:如果您不需要一个单独的团队表,没有时间相关字段,请不要拆分。
答案 1 :(得分:2)
不完全确定您的意思,但EclipseLink完全支持历史记录。您可以通过@DescriptorCustomizer在ClassDescriptor上启用HistoryPolicy。
答案 2 :(得分:1)
似乎您无法使用JPA,因为它假定表名和整个架构是静态的。
最好的选择可能是通过JDBC(例如使用DAO模式)
如果性能是问题,除非我们谈论数以千万计的记录,否则我怀疑是否会动态创建类&amp;编译它&amp;然后加载它会更好。
另一种选择可能是使用视图(如果你必须使用JPA)可能以某种方式抽象表(映射@Entity(name =“myView”),那么你必须动态更新/替换视图,如创建或替换视图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)
答案 3 :(得分:1)
,通过BitemporalWrapper
包裹该实体来实现在两个时间轴(有效期和记录间隔)中跟踪实体。
bitemporal reference documentation提供了一个由Order
实体包装的常规BitemporalOrder
实体的示例。 BitemporalOrder
映射到一个单独的数据库表,其中包含有效和记录间隔的列,以及每个表行的Order
(通过@ManyToOne
)的外键引用。
文档还指出每个双时态包装器(例如BitemporalOrder
)代表双时间记录链中的一个项目。因此,您需要一些包含双时间包装器集合的更高级实体,例如: Customer
实体,其中包含@OneToMany Collection<BitemporalOrder> orders
。
所以,如果你需要一个“逻辑子”实体(例如Order
或Player
)进行双点跟踪,以及它的“逻辑父”实体(例如Customer
或{{ 1}})要进行双点跟踪,你需要为两者提供双时间包装。您将拥有Team
和BitemporalPlayer
。 BitemporalTeam
可以声明BitemporalTeam
。但是如上所述,您需要一些更高级别的实体来包含@OneToMany Collection<BitemporalPlayer> players
。对于
例如,您可以创建一个包含@OneToMany Collection<BitemporalTeam> teams
集合的Game
实体。
但是,如果您不需要记录间隔并且您只需要有效间隔(例如,不是双时间,而是对您的实体进行单时间跟踪),那么您最好的选择是推出自己的自定义实现。