DAO(Data Access Object)用于访问数据的对象,DAO 屏蔽了数据储存的最终介质和具体的实现技术细节;
Spring 提供了一套完善的DAO体系,用于屏蔽各种持久化技术的实现细节,以统一的管理方式进行管理;
统一的DAO异常体系
Spring 提供了一套和实现技术无关的、面向 DAO 层语义的异常体系,并通过转换器将不同的持久化技术的异常转换为Spring 的异常;
Spring DAO异常体系
Spring 以分类的方式建立了异常分类目录,对于大部分应用来说,该异常分类目录对于异常类型的划分具有适当的颗粒度;
异常转换器
对于不同持久化技术的异常转换为 Spring DAO异常,Spring 提供以下的异常转化器:
持久化技术 | 异常转化器 |
JDBC | org.springframework.jdbc.support.SQLExceptionTranslator |
Hibernate | org.springframework.orm.hibernateX.SessionFactoryUtils |
MyBatis | 使用JDBC的异常转化器 |
JPA | org.springframework.orm.jpa.EntiryManagerFactoryUtils |
JDO | org.springframework.orm.jdo.PersistenceManagerFactoryUtils |
※ Spring 4.0 对于 Hibernate 只支持 Hibernate 3.6 之后的版本;
数据访问模板支持
Spring DAO 为不同持久化技术提供了统一的模板,分别提供不同的模板类给予支持;
持久化技术 | 模板类 | 支持类 |
JDBC | org.springframework.jdbc.core.JdbcTemplate | org.springframework.jdbc.core.JdbcSupport |
Hibernate | org.springframework.orm.hibernateX.HibernateTemplate | org.springframework.orm.hibernateX.HibernateDaoSupport |
MyBatis | 直接使用 JDBC 的模板类和支持类 | |
JPA | org.springframework.orm.jpa.JpaTemplate | org.springframework.orm.jpa.JpaDaoSupport |
JDO | org.springframework.orm.jdo.JdoTemplate | org.springframework.orm.jdo.JdoDaoSupport |
数据源配置
对于任何持久化技术,都需要数据连接,Spring 中数据连接时以通过数据源获取的;
Spring 主要使用的数据源有3种:
- Apache DBCP 数据源
- C3P0 数据源
- JNDI 数据源
DBCP 数据源
DBCP 是一个依赖 Jakarta commons-pool 对象池机制的数据库连接池,所以在类路径下需要包含 commons-pool 类包;
使用 DBCP 数据源需要 commons-dbcp:commons-dbc 依赖包(已经包含 commons-pool 类包);
以下是使用 DBCP 配置 MySQL 数据源的片段:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destory-method="close"
p:dirverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://127.0.0.1:3306/sampleDB"
p:username="root"
p:password="1234"/>
以上配置的属性为必要属性,其中 destory-method 为配置数据源相应的关闭方法,以下是一些常用的配置属性:
分类 | 属性 | 默认值 | 说明 |
事务属性 | defaultAutoCommit | true | 连接池创建的连接默认 auto-commit 状态; |
defaultReadOnly | 驱动默认 | 连接池创建的连接默认 read-only 状态; | |
defaultTransactionIsolation | 驱动默认 | 连接池创建的连接默认 transactionIsolation 状态; 可选值: NONE, READ_COMMITTED, READ_UNCOMMITTED, REPEATABLE_READ, SERIALIZABLE | |
数据连接属性 | initialSize | 0 | 初始化连接:连接池启动格式创建的初始化连接数量; |
maxActive | 8 | 最大活动连接:连接池中允许的保持活动状态的最大连接池数量,超过的活动连接将被释放,设置为负数值则不限制; | |
maxIdle / minIdle | 8 / 0 | 最大/最小 空闲连接; | |
maxWait | -1(无限) | 最大等待时间:当没有可用连接时,连接池等待连接被归还的最大时间(毫秒单位),超过时间则抛出异常; | |
缓存设置 | poolPreparedStatements | false | 开始连接池的 prepared statement 功能,所有 CallableStatement 和 PreparedStatement 都会被缓存起来 |
maxOpenPreparedStatments | 0(无限制) | PreparedStatement 池能够同时分配的打开的 statements 的最大数量; | |
连接泄露回收 | removeAbandoned | false | 标记是否删除可能存在泄露的连接,该条件在 (getNumIdle() <2) and (getNumActive() > getMaxActive() -3) 满足时触发; |
removeAbandonedTimeout | 300 | 泄露的连接可以被回收的超时值(单位秒) | |
连接维护和检测 | validationQuery | 无 | 指定一个 SQL SELECT 语句,在将连接返回给调用者之前,使用该 SQL 语句验证从连接池中取出的连接是否可用; |
testOnBorrow | true | 是否从连接池中取出来连接前进行检验,如果检验失败,则从连接池中取出该连接并尝试取出另一个连接; | |
testOnReturn | false | 是否在归还到链接池中前进行检验; | |
testWhileIdle | false | 连接是否被空闲连接回收期进行检验,如果验证失败,则从连接池中去除; | |
timeBetweenEvicitionRunsMills | -1 | 空闲连接线程的运行周期(单位毫秒),设置为负数不运行空闲连接回收线程; | |
numTestsPerEvicitionRun | 3 | 在每次空闲连接回收器线程运行时检查的连接数量; | |
minEvictabledIdleTimeMills | 100 *60*30 | 连接在连接池存空闲时间达到该值时,空闲回收器将其进行回收(单位毫秒) |
C3P0 数据源
C3P0 是一个开源的 JDBC 数据源实现项目,实现了 JDBC3 和 JDBC2 拓展规范说明的 Connection 和 Statement 池;
使用 DBCP 数据源需要 com.mchange:c3p0
依赖包;
以下是使用 DBCP 配置 MySQL 数据源的片段:
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destory-method="close"
p:dirverClass="com.mysql.jdbc.Driver"
p:jdbcUrl="jdbc:mysql://127.0.0.1:3306/sampleDB"
p:username="root"
p:password="1234"/>
C3P0 除了包含以上 CBCP 拥有的配置属性外,还包含以下更多常用的配置:
属性 | 默认值 | 说明 |
acquireIncrement | 3 | 当连接池中的连接用完时,C3P0 一次性创建新连接的数目; |
acquireRetryAttempts | 30 | 从数据库获取新连接失败后重复尝试获取的次数; |
acquireRetryDelay | 1000 | 参数获取连接的间隔时间(单位毫秒); |
autoCommitOnClose | false | 连接关闭时默认将所有未提交的操作回滚; |
propertyCycle | 300 | 用户修改系统配置参数执行前的最多等待参数(单位秒) |
breakAfterAquireFailure | false | 声明为false,获取连接失败将会引起所有等待获取连接的线程抛出异常,大概数据源仍有效保留,并在下次调用 getConnection() 时继续尝试获取连接;声明为 true 时数据源将断开并永久关闭; |
checkoutTimeout | 0(无限) | 当连接池用完时,客户端调用 getConnection() 方法后等待获取新连接的时间,超出后抛出 SQLException (单位毫秒) |
idleConnectionTestPeriod | 0(不检查) | 间隔多少秒检查所有连接池的空闲连接(单位秒); |
initialPoolSize | 3 | 初始化时创建的连接数; |
minPoolSize / maxPoolSize | 0 / 15 | 连接池中保留的最小 / 最大连接数量; |
maxIdleTime | 0(不限制) | 连接的最大空闲时间(单位毫秒); |
maxStatements | 0 | JDBC 的标准参数,用于控制数据源内加载 PreparedStatement 数量, |
maxStatementsPerConnection | 0 | 连接池内单个连接所拥有的最大缓存 Statements数,当maxStatements 和 maxStatementsPerConnection 同时设置为 0 时,缓存关闭; |
numHelperThreads | 3 | C3P0 是异步操作的,缓慢的 JDBC 通过帮助进程完成,拓展这些操作可以有效提高性能; |
JNDI 数据源
如果应用配置在高性能的应用服务器(WebLogic、WebSphere等
)上,则可能更希望使用应用服务器本身提供的数据源(当该数据源使用 JNDI 开放时),Spring 提供了引用 JNDI 数据源的 JndiObjectFactoryBean 类;
以下是一个引用 JNDI 源的片段:
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"
p:jndiName="java:comp/env/jdbc/bbt" />
<!-- jndiName 指向引用的 JNDI 数据源-->
Spring 为获取 Java EE 资源提供了一个 jee 命名空间,通过 jee 空间命名,可以有效地简化 Java EE 资源的引用,以下片段示例使用 jee 命名空间引用 JNDI 数据源的位置:
<beans ...>
<jee:jndi-lookup id="dataSorce" jndi-name="java:comp/env/jdbc/bbt" />
</beans>
使用属性文件配置数据源
以上配置 DBCP 和 C3P0 数据源时,是将数据源连接参数硬编码到bean配置文件中,在实际项目中,为了维护连接数据的安全性和方便修改,往往会将这些连接数据存放到一个配置文件中,然后通过 SpEL 调用该配置文件的参数到bean配置文件;
将 C3P0 数据源的连接参数存放到 jdbc.properties
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql//127.0.0.1:3306/sampleDB
jdbc.userName=root
jdbc.password=1234
配置数据源bean片段
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destory-method="close"
p:dirverClass="#{jdbc.driverClass}"
p:jdbcUrl="#{jdbc.url}"
p:username="#{userName}"
p:password="#{passowrd}" />
解决MySQL的“8小时问题”
如果使用数据库为MySQL,当数据源配置不当时,很容易发生经典的 ”8小时问题“,原因是 MySQL 默认情况如果发现一个连接的空闲时间超过8个小时,则会在数据库端自动关闭该连接,而数据源不知道该数据源连接已经被数据库关闭了,当这个无用的连接返回给某个 DAO 时,DAO 会报无法获取 Connection 的异常;
在采用 DBCP 或 C3P0 的默认配置下,由于 testOnBorrow 属性的默认值为 true,即数据源在将连接交给 DAO 前,都会先检查该连接是否完好,所以并不会发生 ”8 小时问题“;但是这种默认的配置由于每次提交连接给DAO时都会检测连接的有效性,在高并发的应用中会带来性能问题;
一个推荐的高效解决方式如下:
设置 testOnBorrow 为 false,testWhileIdle 为 true,根据实际情况设置 timeBetweenEvictionRunsMills 的值;
这时,DBCP 和 C3P0 将会通过一个后台线程定时对空闲连接进行检测,当发现无用空闲连接时,就会将他们清除掉,只要
timeBetweenEvictionRunsMills 小于8小时,就可以避免 MySQL 的 ”8小时问题“;