Backend Development 12 min read

Master Spring Boot 3 DataSource: 8 Practical Connection Management Techniques

This article presents a comprehensive Spring Boot 3 tutorial that explains how the framework manages database connections, introduces eight built‑in DataSource control methods, and provides detailed code examples for configuring HikariCP, using DataSourceUtils, SmartDataSource, TransactionAwareDataSourceProxy, and transaction managers.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Spring Boot 3 DataSource: 8 Practical Connection Management Techniques

1. Introduction

Spring's core goal for managing database connections is to ensure efficient, safe, and transaction‑aware usage of this critical resource. It abstracts the complexity of acquiring and releasing connections, promotes obtaining a connection only when needed, and releasing it promptly back to the pool.

The framework binds a connection to the current thread, especially within a transaction, so that multiple operations in the same transaction share a single physical connection, guaranteeing atomicity and consistency (ACID). Developers typically rely on declarative transaction annotations such as @Transactional and let Spring handle connection lookup, reuse, and cleanup.

The following sections describe eight built‑in ways Spring controls database connections.

2. Practical Cases

2.1 Using DataSource

Spring obtains connections via a DataSource , a standard JDBC interface that hides pooling and transaction details. Common implementations include Apache Commons DBCP, C3P0, and the modern HikariCP.

<code>@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    HikariDataSource dataSource(DataSourceProperties properties) {
        return (HikariDataSource) DataSourceBuilder.create()
            .type(HikariDataSource.class)
            .driverClassName(properties.getDriverClassName())
            .url(properties.getUrl())
            .username(properties.getUsername())
            .password(properties.getPassword())
            .build();
    }
}
</code>

2.2 Using DataSourceUtils

DataSourceUtils provides static helpers to obtain a connection from JNDI and close it properly. It works with DataSourceTransactionManager , JtaTransactionManager , and JpaTransactionManager . JdbcTemplate internally uses DataSourceUtils to participate in ongoing transactions.

<code>public class UserRepository {
    private final DataSource dataSource;
    public UserRepository(DataSource dataSource) { this.dataSource = dataSource; }
    public void updateUserName(Long userId, String newName) {
        Connection conn = null;
        try {
            conn = DataSourceUtils.getConnection(dataSource);
            try (PreparedStatement ps = conn.prepareStatement(
                    "UPDATE users SET name = ? WHERE id = ?")) {
                ps.setString(1, newName);
                ps.setLong(2, userId);
                ps.executeUpdate();
            }
        } catch (SQLException e) {
            throw new RuntimeException("Update failed", e);
        } finally {
            DataSourceUtils.releaseConnection(conn, dataSource);
        }
    }
}
</code>

2.3 Implementing SmartDataSource

The SmartDataSource interface extends DataSource with a shouldClose(Connection) method, allowing the provider to decide whether a connection should be closed after use.

<code>public interface SmartDataSource extends DataSource {
    // Ask whether to close the connection
    boolean shouldClose(Connection con);
}
</code>

Spring's DataSourceUtils.doCloseConnection checks this flag before closing:

<code>public abstract class DataSourceUtils {
    public static void doCloseConnection(Connection con, @Nullable DataSource dataSource) throws SQLException {
        if (!(dataSource instanceof SmartDataSource smartDataSource) || smartDataSource.shouldClose(con)) {
            con.close();
        }
    }
}
</code>

2.4 Extending AbstractDataSource

When creating a custom DataSource, extend AbstractDataSource and implement the core getConnection methods.

<code>public class Pack extends AbstractDataSource {
    @Override
    public Connection getConnection() throws SQLException { return null; }
    @Override
    public Connection getConnection(String username, String password) throws SQLException { return null; }
}
</code>

2.5 Using SingleConnectionDataSource

SingleConnectionDataSource implements SmartDataSource and reuses a single connection for the lifetime of the bean. It is mainly for testing and does not support multithreading.

2.6 Using DriverManagerDataSource

DriverManagerDataSource creates a new connection for each request and is useful in test or standalone environments.

<code>@Bean
DriverManagerDataSource dataSource() {
    DriverManagerDataSource ds = new DriverManagerDataSource();
    ds.setDriverClassName("org.hsqldb.jdbcDriver");
    ds.setUrl("jdbc:hsqldb:hsql://localhost:");
    ds.setUsername("sa");
    ds.setPassword("");
    return ds;
}
</code>

2.7 Using TransactionAwareDataSourceProxy

This proxy wraps an existing DataSource to add Spring‑transaction awareness, allowing legacy code that expects a plain DataSource to participate in Spring‑managed transactions.

<code>@Configuration
public class LegacyIntegrationConfig {
    @Bean
    public DataSource realDataSource() { return new HikariDataSource(...); }
    @Bean
    public TransactionAwareDataSourceProxy transactionAwareDataSource() {
        return new TransactionAwareDataSourceProxy(realDataSource());
    }
    @Bean
    public PlatformTransactionManager txManager() {
        return new DataSourceTransactionManager(realDataSource());
    }
    @Bean
    public LegacyDao legacyDao() { return new LegacyDao(transactionAwareDataSource()); }
}

public class LegacyDao {
    private final DataSource dataSource;
    public LegacyDao(DataSource ds) { this.dataSource = ds; }
    public void updateData() throws SQLException {
        try (Connection conn = dataSource.getConnection()) {
            conn.prepareStatement("UPDATE t_product x SET x.name = 'xxxooo' WHERE x.id = 2").execute();
        }
    }
}

@Service
public class Service {
    private final LegacyDao legacyDao;
    @Transactional
    public void update() { legacyDao.updateData(); }
}
</code>

2.8 Using DataSourceTransactionManager

DataSourceTransactionManager binds a JDBC connection from a single DataSource to the current thread, supporting nested propagation, custom isolation levels, and transaction timeouts.

<code>@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    DataSourceTransactionManager txManager = new DataSourceTransactionManager();
    txManager.setDataSource(dataSource);
    txManager.setDefaultTimeout(10); // default is 30 seconds
    return txManager;
}
</code>

In a Spring Boot environment, this configuration overrides the default transaction manager.

javaBackend DevelopmentSpring BootTransaction ManagementDataSource
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.