Skip to content

控制数据库连接 (Controlling Database Connections)

本节涵盖以下内容:


使用 DataSource

Spring 通过 DataSource 获取与数据库的连接。DataSource 是 JDBC 规范的一部分,它是一个通用的连接工厂。它允许容器或框架向应用代码隐藏连接池和事务管理的细节。作为开发者,你不需要了解如何连接数据库的具体细节,那是配置数据源的管理员的责任。在开发和测试代码时,你可能同时扮演这两个角色,但你并不一定要知道生产环境的数据源是如何配置的。

当你使用 Spring 的 JDBC 层时,你可以从 JNDI 获取数据源,或者使用第三方提供的连接池实现来配置你自己的数据源。传统的选择包括 Apache Commons DBCP 和 C3P0(具有 Bean 风格的 DataSource 类);对于现代 JDBC 连接池,请考虑使用具有构建器风格 API 的 HikariCP。

注意

你应该只为了测试目的而使用 DriverManagerDataSourceSimpleDriverDataSource 类(包含在 Spring 发行版中)!这些变体不提供连接池,且在有多个连接请求时性能较差。

以下部分将使用 Spring 的 DriverManagerDataSource 实现。稍后将介绍其他几种 DataSource 变体。

要配置 DriverManagerDataSource

  1. 像通常获取 JDBC 连接一样,使用 DriverManagerDataSource 获取连接。
  2. 指定 JDBC 驱动的全限定类名,以便 DriverManager 可以加载驱动类。
  3. 提供一个在不同 JDBC 驱动之间变化的 URL(请查看驱动文档以获取正确的值)。
  4. 提供用户名和密码以连接到数据库。

以下示例展示了如何在 Java、Kotlin 和 XML 中配置 DriverManagerDataSource

java
@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;
}
kotlin
@Bean
fun dataSource() = DriverManagerDataSource().apply {
	setDriverClassName("org.hsqldb.jdbcDriver")
	url = "jdbc:hsqldb:hsql://localhost:"
	username = "sa"
	password = ""
}
xml
<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 配置示例:

java
@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;
}
kotlin
@Bean(destroyMethod = "close")
fun dataSource() = BasicDataSource().apply {
	driverClassName = "org.hsqldb.jdbcDriver"
	url = "jdbc:hsqldb:hsql://localhost:"
	username = "sa"
	password = ""
}
xml
<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 配置示例:

java
@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;
}
kotlin
@Bean(destroyMethod = "close")
fun dataSource() = ComboPooledDataSource().apply {
	driverClass = "org.hsqldb.jdbcDriver"
	jdbcUrl = "jdbc:hsqldb:hsql://localhost:"
	user = "sa"
	password = ""
}
xml
<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,同时也支持 JtaTransactionManagerJpaTransactionManager

请注意,JdbcTemplate 意味着使用 DataSourceUtils 访问连接,它在每个 JDBC 操作背后都会使用它,隐式地参与到正在进行的事务中。

使用 DataSourceTransactionManager / JdbcTransactionManager

DataSourceTransactionManager 类是针对单个 JDBC DataSourcePlatformTransactionManager 实现。它将来自指定 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 与事务的隐形契约

初学者经常问:为什么我没在代码里传 ConnectionJdbcTemplate 就能确保和事务在同一个连接上? 这就是 DataSourceUtils 的功劳。它内部利用了 ThreadLocal 技术。当事务管理器开启事务时,会将连接存入当前线程。DataSourceUtils(以及其调用者 JdbcTemplate)每次都会先检查当前线程是否已经有一个“被事务管理”的连接,如果有就直接用。这就是所谓的“线程绑定”。

3. 选择哪个事务管理器?

  • DataSourceTransactionManager:经典选择,稳定可靠,适用于单数据源 JDBC 场景。
  • JdbcTransactionManager:增强版。它不仅管理事务,还会像 JdbcTemplate 一样帮你把 commit 失败时的错误转换为易读的 Spring 异常体系。现代开发推荐首选它。
  • JtaTransactionManager:当你的事务需要跨越多个数据库(分布式事务)时,才需要请出这位“重量级”选手。

Based on Spring Framework.