Overview

Ebean supports configuring a second DataSource (connection pool) that points to a "Read replica". The "Read Replica" DataSource is set to be readOnly = true and autoCommit=true.

When Ebean is configured with 2 DataSources it will use the "Main" DataSource for explicit transactions and writing data, and will use the "Read Only" DataSource for queries that are executed without an explicit transaction - see Using the Read Replica.

DataSourceBuilder readOnlyUrl

Using DataSourceBuilder we can specify an additional readOnlyUrl which can point to a read replica (e.g. AWS Aurora reader endpoint). When readOnlyUrl is set, the DataSourceBuilder can effectively build 2 DataSources. The difference between these DataSources is the connection url and that the ReadOnly DataSource will have readOnly=true and autoCommit=true.

var dataSourceBuilder =
  DataSourceBuilder.create()
    .username(...)
    .password(...)
    .url(configuration.getJdbcUrl())                 // Writer Endpoint
    .readOnlyUrl(configuration.getReadOnlyJdbcUrl()) // Reader Endpoint
    .applicationName("myAppName");

DatabaseBuilder readOnlyDataSource

If we are not using DataSourceBuilder, we can instead specify a "Read Only" DataSource to use. We can create a DataSource (using say Hikari) and specify it as the readOnlyDataSource.

DataSource writerDataSource = ...;
// should be readOnly=true autoCommit=true
DataSource readOnlyDataSource = ...;

Database.builder()
  ...
  .dataSource(writerDataSource)            // Writer Endpoint
  .readOnlyDataSource(readOnlyDataSource)  // Reader Endpoint
  .build()

Amazon AWS Aurora

Amazon AWS Aurora provides 2 connection endpoints, a Writer and a Reader

The Writer (also known as "Master") endpoint is used for making changes to the database (DML and DDL). The Reader endpoint points to "Read Replica" instances that only support reading data.

  • Writer: Use the writer endpoint for url
  • Reader: Use the reader endpoint for readOnlyUrl
AWS Aurora Endpoints

Using the "Master"

When we have an explicit transaction via @Transactional on a method or using explicitly database.beginTransaction() - then this transaction will use the "Master" DataSource.

When we use database.save() without an explicit transaction, then a transaction will be used implicitly and this will use the "Master" DataSource.

Query.usingMaster()

If we have a query that is executed outside any explicit transaction, and it will fetch data (not a bulk update query or bulk delete query) then by default it will use the "Read Replica".

To force a query to use the "Master/Writer" we use Query.usingMaster() like:

var customers =
  new QCustomer()
    .status.eq(Status.NEW)
    .usingMaster() // Use the "Master/Writer" dataSource
    .findList();

Using the Read Replica

If we have a query that is executed outside any explicit transaction, and it will fetch data (it is not a bulk update query or bulk delete query) then by default it will use the "Read Replica".

// a query executed outside of any explicit transaction
// ... will use the replica
var customers =
  new QCustomer()
    .status.eq(Status.NEW)
    .findList();

@Transaction(readOnly=true)

When a method is annotated with @Transaction(readOnly=true), then this will create a read only transaction using the read only DataSource (which will be pointing to the "Read Replica" instance).

@Transaction(readOnly=true)
void myMethod() {

  // do stuff with a transaction using the
  // Read replica / read only dataSource

}

ReadOnly Database

An Ebean Database can be specified as read-only using DatabaseBuilder.readOnlyDatabase(true) to indicate that this Database can only be used in a read only way.

This means that the DataSource and read-only DataSource are expected to be the same and use readOnly=true and autoCommit=true.

var database = Database.builder()
  .name("mydb")
  .readOnlyDatabase(true)
  ...
  .build();