Boost Performance with Spring Boot’s Lazy Database Connection Feature
The article explains how Spring Boot 4.1’s new spring.datasource.connection-fetch setting can delay acquiring a physical database connection until a SQL statement is executed, reducing unnecessary connection usage in high‑traffic scenarios and improving overall system concurrency.
When a method annotated with @Transactional is invoked, the configured PlatformTransactionManager starts the transaction. If the annotation specifies a non‑default isolation level or readOnly=true, the manager immediately obtains a physical database connection (via DataSourceUtils#prepareConnectionForTransaction) and configures it, even before the business logic runs. DataSourceTransactionManager always acquires a connection regardless of configuration.
This eager acquisition can be problematic in high‑traffic applications. Scenarios such as many read‑only transactional methods or methods that only hit Hibernate’s second‑level cache may hold a connection for the entire method duration, unnecessarily consuming pool resources and limiting concurrency.
Spring Boot 4.1 introduces the spring.datasource.connection-fetch property with two possible values: eager (default) and lazy. Setting the value to lazy causes Spring Boot to wrap the default DataSource with a LazyConnectionDataSourceProxy, postponing the actual connection request until a real SQL operation is performed.
Default (eager) configuration example
@Entity
@Table(name = "t_stock")
public class Stock {
@Id
private Long id;
private String name;
private Integer amount;
}
public interface StockRepository extends JpaRepository<Stock, Long> {
@Modifying
@Query("update Stock s set s.amount = s.amount - :num where s.id = :stockId")
int deductStock(@Param("stockId") Long stockId, @Param("num") Integer num);
}
@Transactional
public void deduct(Long stockId, Integer deductNum) {
if (this.dataSource instanceof HikariDataSource ds) {
HikariPoolMXBean mbean = ds.getHikariPoolMXBean();
System.err.println("总数: %s, 活动连接: %s, 空闲连接: %s"
.formatted(mbean.getTotalConnections(), mbean.getActiveConnections(), mbean.getIdleConnections()));
}
String result = this.restClient.get().uri("/api/query").retrieve().body(String.class);
System.err.println(result);
this.stockRepository.findById(stockId).ifPresent(stock -> {
if (stock.getAmount() >= deductNum) {
int ret = stockRepository.deductStock(stockId, deductNum);
System.err.println("%s - %s".formatted(Thread.currentThread().getName(),
ret > 0 ? "成功购买" : "库存不足"));
}
});
}Running this code prints a line such as 总数: 10, 活动连接: 1, 空闲连接: 9, showing that a physical connection is taken as soon as the transaction starts, even though the method also performs a remote HTTP call that does not need the database.
Enabling lazy connection fetching
spring:
datasource:
url: jdbc:mysql://localhost:3306/testjpa
<<: *common-ds
connection-fetch: lazySpring Boot creates a bean post‑processor that replaces the original DataSource with a LazyConnectionDataSourceProxy when the property is set to lazy:
@ConditionalOnProperty(name = "spring.datasource.connection-fetch", havingValue = "lazy")
class LazyConnectionDataSourceConfiguration {
@Bean
static LazyConnectionDataSourceBeanPostProcessor lazyConnectionDataSourceBeanPostProcessor(
ObjectProvider<MBeanExporter> mbeanExporter) {
return new LazyConnectionDataSourceBeanPostProcessor(mbeanExporter);
}
static class LazyConnectionDataSourceBeanPostProcessor implements BeanPostProcessor, Ordered {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("dataSource") && bean instanceof DataSource dataSource) {
return new LazyConnectionDataSourceProxy(dataSource);
}
return bean;
}
}
}After switching to lazy mode, the service method is updated to detect the proxy and print pool statistics accordingly:
@Transactional
public void deduct(Long stockId, Integer deductNum) {
if (this.dataSource instanceof HikariDataSource ds) {
HikariPoolMXBean mbean = ds.getHikariPoolMXBean();
System.err.println("总数: %s, 活动连接: %s, 空闲连接: %s"
.formatted(mbean.getTotalConnections(), mbean.getActiveConnections(), mbean.getIdleConnections()));
} else if (this.dataSource instanceof LazyConnectionDataSourceProxy proxy) {
HikariDataSource targetDataSource = (HikariDataSource) proxy.getTargetDataSource();
HikariPoolMXBean mbean = targetDataSource.getHikariPoolMXBean();
System.err.println("Proxy, 总数: %s, 活动连接: %s, 空闲连接: %s"
.formatted(mbean.getTotalConnections(), mbean.getActiveConnections(), mbean.getIdleConnections()));
}
// ... remaining business logic unchanged
}Running the lazy‑configured version prints Proxy, 总数: 10, 活动连接: 0, 空闲连接: 10, demonstrating that no physical connection is taken until the repository method actually executes SQL.
Thus, by enabling spring.datasource.connection-fetch=lazy, applications can avoid unnecessary connection allocation in transactions that may not touch the database, leading to better pool utilization and higher overall throughput.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
