Spring + jOOQ - transactional routing to read and write db's

Viewed 49

We use Spring and jOOQ and at present read/write from primary DB. We do have replica DB's and are trying to have non critical reads (like from quartz jobs, pure read api's, etc.) to go to read.

I referred to https://vladmihalcea.com/read-write-read-only-transaction-routing-spring/ and understand that we need a transactional routing layer introduced which would point one way or the other depending on


    @Transactional (readonly = true) 

However, this doesn't seem to be working in our case, wherein all routing seems to be happening to primary db itself.

Here's the code configurations we use for primary


@Bean(name = "primaryTransactionAwareDataSourceProxy")
  public TransactionAwareDataSourceProxy primaryTransactionAwareDataSource(
      @Qualifier("primaryDataSource") DataSource primaryDataSource) {
    return new TransactionAwareDataSourceProxy(primaryDataSource);
  }

  @Bean(name = "xyz")
  public DataSourceConnectionProvider primaryConnectionProvider(
      @Qualifier("primaryTransactionAwareDataSourceProxy") TransactionAwareDataSourceProxy datasource) {
    return new DataSourceConnectionProvider(datasource);
  }

  @Bean(name = "primaryDsl")
  @Primary
  public DefaultDSLContext primaryDsl(
      @Qualifier("primaryConfiguration") DefaultConfiguration configuration) {
    return new DefaultDSLContext(configuration);
  }

  @Bean(name = "primaryConfiguration")
  public DefaultConfiguration primaryConfiguration(
      @Qualifier("primaryDatabaseProperties") DatabaseProperties props,
      @Qualifier("xyz") DataSourceConnectionProvider provider) {
    DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
    jooqConfiguration.set(provider);
    jooqConfiguration.set(SQLDialect.MYSQL);
    Settings settings = new Settings()
        .withRenderMapping(new RenderMapping()
            .withSchemata(new MappedSchema()
                .withInput(<name>)
                .withOutput(props.getName())));

    jooqConfiguration.set(settings);
    return jooqConfiguration;
  }

and for replica we have along similar lines

@Bean(name = "replicaTransactionAwareDataSourceProxy")
  public TransactionAwareDataSourceProxy replicaTransactionAwareDataSource(
      @Qualifier("replicaDataSource") DataSource replicaDataSource) {
    return new TransactionAwareDataSourceProxy(replicaDataSource);
  }

  @Bean(name = "xyz")
  public DataSourceConnectionProvider replicaConnectionProvider(
      @Qualifier("replicaTransactionAwareDataSourceProxy") TransactionAwareDataSourceProxy datasource) {
    return new DataSourceConnectionProvider(datasource);
  }

  @Bean(name = "replicaDsl")
  public DefaultDSLContext replicaDsl(
      @Qualifier("replicaConfiguration") DefaultConfiguration configuration) {
    return new DefaultDSLContext(configuration);
  }

  @Bean(name = "replicaConfiguration")
  public DefaultConfiguration replicaConfiguration(
      @Qualifier("replicaDatabaseProperties") DatabaseProperties props,
      @Qualifier("xyz") DataSourceConnectionProvider provider) {
    DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
    jooqConfiguration.set(provider);
    jooqConfiguration.set(SQLDialect.MYSQL);
    Settings settings = new Settings()
        .withRenderMapping(new RenderMapping()
            .withSchemata(new MappedSchema()
                .withInput(<name>)
                .withOutput(props.getName())));

    jooqConfiguration.set(settings);
    return jooqConfiguration;
  }

The data sources are configured as


  @Bean(name = "primaryDatabaseProperties")
  @ConfigurationProperties(prefix = "database")
  public DatabaseProperties primaryDatabaseProperties() {
    return new DatabaseProperties ();
  }

  @Bean(name = "replicaDatabaseProperties")
  @ConfigurationProperties(prefix = "database.replica")
  public DatabaseProperties replicaDatabaseProperties() {
    return new DatabaseProperties ();
  }

  @Bean(name = "primaryDataSource")
  public DataSource primaryDataSource(
      @Qualifier("primaryDatabaseProperties") DatabaseProperties props) {
    return dataSource(props);
  }

  @Bean(name = "replicaDataSource")
  public DataSource replicaDataSource(
      @Qualifier("replicaDatabaseProperties") DatabaseProperties props) {
    return dataSource(props);
  }

And transactional routing data source defined as

  @Bean
  public TransactionRoutingDataSource actualDataSource(
      @Qualifier("primaryTransactionAwareDataSourceProxy") DataSource primaryDataSource,
      @Qualifier("replicaTransactionAwareDataSourceProxy") DataSource replicaDataSource) {
    TransactionRoutingDataSource routingDataSource = new TransactionRoutingDataSource();

    Map<Object, Object> dataSourceMap = new HashMap<>() {{
      put(DataSourceType.READ_WRITE, primaryDataSource);
      put(DataSourceType.READ_ONLY, replicaDataSource);
    }};

    routingDataSource.setTargetDataSources(dataSourceMap);
    return routingDataSource;
  }

I have an implementation of AbstractRoutingDataSource defined to do the conditional check of if the current transaction is ready only and route to correct datasource accordingly

public class TransactionRoutingDataSource extends AbstractRoutingDataSource {

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

And I use annotations to indicate the necessary routing in the service layer

 @Override
    @Transactional(readOnly = true)
    public <return_type> <method_name>() {}

Any pointers as to what I am missing to have jOOQ route correctly to read db?

0 Answers
Related