环境: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环境下将会覆盖系统的默认事务管理器。