Chciałbym wiedzieć, jak wdrożyć temporal tables w JPA 2 z EclipseLink. Przez czasowy mam na myśli tabele, które definiują okres ważności.Jak wdrożyć tabelę czasową używając WZP?
Jednym z problemów, przed którym stoję, jest to, że tabele odwołań nie mogą już zawierać ograniczeń dotyczących kluczy obcych do przywoływanych tabel (tabel tymczasowych) ze względu na charakter tabel przywoływanych, które obecnie zawierają klucze podstawowe z okresem ważności.
- Jak mam odwzorować relacje moich podmiotów?
- Czy to oznacza, że moje podmioty nie mogą już mieć związek z tymi podmiotami ważne w czasie?
- W przypadku odpowiedzialność ponosi zainicjować te relacje teraz zrobić przeze mnie ręcznie w jakiejś usługi lub specjalistycznej DAO?
Jedyne, co znalazłem, to framework o nazwie DAO Fusion, który zajmuje się tym.
- Czy istnieją inne sposoby rozwiązania tego problemu?
- Czy możesz podać przykład lub zasoby na ten temat (WZP z tymczasowymi bazami danych)?
Oto fikcyjny przykład modelu danych i jego klas. Zaczyna jako prostego modelu, który nie ma do czynienia z aspektów czasowych:
1-ty Scenariusz: Non Temporal model
Data Model:
zespołu:
@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;
}
}
gracza: Klasa
@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;
}
}
Test:
@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);
}
}
Teraz zostaniesz poproszony, aby mieć historię, jak zespół i odtwarzacz był w pewnym momencie tak, co trzeba zrobić, to dodać okres czasu dla każdej tabeli, która chce być śledzona. Dodajmy więc te tymczasowe kolumny. Zaczniemy od zaledwie Player
.
2-ty Scenariusz: Temporal model
Data Model:
Jak widać musieliśmy usunąć klucz podstawowy i określić inny, który zawiera daty (kropka). Musieliśmy także zrezygnować z unikalnych ograniczeń, ponieważ teraz można je powtórzyć w tabeli. Teraz tabela może zawierać bieżące wpisy, a także historię.
Rzeczy stają się bardzo brzydkie, jeśli musimy również sprawić, aby zespół był tymczasowy, w tym przypadku musielibyśmy usunąć ograniczenie klucza obcego, które ma Player
dla Team
. Problem polega na tym, jak można to modelować w Javie i JPA.
Należy zauważyć, że identyfikator jest kluczem zastępczym. Ale teraz klucze zastępcze muszą zawierać datę, ponieważ jeśli nie, to nie pozwoliłyby na przechowywanie więcej niż jednego "wersji" tego samego obiektu (na osi czasu).
1) z jakim narzędziem narysowałeś diagram? 2) jeden wymiar czasowy jest wystarczający dla twoich wymagań, wzory DAOFusion, a także moja odpowiedź (w oparciu o te wzory) są przesadzone według mojej opinii 3) Wolisz rozwiązanie, które po prostu dodaje aspekt czasowy do Gracza lub wolisz? do obu tabel 4) twój ostatni akapit jest nieprawidłowy. Zastępczy klucz nigdy nie będzie zawierał dodatkowych pól. W takim przypadku masz dwa klucze zastępcze. – ChrLipp
@ChrLipp 1) Sparx Enterprise Architect 2) Zgadzam się. 3) Potrzebuję rozwiązania, które doda czas do obu tabel. 4) Nie zgadzam się, że to nie jest klucz zastępczy. Myślę, że jest to klucz zastępczy, ponieważ: 1. Przed dodaniem tymczasowych kolumn był to klucz zastępczy, który jest kluczem bez znaczenia biznesowego.Na przykład klucz biznesowy Gracza to "team_id" i "numer", a od Team to "name". Oba mają własny klucz zastępczy "id", gdy nie mają kolumn czasowych. Problem polega na tym, że kiedy dodaję kolumny tymczasowe, które już nie działają. Ten sam wpis może pojawić się więcej niż jeden raz w tej samej tabeli. –
To dlatego zastępczy klucz "id" sam w sobie nie może już być tylko jedną kolumną, ponieważ jest to ten sam wpis, ale śledzony w różnych ramach czasowych, więc aby pozwolić, aby ten sam wpis pojawił się więcej niż jeden raz, mógłbym dodać następujące jako klucz podstawowy "id + validstart" lub "id + validend" lub "id + validstart + validend". Wybrałem ostatnią opcję dla wygody w Java Mappings, gdzie mam obiekt "Interval", który definiuje kropkę, więc aby odwzorować to w JPA, dodałem, że "Interval" do Id jako EmbeddedId. –