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 HikariCPApache 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 work just 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, so we’ll have to programmatically configure 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 convenient DataSourceBuilder class, a non-fluent version of Joshua Bloch’s builder pattern, to programmatically create our custom DataSource object.

This approach is really nice because the builder makes it easy to configure a DataSource using some common properties. 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(); 
}

Then we can 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 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 by 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", "[email protected]"));
        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", "[email protected]");
            User user2 = new User("Julie", "[email protected]");
            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 article, we learned how to configure a DataSource implementation programmatically in Spring Boot.

As usual, all the code samples shown in this article are available over on GitHub.