控制数据库连接 (Controlling Database Connections)
本节涵盖以下内容:
- 使用
DataSource - 使用
DataSourceUtils - 实现
SmartDataSource - 扩展
AbstractDataSource - 使用
SingleConnectionDataSource - 使用
DriverManagerDataSource - 使用
TransactionAwareDataSourceProxy - 使用
DataSourceTransactionManager/JdbcTransactionManager
使用 DataSource
Spring 通过 DataSource 获取与数据库的连接。DataSource 是 JDBC 规范的一部分,它是一个通用的连接工厂。它允许容器或框架向应用代码隐藏连接池和事务管理的细节。作为开发者,你不需要了解如何连接数据库的具体细节,那是配置数据源的管理员的责任。在开发和测试代码时,你可能同时扮演这两个角色,但你并不一定要知道生产环境的数据源是如何配置的。
当你使用 Spring 的 JDBC 层时,你可以从 JNDI 获取数据源,或者使用第三方提供的连接池实现来配置你自己的数据源。传统的选择包括 Apache Commons DBCP 和 C3P0(具有 Bean 风格的 DataSource 类);对于现代 JDBC 连接池,请考虑使用具有构建器风格 API 的 HikariCP。
注意
你应该只为了测试目的而使用 DriverManagerDataSource 和 SimpleDriverDataSource 类(包含在 Spring 发行版中)!这些变体不提供连接池,且在有多个连接请求时性能较差。
以下部分将使用 Spring 的 DriverManagerDataSource 实现。稍后将介绍其他几种 DataSource 变体。
要配置 DriverManagerDataSource:
- 像通常获取 JDBC 连接一样,使用
DriverManagerDataSource获取连接。 - 指定 JDBC 驱动的全限定类名,以便
DriverManager可以加载驱动类。 - 提供一个在不同 JDBC 驱动之间变化的 URL(请查看驱动文档以获取正确的值)。
- 提供用户名和密码以连接到数据库。
以下示例展示了如何在 Java、Kotlin 和 XML 中配置 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;
}@Bean
fun dataSource() = DriverManagerDataSource().apply {
setDriverClassName("org.hsqldb.jdbcDriver")
url = "jdbc:hsqldb:hsql://localhost:"
username = "sa"
password = ""
}<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<context:property-placeholder location="jdbc.properties"/>接下来的两个示例展示了 DBCP 和 C3P0 的基本连接和配置。要了解有助于控制连接池特性的更多选项,请参阅各个连接池实现的产品文档。
DBCP 配置示例:
@Bean(destroyMethod = "close")
BasicDataSource dataSource() {
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}@Bean(destroyMethod = "close")
fun dataSource() = BasicDataSource().apply {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:hsql://localhost:"
username = "sa"
password = ""
}<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>C3P0 配置示例:
@Bean(destroyMethod = "close")
ComboPooledDataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("org.hsqldb.jdbcDriver");
dataSource.setJdbcUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUser("sa");
dataSource.setPassword("");
return dataSource;
}@Bean(destroyMethod = "close")
fun dataSource() = ComboPooledDataSource().apply {
driverClass = "org.hsqldb.jdbcDriver"
jdbcUrl = "jdbc:hsqldb:hsql://localhost:"
user = "sa"
password = ""
}<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${jdbc.driverClassName}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>使用 DataSourceUtils
DataSourceUtils 类是一个便捷而强大的辅助类,它提供了 static 方法来从 JNDI 获取连接并在必要时关闭连接。它支持带有 DataSourceTransactionManager 的线程绑定(thread-bound)JDBC Connection,同时也支持 JtaTransactionManager 和 JpaTransactionManager。
请注意,JdbcTemplate 意味着使用 DataSourceUtils 访问连接,它在每个 JDBC 操作背后都会使用它,隐式地参与到正在进行的事务中。
使用 DataSourceTransactionManager / JdbcTransactionManager
DataSourceTransactionManager 类是针对单个 JDBC DataSource 的 PlatformTransactionManager 实现。它将来自指定 DataSource 的 JDBC Connection 绑定到当前执行的线程,可能允许每个 DataSource 有一个线程绑定的 Connection。
应用代码需要通过 DataSourceUtils.getConnection(DataSource) 而不是 Java EE 标准的 DataSource.getConnection 来检索 JDBC Connection。它抛出非检查的 org.springframework.dao 异常,而不是受检的 SQLExceptions。所有框架类(如 JdbcTemplate)都隐式地使用这种策略。如果不与事务管理器一起使用,查找策略的行为与 DataSource.getConnection 完全相同,因此可以在任何情况下使用。
注意
从 5.3 开始,Spring 提供了一个扩展的 JdbcTransactionManager 变体,它在 commit/rollback 时增加了异常转译功能(与 JdbcTemplate 一致)。在 DataSourceTransactionManager 仅抛出 TransactionSystemException 的地方,JdbcTransactionManager 会将数据库锁定失败等转换为对应的 DataAccessException 子类。在需要这些细节的场景中,JdbcTransactionManager 是推荐的选择。
补充教学
1. 为什么“连接池”对生产环境至关重要?
在不使用连接池的情况下,每次请求数据库都要经历:TCP 三次握手 -> 数据库身份验证 -> 分配内存资源 -> 执行 SQL -> 释放资源。 这一过程非常昂贵且耗时。 连接池(Connection Pool) 的核心思想是:预先打开一批连接并保持活跃,谁要用就从池子里取,用完归还。这就像是饭店里的公用餐具,洗干净了放在那里,客人来了直接拿,吃完洗好再放回去,而不是为了吃顿饭现去买一套餐具。 在 Spring 中,除非是写简单的 Demo,否则永远不要在生产环境使用 DriverManagerDataSource,而应首选 HikariCP(Spring Boot 默认集成)。
2. DataSourceUtils 与事务的隐形契约
初学者经常问:为什么我没在代码里传 Connection,JdbcTemplate 就能确保和事务在同一个连接上? 这就是 DataSourceUtils 的功劳。它内部利用了 ThreadLocal 技术。当事务管理器开启事务时,会将连接存入当前线程。DataSourceUtils(以及其调用者 JdbcTemplate)每次都会先检查当前线程是否已经有一个“被事务管理”的连接,如果有就直接用。这就是所谓的“线程绑定”。
3. 选择哪个事务管理器?
DataSourceTransactionManager:经典选择,稳定可靠,适用于单数据源 JDBC 场景。JdbcTransactionManager:增强版。它不仅管理事务,还会像JdbcTemplate一样帮你把 commit 失败时的错误转换为易读的 Spring 异常体系。现代开发推荐首选它。JtaTransactionManager:当你的事务需要跨越多个数据库(分布式事务)时,才需要请出这位“重量级”选手。