How to create (not switch) Datasource, JdbcTemplate and TransactionManager instance at runtime?

89 views Asked by At

My Spring Boot application doesn't know which database to connect to at the start of the application. It will receive the input in the HTTP request body (that includes dbtype,dbservername etc) at runtime and it should connect to a DB instance based on this input.

For similar questions that I checked on StackOverflow, people have suggested ways to create Datasource instances beforehand and then switch during runtime based on the input. However, I don't want to hardcode configurations for different DBs. I want the application to create the JdbcTemplate, Datasource, and TransactionManager instances at runtime.

Here's what I have tried so far to create the JdbcTemplate and Datasource instance at runtime:

class DBConfig{
    private DataSource getMSSQLDatasource(String servername){
        // create MSSQL datasource using the servername
    }

    private DataSource getDB2Datasource(String servername){
        // create DB2 datasource using the servername
    }

    private JdbcTemplate setJdbcTemplate(String dbtype, String servername){
        return new JdbcTemplate(dbtype.equals("DB2") ? getDB2Datasource(servername) : getMSSQLDatasource(servername));
    }
}

So I can create JdbcTemplate and Datasource at runtime. But I also want to execute some of the queries as a transaction using @Transactional for which I would need a TransactionManager. I am not sure how to configure it dynamically at runtime.

So I have two questions:

  1. Is there a way to configure TransactionManager dynamically (just like I have done with JdbcTemplate)? If so How?
  2. Any suggestions?
1

There are 1 answers

0
Foos On

I tried a workaround and it seems to be working fine so far. Any suggestions would be really helpful.

Created a custom class (DynamicTransactionManager) that implements PlatformTransactionManager:

@Component
class DynamicTransactionManager implements PlatformTransactionManager{
    private DataSource datasource;
    private DataSourceTransactionManager transactionManager;

    public void setDatasource(DataSource datasource){
        this.datasource = datasource;
        this.transactionManager = new DataSourceTransactionManager(datasource);
    }

    @Override
    public TransactionStatus getTransaction (TransactionDefinition definition) throws TransactionException { 
        return transactionManager.getTransaction (definition);
    }

    @Override
    public void commit (TransactionStatus status) throws TransactionException {
        transactionManager.commit(status);
    }

    @Override
    public void rollback(TransactionStatus status) throws TransactionException {
        transactionManager.rollback(status);
    }
}

Now in the DBConfig class, while creating JdbcTemplate, access DynamicTransactionManager's bean and set the DataSource dynamically like this:

class DBConfig{
    private DataSource getMSSQLDatasource(String servername){
        // create MSSQL datasource using the servername
    }

    private DataSource getDB2Datasource(String servername){
        // create DB2 datasource using the servername
    }

    private JdbcTemplate setJdbcTemplate(String dbtype, String servername){
        DataSource datasource = dbtype.equals("DB2") ? getDB2Datasource(servername) : getMSSQLDatasource(servername);
        
        // CONFIGURE TRANSACTION MANAGER
        DynamicTransactionManager transactionManager = applicationContext.getBean(DynamicTransactionManager.class); 
        transactionManager.setDataSource(datasource);
        
        return new JdbcTemplate(datasource);
    }
}

So basically DynamicTransactionManager's bean is getting created at the start of the application (because of @Component) but the transaction manager inside it is not initialized.

At runtime, while creating the JdbcTemplate in the DBConfig, I am extracting the bean of class DynamicTransactionManager from applicationContext and injecting the DataSource into this bean using the setDatasource method.