Spring Boot 內置八種控制數據庫連接方式
環境:SpringBoot3.4.2
1. 簡介
Spring框架管理數據庫連接的核心目標是確保高效、安全且與事務緊密集成地使用這一關鍵資源。它抽象了底層連接獲取和釋放的復雜性。
關鍵在于連接的生命周期管理:Spring提倡在需要時獲取連接,使用后及時釋放回池(或關閉),避免資源泄漏。更重要的是,它實現了連接與當前執行線程的綁定,尤其是在事務上下文中。這確保了同一事務內的多個數據庫操作共享同一個物理連接,從而保證了操作的原子性和一致性(ACID)。
Spring通過其事務管理基礎設施自動協調連接的獲取、綁定、提交/回滾和釋放。開發者通常無需手動處理連接細節,只需關注業務邏輯和聲明式事務(如@Transactional),框架會透明地處理連接的查找、復用和清理,顯著簡化了開發并提高了可靠性和性能。
接下來,我們將介紹Spring內置的8種數據庫連接的控制方式。
2.實戰案例
2.1 使用DataSource
Spring 通過 DataSource 獲取與數據庫的連接。DataSource 是 JDBC 規范的一部分,是一個通用的連接工廠。它允許容器或框架將連接池和事務管理問題從應用程序代碼中隱藏起來。
我們可以使用第三方提供的連接池實現來配置自己的數據源。傳統的選擇包括 Apache Commons DBCP 和 C3P0;而對于現代的 JDBC 連接池,可以考慮使用 HikariCP 及其基于構建器的 API。
DriverManagerDataSource 和 SimpleDriverDataSource 類(包含在 Spring 發行版中)應僅用于測試目的!這些變體不提供連接池功能,在多個連接請求發生時性能較差。
如下是使用HikariCP數據源的配置:
@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() ;
}
}
2.2 使用DataSourceUtils
DataSourceUtils 類是一個便捷且功能強大的輔助類,它提供了一些靜態方法,用于從 JNDI 獲取連接,并在必要時關閉連接。它支持與 DataSourceTransactionManager 綁定線程的 JDBC 連接,同時也支持與 JtaTransactionManager 和 JpaTransactionManager 的綁定。
需要注意的是,JdbcTemplate 在其內部實現中隱含地使用了 DataSourceUtils 來訪問連接。在每一次 JDBC 操作背后,JdbcTemplate 都會利用 DataSourceUtils,從而隱式地參與到正在進行的事務中。
如下使用示例:
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 {
// 如果當前本身就在一個事務上下文中,那么這里獲取的連接將是當前已經綁定到上下文中的Connection對象
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 釋放連接(事務內不會真正關閉)
DataSourceUtils.releaseConnection(conn, dataSource);
}
}
}
2.3 實現SmartDataSource接口
如果你有一個類,它的任務是給應用程序提供與數據庫的連接(就像是一個“數據庫連接供應商”),那么這個類可以考慮實現 SmartDataSource 接口。
SmartDataSource 接口是 DataSource 接口的一個“升級版”。DataSource 接口已經提供了獲取數據庫連接的基本功能,但 SmartDataSource 接口更進一步,它允許使用這個接口的類去詢問:“嘿,數據庫連接供應商,在我用完這個連接之后,你是否希望我把它還給你(也就是關閉它)?”
為什么這個功能有用呢?想象一下,你正在進行一系列的數據庫操作,這些操作都需要用到同一個數據庫連接。如果你每次操作完都關閉連接,然后再重新獲取一個新的連接,那就會很浪費時間,也很低效。但是,如果你知道接下來還要用這個連接,你就可以告訴連接供應商:“我現在不用關閉它,我接下來還要用。”
所以,SmartDataSource 接口就是給數據庫連接供應商提供了一個“智能”的選擇,讓它們可以根據情況決定是否要關閉連接。當你知道你需要重用連接時,這個功能就特別有用,因為它能幫你節省時間和資源。
接口簽名如下:
public interface SmartDataSource extends DataSource {
// 詢問是否要關閉連接
boolean shouldClose(Connection con);
}
上面介紹的DataSourceUtils工具類,其在關閉連接時會判斷是否是SmartDataSource,代碼如下:
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();
}
}
}
2.4 繼承AbstractDataSource
AbstractDataSource 是 Spring 中 DataSource 實現的一個抽象基類。它實現了所有 DataSource 實現中通用的代碼。如果你打算編寫自己的 DataSource 實現,那么你應該擴展 AbstractDataSource 類。
如下代碼實現:
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;
}
}
我們只需要實現獲取Connection連接對象的核心方法即可。
通常我們可以在需要實現自己的數據源或是代理第三方數據源時使用。
2.5 使用SingleConnectionDataSource
SingleConnectionDataSource 類是 SmartDataSource 接口的一個實現,它封裝了一個單一的數據庫連接,并且這個連接在每次使用后不會被關閉。需要注意的是,它并不支持多線程。
如果有任何客戶端代碼在假設存在連接池的情況下調用了 close 方法(例如在使用持久化工具時),你應該將 suppressClose 屬性設置為 true。這個設置會返回一個關閉抑制代理(close-suppressing proxy),它會封裝實際的物理連接。
SingleConnectionDataSource 主要是一個用于測試的類。它通常與簡單的 JNDI 環境結合使用,以便在應用程序服務器之外輕松測試代碼。與 DriverManagerDataSource 相比,SingleConnectionDataSource 會一直重用同一個連接,從而避免了過多物理連接的創建。
如果你用過UReport一款web在線報表設計工具,那你可能知道SingleConnectionDataSource,該報表工具內部就是使用的該數據源。
2.6 使用DriverManagerDataSource
DriverManagerDataSource 類是標準 DataSource 接口的一個實現,它通過 bean 屬性來配置一個普通的 JDBC 驅動,并且每次都會返回一個新的連接。
這個實現對于在 Jakarta EE 容器之外的測試環境和獨立環境非常有用,既可以作為 Spring IoC 容器中的一個 DataSource bean,也可以與簡單的 JNDI 環境結合使用。假設存在連接池的 Connection.close() 調用會關閉連接,因此任何了解 DataSource 的持久化代碼都應該能夠正常工作。然而,即使在測試環境中,使用基于 JavaBean 風格的連接池(例如 commons-dbcp)也非常簡單,幾乎總是比使用 DriverManagerDataSource 更可取。
如下配置示例:
@Bean
DriverManagerDataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource() ;
dataSource.setDriverClassName("org.hsqldb.jdbcDriver") ;
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:") ;
dataSource.setUsername("sa") ;
dataSource.setPassword("") ;
return dataSource ;
}
提醒:注意使用環境。
2.7 使用TransactionAwareDataSourceProxy
TransactionAwareDataSourceProxy 是一個目標 DataSource 的代理類。這個代理類會包裝目標 DataSource,為其添加對 Spring 管理的事務的感知能力。
通常,除了在必須調用現有代碼并傳遞一個標準 JDBC DataSource 接口實現的情況下,很少需要使用這個類。
TransactionAwareDataSourceProxy 是 Spring 提供的數據源代理類,主要用于將非 Spring 管理的 JDBC 數據源包裝為支持 Spring 事務的代理對象。
如下使用示例:
@Configuration
public class LegacyIntegrationConfig {
@Bean
public DataSource realDataSource() {
// 創建原始數據源(如 DBCP、HikariCP)
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 dataSource) {
this.dataSource = dataSource;
}
public void updateData() throws SQLException {
// 如果我們沒有使用代理,這里拿到的Connection一定不是由Spring事務管理的連接
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;
// 此時我們就將遺留的代碼加入到了Spring管理的事務中
// 這樣我們在updateData方法中獲取的Connection才受Spring事務管理
@Transactional
public void update() {
this.legacyDao.updateData() ;
}
}
2.8 使用DataSourceTransactionManager
DataSourceTransactionManager 類是針對單個 JDBC DataSource 的 PlatformTransactionManager 實現。它將從指定的 DataSource 中獲取的 JDBC 連接綁定到當前正在執行的線程,從而可能為每個 DataSource 提供一個線程綁定的連接。
應用程序代碼需要通過 DataSourceUtils.getConnection(DataSource) 來獲取 JDBC 連接,而不是使用 Jarkarta EE 的標準 DataSource.getConnection 方法。
DataSourceTransactionManager 類支持保存點(PROPAGATION_NESTED)、自定義隔離級別以及適當應用為 JDBC 語句查詢超時的超時設置。為了支持后者,應用程序代碼必須使用 JdbcTemplate,或者為每個創建的語句調用 DataSourceUtils.applyTransactionTimeout(..) 方法。
如下代碼使用示例:
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager txManager = new DataSourceTransactionManager();
txManager.setDataSource(dataSource);
txManager.setDefaultTimeout(10); // 默認超時30秒
// 在這里我們可以做更多的配置了
return txManager;
}
這在Spring Boot環境下將會覆蓋系統的默認事務管理器。