Я хотел бы знать, как реализовать темпоральные таблицы в JPA 2 с помощью EclipseLink. Под временными я подразумеваю таблицы, которые определяют срок действия.
Одна из проблем, с которой я столкнулся, заключается в том, что ссылки на таблицы больше не могут иметь ограничений внешних ключей для ссылочных таблиц (темпоральных таблиц) из-за природы ссылочных таблиц, первичные ключи которых теперь включают период действия.
Единственное, что я нашел, - это фреймворк под названием DAO Fusion , который имеет дело с это.
Вот вымышленный пример модели данных и ее классов.Он начинается с простой модели, которая не имеет отношения к временным аспектам:
1-й сценарий: Non Temporal Model
Модель данных :
Команда :
@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);
}
}
Теперь вас просят сохранить историю того, как команда и игрок были в определенный момент времени, так что что вам нужно do - это добавить период времени для каждой таблицы, которую нужно отслеживать. Итак, давайте добавим эти временные столбцы. Мы начнем с Player
.
2-й сценарий: временная модель
Модель данных:
Как видите, нам пришлось отбросить первичный ключ и определить другой, который включает даты (период). Также нам пришлось отказаться от уникальных ограничений, потому что теперь они могут повторяться в таблице. Теперь таблица может содержать текущие записи, а также историю.
Все становится довольно некрасиво, если мы также должны сделать команду временной, в этом случае нам нужно будет снять ограничение внешнего ключа, которое таблица Player
должна иметь для Team
. Проблема в том, как бы вы смоделировали это на Java и JPA.
Обратите внимание, что ID является суррогатным ключом. Но теперь суррогатные ключи должны включать дату, потому что в противном случае это не позволило бы хранить более одной « версии » одного и того же объекта (на временной шкале).
Кажется, что вы не можете сделать это с 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)
в DAO Fusion , отслеживание объекта в обеих временных шкалах (срок действия и интервал записи) реализуется путем переноса этого объекта в BitemporalWrapper
.
Битемпоральная справочная документация представляет пример с обычной сущностью Order
, обернутой сущностью BitemporalOrder
. BitemporalOrder
отображается в отдельную таблицу базы данных со столбцами для достоверности и интервала записи и ссылкой на внешний ключ на Order
(через @ManyToOne
) для каждой строки таблицы.
Документация также указывает, что каждая битемпоральная обертка (например, BitemporalOrder
) представляет один элемент в цепочке битемпоральной записи . Следовательно, вам нужен объект более высокого уровня, содержащий коллекцию битемпоральных оболочек, например, Customer
объект, который содержит @OneToMany Collection<BitemporalOrder> orders
.
Таким образом, если вам нужно, чтобы «логический дочерний» объект (например, Order
или Player
) отслеживался битемпорально, а его «логический родительский» (например, Customer
или Team
) отслеживался битемпорально также вам необходимо предоставить битемпоральные обертки для обоих. У вас будет BitemporalPlayer
и BitemporalTeam
. BitemporalTeam
можно объявить @OneToMany Collection<BitemporalPlayer> players
. Но вам нужна некоторая сущность более высокого уровня для содержания @OneToMany Collection<BitemporalTeam> teams
, как упомянуто выше. Например, вы можете создать сущность Game
, содержащую коллекцию BitemporalTeam
.
Однако, если вам не нужен интервал записи и вам просто нужен интервал достоверности (например, не битемпоральный, а одновременный трекинг ваших сущностей), вам лучше всего развернуть собственную реализацию.