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.
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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.