Spring用DataSource来保持与数据库的连接。DataSource是JDBC规范的一部分同时是一种通用的连接工厂。它使得框架或者容器对应用代码屏蔽连接池或者事务管理等底层逻辑。作为开发者,你无需知道连接数据库的底层逻辑;这只是创建datasource的管理员该负责的模块。在开发测试过程中你可能需要同时扮演双重角色,但最终上线时你不需要知道生产数据源是如何配置的。
当使用Spring JDBC时,你可以通过JNDI获取数据库数据源、也可以利用第三方依赖包的连接池实现来配置。比较受欢迎的三方库有Apache Jakarta Commons DBCP 和 C3P0。在Spring产品内,有自己的数据源连接实现,但仅仅用于测试目的,同时并没有使用到连接池。
这一节使用了Spring的DriverManagerDataSource实现、其他更多的实现会在后面提到。
注意:仅仅使用DriverManagerDataSource类只是为了测试目的、因为此类没有连接池功能,因此在并发连接请求时性能会比较差
通过DriverManagerDataSource获取数据库连接的方式和传统JDBC是类似的。首先指定JDBC驱动的类全名,DriverManager 会据此来加载驱动类。接下来、提供JDBC驱动对应的URL名称。(可以从相应驱动的文档里找到具体的名称)。然后传入用户名和密码来连接数据库。下面是一个具体配置DriverManagerDataSource连接的Java代码块:
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
dataSource.setUsername("sa");
dataSource.setPassword("");
接下来是相关的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配置:
<bean id="dataSource" class="org.apache.commons.dbcp.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配置:
<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"/>
15.3.2 DataSourceUtils
DataSourceUtils类是一个方便有用的工具类,提供了从JNDI获取和关闭连接等有用的静态方法。它支持线程绑定的连接、例如:使用DataSourceTransactionManager的时候,将把数据库连接绑定到当前的线程上。
15.3.3 SmartDataSource
实现SmartDataSource接口的实现类需要能够提供到关系数据库的连接。它继承了DataSource接口,允许使用它的类查询是否在某个特定的操作后需要关闭连接。这在当你需要重用连接时比较有用。
15.3.4 AbstractDataSource
AbstractDataSource是Spring DataSource实现的基础抽象类,封装了DataSource的基础通用功能。你可以继承AbstractDataSource自定义DataSource 实现。
15.3.5 SingleConnectionDataSource
SingleConnectionDataSource实现了SmartDataSource接口、内部封装了一个在每次使用后都不会关闭的单一连接。显然,这种场景下无法支持多线程。
为了防止客户端代码误以为数据库连接来自连接池(就像使用持久化工具时一样)错误的调用close方法,你应将suppressClose设置为true。这样,通过该类获取的将是代理连接(禁止关闭)而不是原有的物理连接。需要注意你不能将这个类强制转换成Oracle等数据库的原生连接。
这个类主要用于测试目的。例如,他使得测试代码能够脱离应用服务器,很方便的在单一的JNDI环境下调试。和DriverManagerDataSource相反,它总是重用相同的连接,这是为了避免在测试过程中创建过多的物理连接。
15.3.6 DriverManagerDataSource
DriverManagerDataSource类实现了标准的DataSource接口,可以通过Java Bean属性来配置原生的JDBC驱动,并且每次都返回一个新的连接。
这个实现对于测试和JavaEE容器以外的独立环境比较有用,无论是作为一个在Spring IOC容器内的DataSource Bean,或是在单一的JNDI环境中。由于Connection.close()仅仅只是简单的关闭数据库连接,因此任何能够操作DataSource的持久层代码都能很好的工作。但是,使用JavaBean类型的连接池,比如commons-dbcp往往更简单、即使是在测试环境下也是如此,因此更推荐commons-dbcp。
15.3.7 TransactionAwareDataSourceProxy
TransactionAwareDataSourceProxy会创建一个目标DataSource的代理,内部包装了DataSource,在此基础上添加了Spring事务管理功能。有点类似于JavaEE服务器中提供的JNDI事务数据源。
注意:一般情况下很少用到这个类,除非现有代码在被调用的时候需要一个标准的 JDBC DataSource接口实现作为参数。在这种场景下,使用proxy可以仍旧重用老代码,同时能够有Spring管理事务的能力。更多的场景下更推荐使用JdbcTemplate和DataSourceUtils等更高抽象的资源管理类.
更多细节请查看TransactionAwareDataSourceProxy的JavaDoc)
15.3.8 DataSourceTransactionManager
DataSourceTransactionManager类实现了PlatformTransactionManager接口。它将JDBC连接从指定的数据源绑定到当前执行的线程中,
允许一个线程连接对应一个数据源。
应用代码需要通过DataSourceUtils.getConnection(DataSource) 来获取JDBC连接,而不是通过JavaEE标准的DataSource.getConnection来获取。它会抛出org.springframework.dao的运行时异常而不是编译时SQL异常。所有框架类像JdbcTemplate都默认使用这个策略。如果不需要和这个 DataSourceTransactionManager类一起使用,DataSourceUtils 提供的功能跟一般的数据库连接策略没有什么两样,因此它可以在任何场景下使用。
DataSourceTransactionManager支持自定义隔离级别,以及JDBC查询超时机制。为了支持后者,应用代码必须在每个创建的语句中使用JdbcTemplate或是调用DataSourceUtils.applyTransactionTimeout(..)方法
在单一的资源使用场景下它可以替代JtaTransactionManager,不需要要求容器去支持JTA。如果你严格遵循连接查找的模式的话、可以通过配置来做彼此切换。JTA本身不支持自定义隔离级别!