1. Overview
Spring Boot uses an opinionated algorithm to scan for and configure a DataSource. This allows us to easily get a fully-configured DataSource implementation by default.
In addition, Spring Boot automatically configures a lightning-fast connection pool — either HikariCP, Apache Tomcat, or Commons DBCP, in that order, depending on which are on the classpath.
While Spring Boot’s automatic DataSource configuration works very well in most cases, sometimes we’ll need a higher level of control, so we’ll have to set up our own DataSource implementation, hence skipping the automatic configuration process.
In this tutorial, we’ll learn how to configure a DataSource programmatically in Spring Boot.
2. The Maven Dependencies
Creating a DataSource implementation programmatically is straightforward, overall.
To learn how to accomplish this, we’ll implement a simple repository layer, which will perform CRUD operations on some JPA entities.
Let’s take a look at our demo project’s dependencies:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>2.4.1</version> <scope>runtime</scope> </dependency>
As shown above, we’ll use an in-memory H2 database instance to exercise the repository layer. In doing so, we’ll be able to test our programmatically-configured DataSource, without the cost of performing expensive database operations.
In addition, let’s make sure to check the latest version of spring-boot-starter-data-jpa on Maven Central.
3. Configuring a DataSource Programmatically
Now, if we stick with Spring Boot’s automatic DataSource configuration and run our project in its current state, it will just work as expected.
Spring Boot will do all the heavy infrastructure plumbing for us. This includes creating an H2 DataSource implementation, which will be automatically handled by HikariCP, Apache Tomcat, or Commons DBCP, and setting up an in-memory database instance.
Additionally, we won’t even need to create an application.properties file, as Spring Boot will provide some default database settings as well.
As we mentioned before, at times we’ll need a higher level of customization, hence we’ll have to configure programmatically our own DataSource implementation.
The simplest way to accomplish this is by defining a DataSource factory method, and placing it within a class annotated with the @Configuration annotation:
@Configuration public class DataSourceConfig { @Bean public DataSource getDataSource() { DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); dataSourceBuilder.driverClassName("org.h2.Driver"); dataSourceBuilder.url("jdbc:h2:mem:test"); dataSourceBuilder.username("SA"); dataSourceBuilder.password(""); return dataSourceBuilder.build(); } }
In this case, we used the convenience DataSourceBuilder class — a non-fluent version of Joshua Bloch’s builder pattern — to create programmatically our custom DataSource object.
This approach is really nice because the builder makes it easy to configure a DataSource using some common properties. Additionally, it uses the underlying connection pool as well.
4. Externalizing DataSource Configuration with the application.properties File
Of course, it’s also possible to partially externalize our DataSource configuration. For instance, we could define some basic DataSource properties in our factory method:
@Bean public DataSource getDataSource() { DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create(); dataSourceBuilder.username("SA"); dataSourceBuilder.password(""); return dataSourceBuilder.build(); }
And specify a few additional ones in the application.properties file:
spring.datasource.url=jdbc:h2:mem:test spring.datasource.driver-class-name=org.h2.Driver
The properties defined in an external source, such as the above application.properties file or via a class annotated with @ConfigurationProperties, will override the ones defined in the Java API.
It becomes evident that, with this approach, we’ll no longer keep our DataSource configuration settings stored in one single place.
On the other hand, it allows us to keep compile-time and run-time configuration settings nicely separated from each other.
This is really good, as it allows us to easily set a configuration binding point. That way we can include different DataSource settings from other sources, without having to refactor our bean factory methods.
5. Testing the DataSource Configuration
Testing our custom DataSource configuration is very simple. The whole process just boils down to creating a JPA entity, defining a basic repository interface, and testing the repository layer.
5.1. Creating a JPA Entity
Let’s start defining our sample JPA entity class, which will model users:
@Entity @Table(name = "users") public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; private String name; private String email; // standard constructors / setters / getters / toString }
5.2. A Simple Repository Layer
Next, we need to implement a basic repository layer, which allows us to perform CRUD operations on instances of the User entity class defined above.
Since we’re using Spring Data JPA, we don’t have to create our own DAO implementation from scratch. We simply have to extend the CrudRepository interface to get a working repository implementation:
@Repository public interface UserRepository extends CrudRepository<User, Long> {}
5.3. Testing the Repository Layer
Lastly, we need to check that our programmatically-configured DataSource is actually working. We can easily accomplish this with an integration test:
@RunWith(SpringRunner.class) @DataJpaTest public class UserRepositoryIntegrationTest { @Autowired private UserRepository userRepository; @Test public void whenCalledSave_thenCorrectNumberOfUsers() { userRepository.save(new User("Bob", "bob@domain.com")); List<User> users = (List<User>) userRepository.findAll(); assertThat(users.size()).isEqualTo(1); } }
The UserRepositoryIntegrationTest class is pretty self-explanatory. It simply exercises two of the repository interface’s CRUD methods to persist and find entities.
Notice that regardless of whether we decide to programmatically configure our DataSource implementation, or split it into a Java config method and the application.properties file, we should always get a working database connection.
5.4. Running the Sample Application
Finally, we can run our demo application using a standard main() method:
@SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } @Bean public CommandLineRunner run(UserRepository userRepository) throws Exception { return (String[] args) -> { User user1 = new User("John", "john@domain.com"); User user2 = new User("Julie", "julie@domain.com"); userRepository.save(user1); userRepository.save(user2); userRepository.findAll().forEach(user -> System.out.println(user); }; } }
We already tested the repository layer, so we’re sure that our DataSource has been configured successfully. Thus, if we run the sample application, we should see in our console output the list of User entities stored in the database.
6. Conclusion
In this tutorial, we learned how to configure a DataSource implementation programmatically in Spring Boot.
As usual, all the code samples shown in this tutorial are available over on GitHub.