Я хотел бы знать, как реализовать temporal tables в JPA 2 с EclipseLink. По временному я имею в виду таблицы, которые определяют срок действия.Как реализовать временную таблицу с использованием JPA?
Одна из проблем, с которой я столкнулся, заключается в том, что ссылочные таблицы больше не имеют ограничений внешних ключей к ссылочным таблицам (временным таблицам) из-за характера ссылочных таблиц, которые теперь в их первичных ключах включают период действия.
- Как я смогу сопоставить отношения моих сущностей?
- Будет ли это означать, что мои сущности больше не могут иметь отношения к этим объектам, действующим во времени?
- Должна ли ответственность за инициализацию этих отношений теперь выполнять вручную вручную в какой-либо службе или специализированном DAO?
Единственное, что я нашел, это рама под названием DAO Fusion, которая занимается этим.
- Есть ли другие способы решить эту проблему?
- Не могли бы вы предоставить пример или ресурсы по этой теме (JPA с временными базами данных)?
Вот вымышленный пример модели данных и ее классов. Она начинается как простая модель, не приходится иметь дело с временными аспектами:
первого сценарий: Non Temporal Модель
Модели данных:
команды:
@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
.
второй сценарий: Temporal Модель
Data Model:
Как вы можете видеть, что мы должны были отбросить первичный ключ и определить другой, который включает в себя дату (период). Также нам пришлось отказаться от уникальных ограничений, потому что теперь их можно повторить в таблице. Теперь таблица может содержать текущие записи, а также историю.
Все становится довольно уродливым, если мы также должны сделать команду временной, в этом случае нам нужно будет сбросить ограничение внешнего ключа, чтобы таблица Player
имела значение Team
. Проблема в том, как бы вы моделировали это в Java и JPA.
Обратите внимание, что идентификатор является суррогатной клавишей. Но теперь ключи суррогата должны включать дату, потому что, если они этого не сделают, это не позволит хранить более одной «версии» того же объекта (на временной шкале).
1) с помощью какого инструмента вы нарисовали диаграмму? 2) одного временного измерения достаточно для ваших требований, шаблоны DAOFusion, а также мой ответ (на основе этих шаблонов), по моему мнению, являются излишними. 3) Вы предпочитаете решение, которое просто добавляет временный аспект к Player или вы предпочитаете это для обеих таблиц 4) ваш последний абзац неверен. Суррогатный ключ никогда не будет включать дополнительные поля. В этом случае у вас будет два суррогатных ключа. – ChrLipp
@ChrLipp 1) Архитектор Sparx Enterprise 2) Я согласен. 3) Мне нужно решение, которое добавляет временные значения для обеих таблиц. 4) Я не согласен, что это не суррогатный ключ. Я думаю, что это суррогатный ключ, потому что: 1. Перед добавлением временных столбцов это был суррогатный ключ, который является ключом, а не бизнес-значением.Например, бизнес-ключ игрока - «team_id» и «number», а Team - «имя». Оба имеют свой собственный суррогатный ключ «id», когда у них не было временных столбцов. Проблема в том, что когда я добавляю временные столбцы, которые больше не работают. Одна и та же запись может появляться более одного раза в одной таблице. –
Вот почему суррогатный ключ «id» сам по себе не может быть только одним столбцом, потому что он является одной и той же записью, но отслеживается в разные сроки, поэтому, чтобы позволить одной и той же записи появляться более одного раза, я мог бы добавить следующие как первичный ключ «id + validstart» или «id + validend» или «id + validstart + validend». Я выбрал последний вариант для удобства в Java-сопоставлениях, где у меня есть объект «Интервал», который определяет период, поэтому, чтобы сопоставить это в JPA, я добавил, что «Интервал» равен Id как EmbeddedId. –