簡體   English   中英

如何使用 JPA 和 Hibernate 復制 INSERT/UPDATE/DELETE 語句

[英]How to replicate INSERT/UPDATE/DELETE statements using JPA and Hibernate

我想以我的系統可恢復的方式重命名 PostgreSQL (9.6) 表(使用 JPA/Hibernate 的 Java 應用程序)

在我的 java 代碼中,JPA 實體將具有以下注釋@Entity @Table(name="old_name")並且數據庫將具有一個等效的表,稱為old_name

我想以一種可以增量更新數據庫和 Java 應用程序的方式將表重命名為new_name ,允許失敗和回滾。

典型的步驟是

  1. 在新名稱中創建old_name名稱的new_name
  2. 確保讀/寫可用(即數據以兩種方式復制)
  3. 更新 Java 應用程序以使用新表new_name
  4. 當自信的系統更新完成后,刪除old_name

實際上,我想要一個具有相同數據的相同架構中的重復表,它們都能夠接受讀取和寫入,並且可以從 JPA 實體中讀取。

我知道觸發器的使用,並希望避免這種情況。 我希望有一種我不知道也沒有發現的技術可以使這比使用觸發器更不痛苦。

我試圖重命名該表並在其上創建一個“簡單視圖”,但是 JPA 實體抱怨說它找不到具有該視圖名稱的表。 (因為它是一個視圖,而不是一個表:)而且似乎沒有 @View/@Table 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
}

休眠事件監聽器

現在,我們必須注冊 3 個事件偵聽器來攔截Post實體的 INSERT、UPDATE 和 DELETE 操作。

我們可以通過以下事件監聽器來做到這一點:

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;
    }
}

可以使用 Hibernate Integrator注冊 3 個事件監聽器:

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記錄也被刪除:

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

GitHub 上提供的代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM