Подтвердить что ты не робот

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

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

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

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

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

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

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

1-й сценарий: не временная модель

Модель данных: 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<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.

2-й сценарий: временная модель

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

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

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

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

4b9b3361

Ответ 1

Мне очень интересна эта тема. Я работаю уже несколько лет в разработке приложений, которые используют эти шаблоны, идея появилась в нашем случае из немецкой дипломной работы.

Я не знал рамки DAO Fusion, они предоставляют интересную информацию и ссылки, спасибо за предоставление этой информации. Особенно шаблонная страница и страница аспектов великолепны!

На ваши вопросы: нет, я не могу указать другие сайты, примеры или рамки. Я боюсь, что вам придется использовать либо инфраструктуру DAO Fusion, либо реализовать эту функцию самостоятельно. Вы должны различать, какую функциональность вам действительно нужно. Говорить в терминах структуры DAO Fusion: нужны ли вам как "действительные временные", так и "рекордные временные"? Записывайте временные состояния, когда изменение применяется к вашей базе данных (обычно используется для проблем аудита), действительные временные состояния, когда изменение произошло в реальной жизни или действительно в реальной жизни (используется приложением), которое может отличаться от времени записи. В большинстве случаев достаточно одного измерения, а второе измерение не требуется.

В любом случае временная функциональность влияет на вашу базу данных. Как вы заявили: "в настоящее время их первичными ключами являются период действия". Итак, как вы моделируете личность сущности? Я предпочитаю использовать суррогатные ключи. В этом случае это означает:

  • один идентификатор для объекта
  • один идентификатор объекта в базе данных (строка)
  • временные столбцы

Первичным ключом для таблицы является идентификатор объекта. Каждый объект имеет одну или несколько (1-n) записей в таблице, идентифицированных идентификатором объекта. Связывание таблиц основано на идентификаторе объекта. Поскольку временные записи умножают объем данных, стандартные отношения не работают. Стандартное отношение 1-n может стать отношением x * 1-y * n.

Как вы решаете это? Стандартным подходом было бы введение таблицы сопоставления, но это не естественный подход. Для редактирования одной таблицы (например, изменения места жительства) вам также потребуется обновить/вставить таблицу сопоставления, которая является странной для каждого программиста.

Другим подходом было бы не использовать таблицу сопоставления. В этом случае вы не можете использовать ссылочную целостность и внешние ключи, каждая таблица действует изолированно, связь между одной таблицей и другими должна быть выполнена вручную, а не с функциями JPA.

Функциональность инициализации объектов базы данных должна быть внутри объектов (как в структуре DAO Fusion). Я бы не поместил его в службу. Если вы передадите его в DAO или используете шаблон Active Record, вам придется.

Я знаю, что мой ответ не предоставляет вам "готовые к использованию" рамки. Вы находитесь в очень сложной области, из моего опыта ресурсов для этого сценария использования очень сложно найти. Спасибо за ваш вопрос! Но в любом случае я надеюсь, что помог вам в вашем дизайне.

В этом ответе вы найдете справочник "Разработка ориентированных на время приложений баз данных в SQL", см. fooobar.com/questions/120435/...

Обновление: пример

  • Вопрос: скажем, что у меня есть таблица PERSON, у которой есть суррогатный ключ, который является полем с именем "id". Каждая таблица ссылок в этот момент будет иметь этот идентификатор в качестве ограничения внешнего ключа. Если теперь добавить временные столбцы, я должен изменить первичный ключ на "id + from_date + to_date". Перед изменением первичного ключа я должен сначала удалить каждое внешнее ограничение каждой таблицы ссылок в эту ссылочную таблицу (Person). Я прав? Я верю, что вы имеете в виду суррогатный ключ. ID - это сгенерированный ключ, который может быть сгенерирован последовательностью. Бизнес-ключ таблицы Person - это SSN.
  • Ответ: Не совсем. SSN будет естественным ключом, который я не использую для идентичности objcet. Также "id + from_date + to_date" будет составным ключом, чего я также избегу. Если вы посмотрите на exampleу вас будет две таблицы, человек и место жительства, и в нашем примере мы говорим, что у нас есть 1-я связь с резиденцией иностранного ключа. Теперь мы добавляем временные поля для каждой таблицы. Да, мы бросаем все ограничения внешнего ключа. Человек получит 2 идентификатора, один идентификатор для идентификации строки (назовите его ROW_ID), один идентификатор, чтобы идентифицировать самого человека (назовите его ENTIDY_ID) индексом этого идентификатора. То же самое для человека. Конечно, ваш подход тоже будет работать, но в этом случае у вас будут операции, которые изменяют ROW_ID (когда вы закрываете временной интервал), чего я бы избегал.

