控制数据库连接
本节涵盖:
使用 DataSource
Spring通过DataSource与数据库建立连接。DataSource是JDBC规范的一部分,它是一种通用的连接工厂。它允许容器或框架将连接池管理和事务管理的相关细节隐藏在应用程序代码之外。作为开发者,你不需要了解如何与数据库连接的详细信息。这些职责由负责设置DataSource的管理员来承担。在开发和测试代码的过程中,你很可能会同时扮演开发者和管理员的角色,但你不一定需要知道生产环境中的数据源是如何配置的。
当你使用Spring的JDBC层时,你可以从JNDI获取数据源,或者使用第三方提供的连接池实现来配置自己的数据源。传统的选择有Apache Commons DBCP和C3P0,它们都提供bean风格的DataSource类;而对于现代的JDBC连接池,可以考虑使用HikariCP,它提供了builder风格的API。
您应该仅将DriverManagerDataSource和SimpleDriverDataSource类(包含在Spring发行版中)用于测试目的!这些实现不提供连接池功能,在同时有多个连接请求时性能表现较差。
以下部分使用了Spring的DriverManagerDataSource实现。后面还会介绍其他几种DataSource的变体。
要配置一个DriverManagerDataSource:
-
如同获取JDBC连接一样,使用
DriverManagerDataSource来建立连接。 -
指定JDBC驱动程序的完全限定类名,以便
DriverManager能够加载该驱动程序类。 -
提供一个因JDBC驱动程序而异的URL(请参考相应驱动程序的文档以获取正确的URL值)。
-
提供用户名和密码来连接数据库。
以下示例展示了如何配置DriverManagerDataSource:
- Java
- Kotlin
- Xml
@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的配置:
- Java
- Kotlin
- Xml
@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>
<context:property-placeholder location="jdbc.properties"/>
以下示例展示了C3P0的配置:
- Java
- Kotlin
- Xml
@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>
<context:property-placeholder location="jdbc.properties"/>
使用 DataSourceUtils
DataSourceUtils 类是一个方便且强大的辅助类,它提供了 static 方法来从 JNDI 中获取连接,并在必要时关闭连接。该类支持与 DataSourceTransactionManager 绑定的 JDBC Connection,同时也支持与 JtaTransactionManager 和 JpaTransactionManager 绑定的连接。
请注意,JdbcTemplate 暗示了通过 DataSourceUtils 进行连接访问,在每次 JDBC 操作之后都会使用它,从而隐式地参与到一个正在进行的事务中。
实现 SmartDataSource
SmartDataSource接口应由能够提供与关系型数据库连接的类实现。它扩展了DataSource接口,使得使用它的类可以查询在特定操作后是否需要关闭连接。当你知道需要重用连接时,这种用法是高效的。
扩展 AbstractDataSource
AbstractDataSource 是 Spring 中 DataSource 实现的抽象基类。它实现了所有 DataSource 实现共有的代码。如果您要编写自己的 DataSource 实现,应该继承 AbstractDataSource 类。
使用 SingleConnectionDataSource
SingleConnectionDataSource 类是 SmartDataSource 接口的实现,它封装了一个在每次使用后不会被关闭的单一 Connection(连接)。该类不支持多线程操作。
如果任何客户端代码在假设连接为池化连接的情况下调用close(例如在使用持久化工具时),你应该将suppressClose属性设置为true。此设置会返回一个抑制关闭操作的代理对象,该代理对象会封装实际的连接。请注意,你不能再将这个代理对象转换为原始的Oracle Connection或类似的对象了。
SingleConnectionDataSource主要是一个测试类。它通常用于在与简单的JNDI环境结合使用时,方便地测试应用程序服务器外的代码。与DriverManagerDataSource不同,它会一直重用同一个连接,从而避免过多地创建物理连接。
使用 DriverManagerDataSource
DriverManagerDataSource 类是标准 DataSource 接口的实现,它通过 Bean 属性配置普通的 JDBC 驱动程序,并且每次都会返回一个新的 Connection。
这种实现对于Jakarta EE容器之外的测试和独立环境非常有用,既可以作为Spring IoC容器中的DataSource bean使用,也可以与简单的JNDI环境结合使用。假设Connection.close()会关闭连接,那么任何依赖DataSource的持久化代码都应该可以正常工作。然而,在测试环境中,使用JavaBean风格的连接池(如commons-dbcp)也极其简单,因此几乎总是更倾向于使用这样的连接池,而不是DriverManagerDataSource。
使用 TransactionAwareDataSourceProxy
TransactionAwareDataSourceProxy 是一个目标 DataSource 的代理。该代理包装了目标 DataSource,以添加对 Spring 管理的事务的支持。在这方面,它类似于 Jakarta EE 服务器提供的事务性 JNDI DataSource。
除了在必须调用现有代码并传递标准的JDBC DataSource接口实现时,很少有使用这个类的必要。在这种情况下,你仍然可以使这段代码可用,同时让这段代码参与Spring管理的事务。通常,更推荐使用更高层次的资源管理抽象(如JdbcTemplate或DataSourceUtils)来编写新的代码。
有关更多详细信息,请参阅TransactionAwareDataSourceProxy的Javadoc。
使用 DataSourceTransactionManager / JdbcTransactionManager
DataSourceTransactionManager 类是针对单个 JDBC DataSource 的 PlatformTransactionManager 实现。它将来自指定 DataSource 的 JDBC Connection 绑定到当前执行的线程上,从而每个 DataSource 可能只允许有一个与线程绑定的 Connection。
需要应用程序代码通过 DataSourceUtils.getConnection(DataSource) 来获取 JDBC Connection,而不是使用 Java EE 的标准 DataSource.getConnection。该方法会抛出未检查的 org.springframework.dao 异常,而不是检查过的 SQLException。所有框架类(如 JdbcTemplate)都会隐式地使用这种策略。如果不与事务管理器一起使用,这种获取方式的行为与 DataSource.getConnection 完全相同,因此在任何情况下都可以使用。
DataSourceTransactionManager 类支持保存点(PROPAGATION_NESTED)、自定义隔离级别,以及超时设置,这些超时设置会相应地应用于 JDBC 语句查询中。为了支持后者,应用程序代码必须使用 JdbcTemplate,或者对每个创建的语句调用 DataSourceUtils.applyTransactionTimeout(..) 方法。
在单资源的情况下,你可以使用DataSourceTransactionManager代替JtaTransactionManager,因为后者不需要容器支持JTA事务协调器。在这些事务管理器之间进行切换只需进行配置调整即可,前提是你要遵循所需的连接查找模式。需要注意的是,JTA不支持保存点(savepoints)或自定义隔离级别,并且具有不同的超时机制,但在JDBC资源以及JDBC提交/回滚管理方面,它们的行为是相似的。
为了实现JTA风格的延迟获取实际资源连接,Spring为目标连接池提供了一个相应的DataSource代理类:请参见LazyConnectionDataSourceProxy。这对于可能为空且不执行任何语句的交易(在这种情况下永远不会获取实际资源)特别有用,同时也适用于作为路由DataSource使用的情况,这意味着需要考虑事务同步的只读标志和/或隔离级别(例如IsolationLevelDataSourceRouter)。
LazyConnectionDataSourceProxy 还为只读事务提供了特殊的支持,可以在只读事务期间使用只读连接池,从而避免了在每次从主连接池获取连接时,在事务开始和结束时切换 JDBC 连接的只读标志所带来的开销(这取决于 JDBC 驱动程序,可能会产生较高的成本)。
从Spring 5.3开始,提供了一个扩展的JdbcTransactionManager版本,该版本在提交/回滚时添加了异常转换功能(与JdbcTemplate保持一致)。DataSourceTransactionManager只会抛出TransactionSystemException(类似于JTA),而JdbcTransactionManager会将数据库锁定失败等异常转换为相应的DataAccessException子类。需要注意的是,应用程序代码需要为这些异常做好准备,而不能仅仅期望会抛出TransactionSystemException。在这种情况下,JdbcTransactionManager是推荐的选择。
在异常行为方面,JdbcTransactionManager大致相当于JpaTransactionManager,也相当于R2dbcTransactionManager,它们可以互相作为直接的替代品。另一方面,DataSourceTransactionManager相当于JtaTransactionManager,并且可以在那里直接作为替代品使用。