Как реализовать темпоральную таблицу с помощью JPA?

Я хотел бы знать, как реализовать темпоральные таблицы в JPA 2 с помощью EclipseLink. Под временными я подразумеваю таблицы, которые определяют срок действия.

Одна из проблем, с которой я столкнулся, заключается в том, что ссылки на таблицы больше не могут иметь ограничений внешних ключей для ссылочных таблиц (темпоральных таблиц) из-за природы ссылочных таблиц, первичные ключи которых теперь включают период действия.

  • Как мне отобразить отношения моих сущностей?
  • Означает ли это, что мои сущности больше не могут иметь отношения к этим сущностям действительного времени?
  • Должен ли я выполнять инициализацию этих отношений вручную в какой-то Службе или специализированном DAO?

Единственное, что я нашел, - это фреймворк под названием DAO Fusion , который имеет дело с это.

  • Есть ли другие способы решить эту проблему?
  • Не могли бы вы предоставить пример или ресурсы по этой теме (JPA с темпоральными базами данных)?

Вот вымышленный пример модели данных и ее классов.Он начинается с простой модели, которая не имеет отношения к временным аспектам:

1-й сценарий: Non Temporal Model

Модель данных : Non Temporal Data 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-й сценарий: временная модель

Модель данных: Temporal Data Model

Как видите, нам пришлось отбросить первичный ключ и определить другой, который включает даты (период). Также нам пришлось отказаться от уникальных ограничений, потому что теперь они могут повторяться в таблице. Теперь таблица может содержать текущие записи, а также историю.

Все становится довольно некрасиво, если мы также должны сделать команду временной, в этом случае нам нужно будет снять ограничение внешнего ключа, которое таблица Player должна иметь для Team . Проблема в том, как бы вы смоделировали это на Java и JPA.

Обратите внимание, что ID является суррогатным ключом. Но теперь суррогатные ключи должны включать дату, потому что в противном случае это не позволило бы хранить более одной « версии » одного и того же объекта (на временной шкале).

25
задан Alfredo Osorio 9 March 2012 в 19:27
поделиться

2 ответа

Кажется, что вы не можете сделать это с 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)

1
ответ дан 28 November 2019 в 21:59
поделиться

в 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.

Однако, если вам не нужен интервал записи и вам просто нужен интервал достоверности (например, не битемпоральный, а одновременный трекинг ваших сущностей), вам лучше всего развернуть собственную реализацию.

1
ответ дан 28 November 2019 в 21:59
поделиться
Другие вопросы по тегам:

Похожие вопросы: