PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Cara membagi transaksi read-only dan read-write dengan JPA dan Hibernate

Perutean transaksi musim semi

Pertama, kita akan membuat DataSourceType Java Enum yang mendefinisikan opsi perutean transaksi kami:

public enum  DataSourceType {
    READ_WRITE,
    READ_ONLY
}

Untuk merutekan transaksi baca-tulis ke node Primer dan transaksi hanya baca ke node Replica, kita dapat mendefinisikan ReadWriteDataSource yang menghubungkan ke node Primer dan ReadOnlyDataSource yang terhubung ke node Replika.

Perutean transaksi baca-tulis dan baca-saja dilakukan oleh AbstractRoutingDataSource Spring abstraksi, yang diimplementasikan oleh TransactionRoutingDatasource , seperti yang diilustrasikan oleh diagram berikut:

TransactionRoutingDataSource sangat mudah diimplementasikan dan terlihat sebagai berikut:

public class TransactionRoutingDataSource 
        extends AbstractRoutingDataSource {

    @Nullable
    @Override
    protected Object determineCurrentLookupKey() {
        return TransactionSynchronizationManager
            .isCurrentTransactionReadOnly() ?
            DataSourceType.READ_ONLY :
            DataSourceType.READ_WRITE;
    }
}

Pada dasarnya, kami memeriksa Spring TransactionSynchronizationManager class yang menyimpan konteks transaksional saat ini untuk memeriksa apakah transaksi Spring yang sedang berjalan adalah hanya-baca atau tidak.

determineCurrentLookupKey metode mengembalikan nilai diskriminator yang akan digunakan untuk memilih DataSource read-write atau read-only JDBC .

Konfigurasi musim semi baca-tulis dan baca-saja JDBC DataSource

DataSource konfigurasi terlihat sebagai berikut:

@Configuration
@ComponentScan(
    basePackages = "com.vladmihalcea.book.hpjp.util.spring.routing"
)
@PropertySource(
    "/META-INF/jdbc-postgresql-replication.properties"
)
public class TransactionRoutingConfiguration 
        extends AbstractJPAConfiguration {

    @Value("${jdbc.url.primary}")
    private String primaryUrl;

    @Value("${jdbc.url.replica}")
    private String replicaUrl;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource readWriteDataSource() {
        PGSimpleDataSource dataSource = new PGSimpleDataSource();
        dataSource.setURL(primaryUrl);
        dataSource.setUser(username);
        dataSource.setPassword(password);
        return connectionPoolDataSource(dataSource);
    }

    @Bean
    public DataSource readOnlyDataSource() {
        PGSimpleDataSource dataSource = new PGSimpleDataSource();
        dataSource.setURL(replicaUrl);
        dataSource.setUser(username);
        dataSource.setPassword(password);
        return connectionPoolDataSource(dataSource);
    }

    @Bean
    public TransactionRoutingDataSource actualDataSource() {
        TransactionRoutingDataSource routingDataSource = 
            new TransactionRoutingDataSource();

        Map<Object, Object> dataSourceMap = new HashMap<>();
        dataSourceMap.put(
            DataSourceType.READ_WRITE, 
            readWriteDataSource()
        );
        dataSourceMap.put(
            DataSourceType.READ_ONLY, 
            readOnlyDataSource()
        );

        routingDataSource.setTargetDataSources(dataSourceMap);
        return routingDataSource;
    }

    @Override
    protected Properties additionalProperties() {
        Properties properties = super.additionalProperties();
        properties.setProperty(
            "hibernate.connection.provider_disables_autocommit",
            Boolean.TRUE.toString()
        );
        return properties;
    }

    @Override
    protected String[] packagesToScan() {
        return new String[]{
            "com.vladmihalcea.book.hpjp.hibernate.transaction.forum"
        };
    }

    @Override
    protected String databaseType() {
        return Database.POSTGRESQL.name().toLowerCase();
    }

    protected HikariConfig hikariConfig(
            DataSource dataSource) {
        HikariConfig hikariConfig = new HikariConfig();
        int cpuCores = Runtime.getRuntime().availableProcessors();
        hikariConfig.setMaximumPoolSize(cpuCores * 4);
        hikariConfig.setDataSource(dataSource);

        hikariConfig.setAutoCommit(false);
        return hikariConfig;
    }

    protected HikariDataSource connectionPoolDataSource(
            DataSource dataSource) {
        return new HikariDataSource(hikariConfig(dataSource));
    }
}

