sql >> Database teknologi >  >> RDS >> PostgreSQL

Sådan opdeles skrivebeskyttet og læse-skrive-transaktioner med JPA og Hibernate

Forårstransaktionsruting

Først vil vi oprette en DataSourceType Java Enum, der definerer vores muligheder for transaktionsrouting:

public enum  DataSourceType {
    READ_WRITE,
    READ_ONLY
}

For at dirigere læse-skrive-transaktionerne til den primære node og skrivebeskyttede transaktioner til replika-noden kan vi definere en ReadWriteDataSource der forbinder til den primære node og en ReadOnlyDataSource der forbinder til replika-noden.

Read-write og read-only transaktionsrouting udføres af Spring AbstractRoutingDataSource abstraktion, som er implementeret af TransactionRoutingDatasource , som illustreret af følgende diagram:

TransactionRoutingDataSource er meget nem at implementere og ser ud som følger:

public class TransactionRoutingDataSource 
        extends AbstractRoutingDataSource {

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

Grundlæggende inspicerer vi Spring TransactionSynchronizationManager klasse, der gemmer den aktuelle transaktionskontekst for at kontrollere, om den aktuelt kørende Spring-transaktion er skrivebeskyttet eller ej.

determineCurrentLookupKey metoden returnerer diskriminatorværdien, der vil blive brugt til at vælge enten read-write eller read-only JDBC DataSource .

Forår læse-skrive og skrivebeskyttet JDBC DataSource-konfiguration

DataSource konfigurationen ser ud som følger:

@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 ressourcefil giver konfigurationen for læse-skrive- og skrivebeskyttet JDBC DataSource komponenter:

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 egenskaben definerer URL'en for den primære node, mens jdbc.url.replica definerer URL'en for replika-noden.

readWriteDataSource Spring-komponent definerer læse-skrive JDBC DataSource mens readOnlyDataSource komponent definerer den skrivebeskyttede JDBC DataSource .

Bemærk, at både læse-skrive- og skrivebeskyttet datakilder bruger HikariCP til forbindelsespooling.

actualDataSource fungerer som en facade for læse-skrive- og skrivebeskyttede datakilder og implementeres ved hjælp af TransactionRoutingDataSource værktøj.

readWriteDataSource er registreret ved hjælp af DataSourceType.READ_WRITE nøglen og readOnlyDataSource ved hjælp af DataSourceType.READ_ONLY nøgle.

Så når du udfører en read-write @Transactional metode, readWriteDataSource vil blive brugt, mens der udføres en @Transactional(readOnly = true) metoden, readOnlyDataSource vil blive brugt i stedet.

Bemærk, at additionalProperties metoden definerer hibernate.connection.provider_disables_autocommit Hibernate-egenskab, som jeg føjede til Hibernate for at udsætte databaseanskaffelsen for RESOURCE_LOCAL JPA-transaktioner.

Ikke kun at hibernate.connection.provider_disables_autocommit giver dig mulighed for at gøre bedre brug af databaseforbindelser, men det er den eneste måde, vi kan få dette eksempel til at fungere, da forbindelsen, uden denne konfiguration, opnås, før du kalder determineCurrentLookupKey metode TransactionRoutingDataSource .

De resterende Spring-komponenter, der er nødvendige for at bygge JPA EntityManagerFactory er defineret af AbstractJPAConfiguration basisklasse.

Grundlæggende er actualDataSource er yderligere pakket ind af DataSource-Proxy og leveret til JPA EntityManagerFactory . Du kan tjekke kildekoden på GitHub for flere detaljer.

Testtid

For at kontrollere, om transaktionsroutingen virker, vil vi aktivere PostgreSQL-forespørgselsloggen ved at indstille følgende egenskaber i postgresql.conf konfigurationsfil:

log_min_duration_statement = 0
log_line_prefix = '[%d] '

log_min_duration_statement egenskabsindstillingen er til at logge alle PostgreSQL-sætninger, mens den anden tilføjer databasenavnet til SQL-loggen.

Så når du ringer til newPost og findAllPostsByTitle metoder som denne:

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

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

Vi kan se, at PostgreSQL logger følgende meddelelser:

[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

Logsætningerne ved hjælp af high_performance_java_persistence præfiks blev udført på den primære node, mens dem, der brugte high_performance_java_persistence_replica på replika-noden.

Så alt fungerer som en charme!

Al kildekoden kan findes i mit High-Performance Java Persistence GitHub-lager, så du kan også prøve det.

Konklusion

Du skal sørge for at indstille den rigtige størrelse til dine tilslutningsbassiner, for det kan gøre en kæmpe forskel. Til dette anbefaler jeg at bruge Flexy Pool.

Du skal være meget omhyggelig og sørge for at markere alle skrivebeskyttede transaktioner i overensstemmelse hermed. Det er usædvanligt, at kun 10 % af dine transaktioner er skrivebeskyttede. Kan det være, at du har sådan en applikation, hvor du skriver mest, eller du bruger skrivetransaktioner, hvor du kun udsteder forespørgselserklæringer?

Til batchbehandling har du helt sikkert brug for læse-skrive-transaktioner, så sørg for at aktivere JDBC-batching, sådan her:

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

Til batching kan du også bruge en separat DataSource der bruger en anden forbindelsespulje, der forbinder til den primære node.

Bare sørg for, at din samlede forbindelsesstørrelse for alle forbindelsespuljer er mindre end antallet af forbindelser, PostgreSQL er blevet konfigureret med.

Hvert batchjob skal bruge en dedikeret transaktion, så sørg for at bruge en rimelig batchstørrelse.

Mere, du ønsker at holde låse og afslutte transaktioner så hurtigt som muligt. Hvis batchprocessoren bruger samtidige behandlingsarbejdere, skal du sørge for, at den tilknyttede forbindelsespuljestørrelse er lig med antallet af arbejdere, så de ikke venter på, at andre frigiver forbindelser.



  1. Oracle-klient- og netværkskomponenter blev ikke fundet

  2. Opdateringer fra Microsoft Access-teamet (juni 2017)

  3. Sådan opretter du DMZ til EBS R12

  4. ScaleGrid DBaaS shortlistet til Cloud Excellence Awards 2018