Spring Web Flux | Master - Slave - Pool Configuration

Devashish Taneja

--

In this article, we will see how to enable the master-slave config in Spring webflux. The same principle can be applied for connecting to multiple data sources in the microservice. For the sake of this article, we will be using Spring webflux 3.x, Java 17 and MySQL.

Database Setup

We will start by defining a Schema and Entity model

CREATE TABLE customer (id SERIAL PRIMARY KEY, first_name VARCHAR(255), last_name VARCHAR(255));

INSERT INTO customer(first_name, last_name)
VALUES("Devashish", "Taneja");
package com.examples.r2dbc_master_slave.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;

@Setter
@Getter
@AllArgsConstructor
public class Customer {
@Id
private Long id;
@Column(value = "first_name")
private String firstName;
@Column(value = "last_name")
private String lastName;

public String toString() {
return "\nUser \nId:" + id + "\nName : " + firstName + " " + lastName;
}
}

Adding Spring Dependencies

Add spring-boot-starter-data-r2dbc, mysql connector and io.asyncer:r2dbc-mysql in build.gradle.

implementation 'org.springframework.boot:spring-boot-starter-data-r2dbc'
runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'io.asyncer:r2dbc-mysql'

R2dbc Autoconfiguration

spring.r2dbc.url=r2dbc:mysql://root:@localhost:3306/example
spring.r2dbc.pool.enabled=true

Doing the above will automatically create an R2dbcEntityTemplate, which can be used in the application like below:-

@SpringBootApplication
@Slf4j
public class R2dbcMasterSlaveApplication {

public static void main(String[] args) {
SpringApplication.run(R2dbcMasterSlaveApplication.class, args);
}

@Bean
public CommandLineRunner demo(R2dbcEntityTemplate entityTemplate) {
return args -> entityTemplate.select(Query.empty(), Customer.class)
.doOnNext(customer -> log.info(customer.toString()))
.blockLast();
}
}

Now, the good part enabling Multiple R2dbc connections for setting up master and slave config.

Setting up multiple R2dbc Connections

  1. Define database URLs in application.properties file
# Writer Database URL
spring.r2dbc.master.url=r2dbc:mysql://user:password@test-db.cluster-cey7aslike.us-east-1.rds.amazonaws.com/example

# Reader Database URL
spring.r2dbc.slave.url=r2dbc:mysql://user:password@test-db.cluster-ro-cey7aslike.us-east-1.rds.amazonaws.com/example

2. Override existing Connection Factory — The below snippet will generate a new connection factory which overrides the default


@Configuration
public class DatabaseConfig {
@Value("${spring.r2dbc.slave.url}")
private String slaveDbURL;

@Primary
@Bean(name = "readerConnectionFactory")
public ConnectionFactory readerConnectionFactory() {
return ConnectionFactories.get(slaveDbURL);
}

@Primary
@Bean(name = "readerR2dbcEntityTemplate")
public R2dbcEntityTemplate readerR2dbcEntityTemplate(@Qualifier(value = "readerConnectionFactory") ConnectionFactory connectionFactory) {
return new R2dbcEntityTemplate(connectionFactory);
}
}

3. (Optional) Enable collection pooling


public ConnectionFactory readerConnectionFactory() {
ConnectionFactory connectionFactory = ConnectionFactories.get(slaveDbURL);
ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory).build();
return new ConnectionPool(configuration);
}

4. Enabling both master and slave database configuration

@Configuration
public class DatabaseConfig {
@Value("${spring.r2dbc.master.url}")
private String masterDbURL;

@Value("${spring.r2dbc.slave.url}")
private String slaveDbURL;

@Bean(name = "readerConnectionFactory")
@Primary
public ConnectionFactory readerConnectionFactory() {
return getConnectionPool(slaveDbURL);
}

@Bean(name = "writerConnectionFactory")
public ConnectionFactory primaryConnectionFactory() {
return getConnectionPool(masterDbURL);
}

@Bean(name = "readerR2dbcEntityTemplate")
@Primary
public R2dbcEntityTemplate readerR2dbcEntityTemplate(@Qualifier(value = "readerConnectionFactory") ConnectionFactory connectionFactory) {
return new R2dbcEntityTemplate(connectionFactory);
}

@Bean(name = "writerR2dbcEntityTemplate")
public R2dbcEntityTemplate writerR2dbcEntityTemplate(@Qualifier(value = "writerConnectionFactory") ConnectionFactory connectionFactory) {
return new R2dbcEntityTemplate(connectionFactory);
}

private ConnectionPool getConnectionPool(String url) {
ConnectionFactory connectionFactory = ConnectionFactories.get(url);
ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory).build();
return new ConnectionPool(configuration);
}

}

5. Testing it out ;)

@Bean
public CommandLineRunner demo(R2dbcEntityTemplate readerEntityTemplate, @Qualifier(value = "writerR2dbcEntityTemplate") R2dbcEntityTemplate writerEntityTemplate) {
return args -> {
readerEntityTemplate.select(Query.empty(), Customer.class)
.doOnNext(customer -> log.info(customer.toString()))
.blockLast();

writerEntityTemplate.insert(Customer.class)
.into("customer")
.using(new Customer(null, "fistName", "lastName"))
.doOnNext(customer -> log.info(customer.toString()))
.block();
};
}

Additional

To enable query tracing in logs, add this to application.properties

logging.level.org.springframework.r2dbc=TRACE

Feel free to check the code for more details on Github

References

https://spring.io/guides/gs/accessing-data-r2dbc/

--

--

No responses yet

Write a response