Как реплицировать INSERT / UPDATE / DELETE заявления с использованием JPA и Hibernate

Митч Кент:

Я хотел бы переименовать таблицу PostgreSQL (9.6) таким образом, что это извлекаемые для моей системы (приложение Java с помощью JPA / Hibernate)

В моем Java коде, JPA объект будет иметь следующие аннотации @Entity @Table(name="old_name")и база данных будут иметь эквивалентную таблицу называется old_name.

Я хотел бы переименовать таблицу new_nameтаким образом , что я могу постепенно обновить базу данных и Java приложения, что позволяет для отказа и отката.

Типичные шаги будут

  1. создать копию old_nameинnew_name
  2. обеспечения чтения / записи доступны в обоих (т.е. репликации данных в обоих направлениях)
  3. обновить Java приложение, чтобы использовать новую таблицу new_name
  4. когда уверены обновления системы завершена, удалить old_name

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

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

Я пытался переименовать таблицу и создать «простое представление» над ним, однако JPA организация жалуется, поскольку он не может найти таблицу с именем вида. (Потому что это не вид, а не таблица :) и там, кажется, нет @ Посмотреть / @ Таблица JPA аннотации, которые будут обращаться с этим)

Я еще не пробовал средства , перечисленные здесь: http://wiki.postgresql.org/wiki/Replication,_Clustering,_and_Connection_Pooling , как большинство , кажется, об объединении, сегментировании, и мне нужна простая кратковременные таблицы реплика, но Я буду расследовать это также.

Спасибо - я хотел бы простейший вариант, конечно, предпочитая что-то встроенное в Postgres / JPA, но будет серьезно рассмотреть вопрос о 3-й варианте партии также.

Влад Михалч:

Это был очень интересным вопрос, так что я решил превратить этот ответ в полномасштабную статью .

таблицы баз данных

Если у вас есть следующие две таблицы:

CREATE TABLE old_post (
    id int8 NOT NULL,
    title varchar(255),
    version int4 NOT NULL,
    PRIMARY KEY (id)
)

CREATE TABLE post (
    id int8 NOT NULL,
    created_on date, 
    title varchar(255),
    version int4 NOT NULL,
    PRIMARY KEY (id)
)

JPA объект

old_postТаблица должна быть воспроизведена с новее post. Обратите внимание , что postтаблица имеет больше столбцов, чем старая таблица.

Нам нужно только отобразить Postобъект:

@Entity(name = "Post")
@Table(name = "post")
public static class Post {

    @Id
    private Long id;

    private String title;

    @Column(name = "created_on")
    private LocalDate createdOn = LocalDate.now();

    @Version
    private int version;

    //Getters and setters omitted for brevity
}

Приемники событий Hibernate

Теперь мы должны зарегистрировать 3 слушателей событий для перехвата INSERT, UPDATE и DELETE операций для Postлица.

Мы можем сделать это с помощью следующих слушателей событий:

public class ReplicationInsertEventListener 
        implements PostInsertEventListener {

    public static final ReplicationInsertEventListener INSTANCE = 
        new ReplicationInsertEventListener();

    @Override
    public void onPostInsert(
            PostInsertEvent event) 
            throws HibernateException {
        final Object entity = event.getEntity();

        if(entity instanceof Post) {
            Post post = (Post) entity;

            event.getSession().createNativeQuery(
                "INSERT INTO old_post (id, title, version) " +
                "VALUES (:id, :title, :version)")
            .setParameter("id", post.getId())
            .setParameter("title", post.getTitle())
            .setParameter("version", post.getVersion())
            .setFlushMode(FlushMode.MANUAL)
            .executeUpdate();
        }
    }

    @Override
    public boolean requiresPostCommitHanding(
            EntityPersister persister) {
        return false;
    }
}

public class ReplicationUpdateEventListener 
    implements PostUpdateEventListener {

    public static final ReplicationUpdateEventListener INSTANCE = 
        new ReplicationUpdateEventListener();

    @Override
    public void onPostUpdate(
            PostUpdateEvent event) {
        final Object entity = event.getEntity();

        if(entity instanceof Post) {
            Post post = (Post) entity;

            event.getSession().createNativeQuery(
                "UPDATE old_post " +
                "SET title = :title, version = :version " +
                "WHERE id = :id")
            .setParameter("id", post.getId())
            .setParameter("title", post.getTitle())
            .setParameter("version", post.getVersion())
            .setFlushMode(FlushMode.MANUAL)
            .executeUpdate();
        }
    }

    @Override
    public boolean requiresPostCommitHanding(
            EntityPersister persister) {
        return false;
    }
}

