Using multiple DataSources with Spring Boot

When you want to use multiple DataSources with Spring Boot there are many ways. Lets take a look how Spring Boot configures a single DataSource and then do the same with multiple DataSources.

When you want to use a Datasource with Spring Boot the DataSourceAutoConfiguration will set up some things for you.

It will allow you to execute some initial SQL to initialize your database, support the spring.datasource properties, create an embedded Database for you when one of the supported .jars is found in the classpath, and support connection pooling.

When we want to configure more than one Datasource the auto configuration won't be able to handle this situation since it's only expecting a single DataSource bean. You could add the @Primary annotation to one of your DataSources. But then only one of your DataSources will get auto configured in the way described above.

# The better solution

When we look at the source code of the DataSourceAutoConfіguration we can see how the above features are implemented. We can then implement them ourself.

# The DataSource configuration

First of all we want to be able to configure our multiple DataSources. Spring Boot achives this with the DataSourceProperties configuration bean. All the spring.datasource properties are mapped to this bean. This is achived with the @ConfigurationProperties annotation.

But we need to be able to create this bean multiple times. With different prefixes for our DataSources. We could extend the class multiple times and each time add our own @ConfigurationProperties annotation. But I think there is an better solution.

We create the configuration bean programmatically in one of our configuration classes:

@Bean
@ConfigurationProperties(datasourceOneProperties)
public DataSource dataSourceOne(
		@Qualifier("datasourceOneProperties") DataSourceProperties properties)
{
	return properties.initializeDataSourceBuilder().build();
}

@Bean
@ConfigurationProperties(datasourceTwoProperties)
public DataSource dataSourceTwo(
		@Qualifier("datasourceTwoProperties") DataSourceProperties properties)
{
	return properties.initializeDataSourceBuilder().build();
}

# The DataSourceInitializer

The DataSourceInitializer is more difficult. It is not a public class. We need to copy the source file and create our own DataSourceInitializer. Then we can create the beans programmatically:

@Bean
public DataSourceInitializer dataSourceOneInitializer(
		@Qualifier("datasourceOneProperties") DataSourceProperties properties,
		ApplicationContext applicationContext)
{
	return new DataSourceInitializer(properties, applicationContext);
}

@Bean
public DataSourceInitializer dataSourceTwoInitializer(
		@Qualifier("datasourceTwoProperties") DataSourceProperties properties,
		ApplicationContext applicationContext)
{
	return new DataSourceInitializer(properties, applicationContext);
}

# The actual DataSource

The Autoconfiguration uses a multitude of conditions to decide which Configuration to use to create the actual DataSource. The easiest to understand is the EmbeddedDatabaseConfiguration. It it will only apply if no DataSource is defined and no PooledDataSource is available. Take a look at the DataSourceAutoConfiguration class to see how these checks are implemented.

In the case that you want to create your own DataSource for an embedded Database the code would look like this:

private EmbeddedDatabase databaseOne;
private EmbeddedDatabase databaseTwo;

@Bean
public EmbeddedDatabase dataSourceOne(
		@Qualifier("datasourceOneProperties") DataSourceProperties properties,
		ClassLoader classLoader)
{
	EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
			.setType(EmbeddedDatabaseConnection.get(classLoader).getType());
	databaseOne = builder.setName(properties.getName())
			.generateUniqueName(properties.isGenerateUniqueName()).build();
	return databaseOne;
}

@Bean
public EmbeddedDatabase dataSourceTwo(
		@Qualifier("datasourceTwoProperties") DataSourceProperties properties,
		ClassLoader classLoader)
{
	EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder()
			.setType(EmbeddedDatabaseConnection.get(classLoader).getType());
	databaseOne = builder.setName(properties.getName())
			.generateUniqueName(properties.isGenerateUniqueName()).build();
	return databaseOne;
}

@PreDestroy
public void shutdownDatabases() {
	databaseOne.shutdown();
	databaseTwo.shutdown();
}

But in most cases you don't want an embedded database. In these cases you can let the DataSourceBuilder create an DataSource for you:

@Bean
@ConfigurationProperties(datasourceOneProperties)
public DataSource dataSourceOne(
			@Qualifier("datasourceOneProperties") DataSourceProperties properties)
{
	return properties.initializeDataSourceBuilder().build();
}

@Bean
@ConfigurationProperties(datasourceTwoProperties)
public DataSource dataSourceTwo(
			@Qualifier("datasourceTwoProperties") DataSourceProperties properties)
{
	return properties.initializeDataSourceBuilder().build();
}

# The last touch

We now have two DataSources using the Tomcat connection pool which can be configured under two different prefixes. But we have a problem. The DataSourceAutoConfiguration still wants to create its own DataSourceInitializer bean. And when you are using the Tomcat connection pool also the TomcatDataSourceJmxConfiguration. This will fail because spring does not know which DataSource to use.

Again we could use the @Primary annotation. But since we decided we want to take DataSource creation in our own hands its best to just disable the autoconfiguration of DataSources:

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class App {
	// stuff
}

Contact

direkt gruppe GmbH
Tel.: +49 04 88155-0
info@direkt-gruppe.de

Hamburg

Griegstraße 75, H. 26
22763 Hamburg
Find us

Cologne

Im Mediapark 6b
50670 Cologne
Find us

Munich

Landbaubogen 1
81373 Munich
Find us

Paderborn

Technologiepark 9
33100 Paderborn
Find us