/META-INF/jdbc-postgresql-replication.properties file resource menyediakan konfigurasi untuk JDBC baca-tulis dan baca-saja DataSource komponen:

hibernate.dialect=org.hibernate.dialect.PostgreSQL10Dialect

jdbc.url.primary=jdbc:postgresql://localhost:5432/high_performance_java_persistence
jdbc.url.replica=jdbc:postgresql://localhost:5432/high_performance_java_persistence_replica

jdbc.username=postgres
jdbc.password=admin

jdbc.url.primary properti mendefinisikan URL dari node Primer sedangkan jdbc.url.replica mendefinisikan URL dari node Replika.

readWriteDataSource Komponen pegas mendefinisikan DataSource baca-tulis JDBC sedangkan readOnlyDataSource komponen mendefinisikan DataSource JDBC hanya-baca .

Perhatikan bahwa sumber data baca-tulis dan baca-saja menggunakan HikariCP untuk penyatuan koneksi.

actualDataSource bertindak sebagai fasad untuk sumber data baca-tulis dan baca-saja dan diimplementasikan menggunakan TransactionRoutingDataSource utilitas.

readWriteDataSource terdaftar menggunakan DataSourceType.READ_WRITE kunci dan readOnlyDataSource menggunakan DataSourceType.READ_ONLY kunci.

Jadi, saat menjalankan @Transactional baca-tulis metode, readWriteDataSource akan digunakan saat menjalankan @Transactional(readOnly = true) metode, readOnlyDataSource akan digunakan sebagai gantinya.

Perhatikan bahwa additionalProperties metode mendefinisikan hibernate.connection.provider_disables_autocommit Properti Hibernate, yang saya tambahkan ke Hibernate untuk menunda akuisisi database untuk transaksi RESOURCE_LOCAL JPA.

Tidak hanya itu hibernate.connection.provider_disables_autocommit memungkinkan Anda untuk memanfaatkan koneksi database dengan lebih baik, tetapi ini adalah satu-satunya cara kami dapat membuat contoh ini berfungsi karena, tanpa konfigurasi ini, koneksi diperoleh sebelum memanggil determineCurrentLookupKey metode TransactionRoutingDataSource .

Komponen Spring yang tersisa diperlukan untuk membangun EntityManagerFactory JPA didefinisikan oleh AbstractJPAConfiguration kelas dasar.

Pada dasarnya, actualDataSource selanjutnya dibungkus oleh DataSource-Proxy dan diberikan ke EntityManagerFactory JPA . Anda dapat memeriksa kode sumber di GitHub untuk detail lebih lanjut.

Waktu pengujian

Untuk memeriksa apakah perutean transaksi berfungsi, kita akan mengaktifkan log kueri PostgreSQL dengan mengatur properti berikut di postgresql.conf file konfigurasi:

log_min_duration_statement = 0
log_line_prefix = '[%d] '

log_min_duration_statement pengaturan properti adalah untuk mencatat semua pernyataan PostgreSQL sementara yang kedua menambahkan nama database ke log SQL.

Jadi, saat memanggil newPost dan findAllPostsByTitle metode, seperti ini:

Post post = forumService.newPost(
    "High-Performance Java Persistence",
    "JDBC", "JPA", "Hibernate"
);

List<Post> posts = forumService.findAllPostsByTitle(
    "High-Performance Java Persistence"
);

Kita dapat melihat bahwa PostgreSQL mencatat pesan berikut:

[high_performance_java_persistence] LOG:  execute <unnamed>: 
    BEGIN

[high_performance_java_persistence] DETAIL:  
    parameters: $1 = 'JDBC', $2 = 'JPA', $3 = 'Hibernate'
[high_performance_java_persistence] LOG:  execute <unnamed>: 
    select tag0_.id as id1_4_, tag0_.name as name2_4_ 
    from tag tag0_ where tag0_.name in ($1 , $2 , $3)