public class ReplicationDeleteEventListener 
        implements PreDeleteEventListener {

    public static final ReplicationDeleteEventListener INSTANCE = 
        new ReplicationDeleteEventListener();

    @Override
    public boolean onPreDelete(
            PreDeleteEvent event) {
        final Object entity = event.getEntity();

        if(entity instanceof Post) {
            Post post = (Post) entity;

            event.getSession().createNativeQuery(
                "DELETE FROM old_post " +
                "WHERE id = :id")
            .setParameter("id", post.getId())
            .setFlushMode(FlushMode.MANUAL)
            .executeUpdate();
        }

        return false;
    }
}

В 3 Приемники событий могут быть зарегистрированы с помощью Hibernate Integrator:

public class ReplicationEventListenerIntegrator 
        implements Integrator {

    public static final ReplicationEventListenerIntegrator INSTANCE = 
        new ReplicationEventListenerIntegrator();

    @Override
    public void integrate(
            Metadata metadata,
            SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {

        final EventListenerRegistry eventListenerRegistry =
                serviceRegistry.getService(EventListenerRegistry.class);

        eventListenerRegistry.appendListeners(
            EventType.POST_INSERT, 
            ReplicationInsertEventListener.INSTANCE
        );

        eventListenerRegistry.appendListeners(
            EventType.POST_UPDATE, 
            ReplicationUpdateEventListener.INSTANCE
        );

        eventListenerRegistry.appendListeners(
            EventType.PRE_DELETE, 
            ReplicationDeleteEventListener.INSTANCE
        );
    }

    @Override
    public void disintegrate(
            SessionFactoryImplementor sessionFactory,
            SessionFactoryServiceRegistry serviceRegistry) {

    }
}

И поручить Hibernate использовать этот обычай Integrator, вам необходимо настроить hibernate.integrator_providerсвойство конфигурации:

<property name="hibernate.integrator_provider"
          value="com.vladmihalcea.book.hpjp.hibernate.listener.ReplicationEventListenerIntegrator "/>

время тестирования

Теперь, когда сохраняющиеся в Postлицо:

Post post1 = new Post();
post1.setId(1L);
post1.setTitle(
    "The High-Performance Java Persistence book is to be released!"
);

entityManager.persist(post1);

Hibernate будет выполнять следующие операторы SQL INSERT:

Query:["INSERT INTO old_post (id, title, version) VALUES (?, ?, ?)"], Params:[(1, The High-Performance Java Persistence book is to be released!, 0)]

Query:["insert into post (created_on, title, version, id) values (?, ?, ?, ?)"], Params:[(2018-12-12, The High-Performance Java Persistence book is to be released!, 0, 1)]

При выполнении другой транзакции , которая обновляет существующий Postобъект и создает новый Postобъект:

Post post1 = entityManager.find(Post.class, 1L);
post1.setTitle(post1.getTitle().replace("to be ", ""));

Post post2 = new Post();
post2.setId(2L);
post2.setTitle(
    "The High-Performance Java Persistence book is awesome!"
);

entityManager.persist(post2);

Hibernate копирует все действия к old_postстолу , а также:

 Query:["select tablerepli0_.id as id1_1_0_, tablerepli0_.created_on as created_2_1_0_, tablerepli0_.title as title3_1_0_, tablerepli0_.version as version4_1_0_ from post tablerepli0_ where tablerepli0_.id=?"], Params:[(1)]

 Query:["INSERT INTO old_post (id, title, version) VALUES (?, ?, ?)"], Params:[(2, The High-Performance Java Persistence book is awesome!, 0)]

 Query:["insert into post (created_on, title, version, id) values (?, ?, ?, ?)"], Params:[(2018-12-12, The High-Performance Java Persistence book is awesome!, 0, 2)]

 Query:["update post set created_on=?, title=?, version=? where id=? and version=?"], Params:[(2018-12-12, The High-Performance Java Persistence book is released!, 1, 1, 0)]

 Query:["UPDATE old_post SET title = ?, version = ? WHERE id = ?"], Params:[(The High-Performance Java Persistence book is released!, 1, 1)]

При удалении Postобъекта:

Post post1 = entityManager.getReference(Post.class, 1L);
entityManager.remove(post1);

old_postЗапись deletected , а также:

Query:["DELETE FROM old_post WHERE id = ?"], Params:[(1)]
Query:["delete from post where id=? and version=?"], Params:[(1, 1)]

Для получения более подробной информации ознакомьтесь с этой статьей .

Код доступен на GitHub .

рекомендация

отhttp://43.154.161.224:23101/article/api/json?id=184080&siteId=1