Чтобы расширить example, реализованный с предположениями выше (2 таблицы, 1-n):

  • запрос для отображения всех записей в базе данных (вся информация о достоверности и запись - ака-техническая информация включена):

    SELECT * FROM Person p, Residence r
    WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON          // JOIN 
  • запрос, чтобы скрыть запись - ака техническая информация. Это показывает все значения validy-Changes сущностей.

    SELECT * FROM Person p, Residence r
    WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON AND
    p.recordTo=[infinity] and r.recordTo=[infinity]    // only current technical state
  • запрос для отображения фактических значений.

    SELECT * FROM Person p, Residence r
    WHERE p.ENTITY_ID = r.FK_ENTITY_ID_PERSON AND
    p.recordTo=[infinity] and r.recordTo=[infinity] AND
    p.validFrom <= [now] AND p.validTo > [now] AND        // only current valid state person
    r.validFrom <= [now] AND r.validTo > [now]            // only current valid state residence

Как вы можете видеть, я никогда не использую ROW_ID. Замените [сейчас] временной отметкой, чтобы вернуться назад.

Обновить, чтобы отразить ваше обновление
Я бы рекомендовал следующую модель данных:

Ввести таблицу "PlaysInTeam":

  • ID
  • Команда ID (внешний ключ для команды)
  • ID Player (внешний ключ для игрока)
  • ValidFrom
  • ValidTo

Когда вы указываете игроков команды, вы должны запросить дату, для которой отношение действительно, и должно быть в [ValdFrom, ValidTo)

Для создания временной команды у меня есть два подхода:

Подход 1: Ввести таблицу "Сезон", которая моделирует срок действия сезона

  • ID
  • Название сезона (например, лето 2011)
  • От (возможно, не обязательно, потому что каждый знает, когда сезон)
  • To (возможно, не обязательно, потому что каждый знает, когда сезон)

Разделите таблицу команд. У вас будут поля, принадлежащие команде, и которые не являются релевантными по времени (имя, адрес и т.д.) И поля, которые относятся к сезону (выигрыш, проигрыш,..). В этом случае я бы использовал Team и TeamInSeason. PlaysInTeam может ссылаться на TeamInSeason вместо Team (должен быть рассмотрен - я бы хотел указать на команду)

TeamInSeason

  • ID
  • ID Team
  • Сезон идентификаторов
  • Win
  • Потеря
  • ...

Подход 2: Не моделируйте сезон явно. Разделите таблицу команд. У вас будут поля, которые принадлежат команде, и которые не являются релевантными по времени (имя, адрес и т.д.) И поля, которые относятся к времени (выигрыш, потеря,..). В этом случае я бы использовал Team и TeamInterval. TeamInterval будет иметь поля "от" и "до" для интервала. PlaysInTeam может ссылаться на TeamInterval вместо Team (я бы отпустил его в Team)

TeamInterval

  • ID
  • ID Team
  • С
  • Для
  • Win
  • Потеря
  • ...

В обоих подходах: если вам не нужна отдельная таблица команд в поле времени, не разделяющее время, не разделяйте.

Ответ 2

Не совсем понятно, что вы имеете в виду, но EclipseLink имеет полную поддержку истории. Вы можете включить HistoryPolicy в ClassDescriptor через @DescriptorCustomizer.

Ответ 3

Кажется, вы не можете сделать это с JPA, так как он предполагает, что имя таблицы и целая схема статичны.

Лучшим вариантом может быть использование JDBC (например, с использованием шаблона DAO)

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

Другим вариантом может быть использование представлений (если вы должны использовать JPA) может быть каким-то образом абстрагировать таблицу (сопоставьте @Entity (name= "myView" ), тогда вам придется динамически обновлять/заменять представление как в CREATE ИЛИ REPLACE VIEW 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)

Ответ 4

в DAO Fusion, отслеживание объекта в обоих временных рамках (срок действия и интервал записи) реализуется путем переноса этого объекта на BitemporalWrapper.

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

В документации также указывается, что каждая битпольная обертка (например, BitemporalOrder) представляет один элемент в цепочке битпоральной записи. Следовательно, вам нужен какой-то объект более высокого уровня, который содержит битмпоральную коллекцию оберток, например. Customer, который содержит @OneToMany Collection<BitemporalOrder> orders.

Итак, если вам нужен "логический дочерний" объект (например, Order или Player), который будет отслеживаться по методу "бит" , а его "логическая родительская" сущность (например, Customer или Team) должна быть отслежена Кроме того, вам необходимо предоставить bitemporal wrappers для обоих. У вас будут BitemporalPlayer и BitemporalTeam. BitemporalTeam может объявить @OneToMany Collection<BitemporalPlayer> players. Но вам нужен какой-то объект более высокого уровня для содержания @OneToMany Collection<BitemporalTeam> teams, как указано выше. Для Например, вы можете создать объект Game, содержащий коллекцию BitemporalTeam.

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