Como implementar uma tabela temporal usando JPA?
Gostaria de saber como implementartemporal tables no JPA 2 com EclipseLink. Por temporal, quero dizer tabelas que definem o período de validad
Um problema que estou enfrentando é que as tabelas de referência não podem mais ter restrições de chaves estrangeiras nas tabelas referenciadas (tabelas temporais) devido à natureza das tabelas referenciadas, que agora suas chaves primárias incluem o período de validad
Como mapearia os relacionamentos de minhas entidades?Isso significa que minhas entidades não podem mais ter um relacionamento com essas entidades de tempo válido? A responsabilidade de inicializar esses relacionamentos agora é feita manualmente por mim em algum tipo de Serviço ou DAO especializado?A única coisa que encontrei é uma estrutura chamadaDAO Fusion que lida com isso.
Existem outras maneiras de resolver iss Você poderia fornecer um exemplo ou recursos sobre este tópico (JPA com bancos de dados temporaisAqui está um exemplo fictício de um modelo de dados e suas classes. Começa como um modelo simples que não precisa lidar com aspectos temporais:
1º Cenário: Modelo Não Temporal
Modelo de dado:
Equip:
@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;
}
}
Jogado:
@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;
}
}
Classe de teste:
@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);
}
}
Agora, você é solicitado a manter um histórico de como o time e o jogador estavam em determinado ponto do tempo; portanto, o que você precisa fazer é adicionar um período para cada tabela que deseja ser rastreada. Então, vamos adicionar essas colunas temporais. Vamos começar com apenasPlayer
.
2º Cenário: Modelo Temporal
Modelo de dados
Como você pode ver, tivemos que soltar a chave primária e definir outra que inclua as datas (período). Também tivemos que eliminar as restrições exclusivas, porque agora elas podem ser repetidas na tabela. Agora a tabela pode conter as entradas atuais e também o históric
s coisas ficam muito feias se também tivermos que tornar a equipe temporária; nesse caso, precisaríamos eliminar a restrição de chave estrangeira quePlayer
tabela tem queTeam
. O problema é como você modelaria isso em Java e JP
Observe que o ID é uma chave substituta. Mas agora as chaves substitutas precisam incluir a data porque, se não o fizerem, não permitirão armazenar mais de um "versã "da mesma entidade (durante a linha do tempo).