Read/Write in different DB Instances | Java | Spring

This Blog will explain how I route my spring boot application Database Calls into different DB instances based on the needs

Sivaram Rasathurai
Javarevisited

--

I have a spring application that is using amazon Aurora DB Cluster as a data source.

Amazon Aurora DB Cluster

Two types of DB instances make up an Aurora DB cluster:

  • Primary DB instance — Supports read and write operations, and performs all of the data modifications to the cluster volume. Each Aurora DB cluster has one primary DB instance.
  • Aurora Replica — Connects to the same storage volume as the primary DB instance and supports only read operations. Each Aurora DB cluster can have up to 15 Aurora Replicas in addition to the primary DB instance. Maintain high availability by locating Aurora Replicas in separate Availability Zones. Aurora automatically fails over to an Aurora Replica in case the primary DB instance becomes unavailable. You can specify the failover priority for Aurora Replicas. Aurora Replicas can also offload read workloads from the primary DB instance.

To use Aurora benefits, we have to route our application DB calls to the relevant instances.

With Spring, we can configure the read queries into one DB instance and other queries into our primary instance.

Spring DB Configuration

Here, we have created two data sources

  1. readWriteConfiguration bean with db.master properties
  2. readOnlyConfiguration with db.slave properties

We created two Datasource but, how to configure these data sources to route the DB connections?

For that, we are going to use the Spring Transaction setup. By default, Spring transactions are read-write, but you can explicitly configure them to be executed in a read-only context via the read-only attribute of the @Transactional annotation.

We are going to create a custom Transaction Manager which manages the transaction life cycle. when a new transaction is created, our ReplicaAwareTransactionManager is a custom transaction manager.

ReplicaAwareTransactionManager

If you look at our ReplicaAwareTransactionManager implementation, you can clearly say, It acts as a proxy for the spring transaction manager. All the work is done by the spring transaction manager. what here, we have done is simply when a get transaction method is called, based on the transaction definition we set up a parameter in TransactionRoutingSource.

public TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException {  
TransactionRoutingDataSource.setReadonlyDataSource(definition != null && definition.isReadOnly());
return wrapped.getTransaction(definition); }

so, when getTransaction is called on TrasactionManager, we are telling TransactionRoutingSource to set its readOnly flag to true. Let’s dig into TransactionRoutingSource. why do we set it like that…

TransactionRoutingSource

Since the transaction is created by the thread, we are going to save the data source information on a thread basis. This TransactionRoutingSource contains the master and slave data sources in a hashmap which is called data sources.

DataSource implementation that routes getConnection() calls to one of the various target DataSources based on a lookup key. The latter is usually (but not necessarily) determined through some thread-bound transaction context. When the application needs to get the connection it will call the lookup key in the transactionRoutingSource. TransactionRoutingSource currentDatasource will be set by the flag which is controlled by the TransactionManager. the currentDatasource will be used to get the connections.

Our Primary Datasource is TransactionRoutingSource which consists of both master and slave datasource.

TransactionRoutingSource will switch the master and slave data sources based on readOnly flag. hence when the application needs to get the db connections it will look like the currentLookUpKey. This readOnlyFlag will be changed by TransactionManager based on the transaction definition. since the default transaction type is readAndWrite, All DB connection calls will go to the Master Database. If we want to direct to slave database, we have to add readOnly=true in @TrasactionAnnotation as below.

@Transaction(readOnly=true)

Lets wrap, with the following image

When a transaction is initiated by the user. It has a transaction definition.

  1. The transaction will be initiated with the getTrasaction call.
  2. When this getTrasaction method is called, the ReplicaAwareTransactionManager will set invoke the TrasactionRoutingSource setReadOnly method. which will change based on the transaction definition readOnly flag.
  3. when setReadOnly method is invoked based on the invocation flag, the currentDatasource will be changed.
    true → slaveDatasource will be selected
    false → masterDatasource will be selected
  4. Now Transaction will be processed. To get the DB connections Application will look currentLookupkey on the data source.
  5. for us, our data source is TransactionRoutingSource.
  6. Our Datasource will give the relevant data source for DB Connections.
  7. The application will connect to the database with the configuration provided by our data source.

--

--