[high_performance_java_persistence] LOG:  execute <unnamed>: 
    select nextval ('hibernate_sequence')

[high_performance_java_persistence] DETAIL:  
    parameters: $1 = 'High-Performance Java Persistence', $2 = '4'
[high_performance_java_persistence] LOG:  execute <unnamed>: 
    insert into post (title, id) values ($1, $2)

[high_performance_java_persistence] DETAIL:  
    parameters: $1 = '4', $2 = '1'
[high_performance_java_persistence] LOG:  execute <unnamed>: 
    insert into post_tag (post_id, tag_id) values ($1, $2)

[high_performance_java_persistence] DETAIL:  
    parameters: $1 = '4', $2 = '2'
[high_performance_java_persistence] LOG:  execute <unnamed>: 
    insert into post_tag (post_id, tag_id) values ($1, $2)

[high_performance_java_persistence] DETAIL:  
    parameters: $1 = '4', $2 = '3'
[high_performance_java_persistence] LOG:  execute <unnamed>: 
    insert into post_tag (post_id, tag_id) values ($1, $2)

[high_performance_java_persistence] LOG:  execute S_3: 
    COMMIT
    
[high_performance_java_persistence_replica] LOG:  execute <unnamed>: 
    BEGIN
    
[high_performance_java_persistence_replica] DETAIL:  
    parameters: $1 = 'High-Performance Java Persistence'
[high_performance_java_persistence_replica] LOG:  execute <unnamed>: 
    select post0_.id as id1_0_, post0_.title as title2_0_ 
    from post post0_ where post0_.title=$1

[high_performance_java_persistence_replica] LOG:  execute S_1: 
    COMMIT

Pernyataan log menggunakan high_performance_java_persistence awalan dieksekusi pada node Utama sedangkan yang menggunakan high_performance_java_persistence_replica pada simpul Replika.

Jadi, semuanya bekerja seperti pesona!

Semua kode sumber dapat ditemukan di repositori GitHub Java Persistence High-Persistence saya, sehingga Anda dapat mencobanya juga.

Kesimpulan

Anda perlu memastikan bahwa Anda mengatur ukuran yang tepat untuk kumpulan koneksi Anda karena itu dapat membuat perbedaan besar. Untuk ini, saya sarankan menggunakan Flexy Pool.

Anda harus sangat rajin dan memastikan Anda menandai semua transaksi hanya-baca. Tidak biasa bahwa hanya 10% dari transaksi Anda yang bersifat read-only. Mungkinkah Anda memiliki aplikasi paling banyak menulis atau Anda menggunakan transaksi tulis di mana Anda hanya mengeluarkan pernyataan kueri?

Untuk pemrosesan batch, Anda pasti membutuhkan transaksi read-write, jadi pastikan Anda mengaktifkan batching JDBC, seperti ini:

<property name="hibernate.order_updates" value="true"/>
<property name="hibernate.order_inserts" value="true"/>
<property name="hibernate.jdbc.batch_size" value="25"/>

Untuk pengelompokan, Anda juga dapat menggunakan DataSource yang terpisah yang menggunakan kumpulan koneksi berbeda yang terhubung ke node Utama.

Pastikan ukuran total koneksi Anda dari semua kumpulan koneksi kurang dari jumlah koneksi yang telah dikonfigurasi dengan PostgreSQL.

Setiap tugas batch harus menggunakan transaksi khusus, jadi pastikan Anda menggunakan ukuran batch yang wajar.

Lebih dari itu, Anda ingin menahan kunci dan menyelesaikan transaksi secepat mungkin. Jika prosesor batch menggunakan pekerja pemrosesan bersamaan, pastikan ukuran kumpulan koneksi terkait sama dengan jumlah pekerja, sehingga mereka tidak menunggu orang lain melepaskan koneksi.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Bagaimana cara saya memasukkan beberapa baris dengan benar ke PG dengan node-postgres?

  2. Aksen PostgreSQL + pencarian tidak peka huruf besar-kecil

  3. Postgres/JSON - perbarui semua elemen array

  4. Cara memasukkan dan menghapus data di PostgreSQL

  5. Bagaimana cara mendapatkan nilai dari baris yang dimasukkan terakhir?