5.1 Spring数据访问原理
DAO 数据访问对象(data access object)。
DAO提供了数据读取和写入到数据库中的一种方式。他们应该以接口的方式发布功能,而应用程序的其他部分就可以通过接口来进行访问了。
实现了松耦合代码
5.1.1 Spring数据访问异常体系
不与特定的持久化方式相关联。这意味着可以使用Spring抛出一致的异常,而不用关心所选择的持久化方案。
Spring的数据访问异常:
- CannotAcquireLockException
- CannotSerializeTransactionException
- CleanupFailureDataAccessException
- ConcurrencyFailureException
- DataAccessException
- DataAccessResourceFailureException
- DataIntegrityViolationException
- DataRetrievalFailureException
- DeadlockLoserDataAccessException
- EmptyResultDataAccessException
- IncorrectResultSizeDataAceessException
- InvalidDataAccessApiUsageException
- InvalidDataAccessResourceUsageException
- OptimisticLockingFailureException
- PermissionDeniedDataAccessException
- PessimisticLockingFailureException
- PessimisticLockingFailureException
- TypeMismatchDataAccessException
- UncategorizedDataAccessException
...
这些异常都继承自DataAccessException,DataAccessException是一个非检查型异常,没有必要去捕获Spring所抛出的数据访问异常,这是因为Spring认为触发异常的很多问题是不能在catch代码块中修复的。因此开发人员可以选择是否编写catch代码块。
5.1.2 数据访问模板化
模板方法模式:定义了过程的主要框架,将过程中与特点实现相关的部分委托给接口,而这个接口的不同实现定义了过程中的具体行为。
Spring在数据访问中使用的就是模板方法模式。
Spring将数据访问过程中固定的和可变的部分明确划分为两个不同的类:模板 template 、回调 callback,模板管理过程中固定的部分,回调处理自定义的数据访问代码。
Spring的处理逻辑
DAO模块 | DAO回调 |
1.准备资源 2.开始事务 |
3.在事务中执行 |
5.提交/回滚事务 6.关闭资源和处理错误 |
4.返回数据 |
Spring的模板类处理数据访问的固定部分——事务控制、管理资源、异常处理。应用程序相关的数据访问(创建语句、绑定参数以及整理结果集)在回调的实现中处理
针对不同的持久化平台,Spring提供了多个可选的模板
模板类(org.springframework.*) | 用途 |
jca.cci.core.CciTemplate | JCA CCI连接 |
jdbc.core.JdbcTemplate | JDBC连接 |
jdbc.core.namedparam.NamedParameterJdbcTemplate | 支持命名参数的JDBC连接 |
jdbc.core.simple.SimpleJdbcTemplate | 通过Java 5简化后的JDBC连接 |
orm.hibernate.HibernateTemplate | Hibernate 2.x的Session |
orm.hibernate3.HibernateTemplate | Hibernate 3.x的Session |
orm.ibatis.SqlMapClientTemplate | iBATIS SqlMap客户端 |
orm.jdo.JdoTemplate | Java数据对象(Java Data Object)实现 |
orm.jpa.JpaTemplate | Java持久化API的实体管理器 |
使用数据访问模板只需将其配置为Spring上下文中的Bean并将其织入到应用程序的DAO中。或者使用Spring的DAO支持类进一步简化应用程序的DAO配置。
5.1.3 DAO支持类
基于模板-回调设计,Spring提供了DAO支持类,而将业务自己的DAO类作为它的子类。当编写应用程序自己的DAO实现时,可以继承自DAO支持类并调用模板获取方法来直接访问底层的数据访问模板。Spring不仅提供了多个数据模板实现类,还为每种模板提供了对应的DAO支持类。
DAO支持类(org.springframework.*) | 为谁提供DAO支持 |
jca.cci.support.CcciDaoSupport | JCA CCI连接 |
jdbc.core.support.JdbcDaoSupport | JDBC连接 |
jdbc.core.namedparam.NamedParameterJdbcDaoSupport | 带有命名参数的JDBC连接 |
jdbc.core.simple.SimpleJdbcDaoSupport | 用Java5进行简化的JDBC连接 |
orm.hibernate.support.HibernateDaoSupport | Hibernate2.x的Session |
orm.hibernate3.support.HibernateDaoSupport | Hibernate3.x的Session |
orm.ibatis.support.SqlMapClientDaoSupport | iBaTIS SqlMap客户端 |
orm.jdo.support.JdoDaoSupport | Java数据对象(Java Data Object)实现 |
orm.jpa.support.JpaDaoiSupport | Java持久化API的试题管理器 |
5.2 配置数据源
无论选择哪一种SpringDAO的支持方式,都需要配置一个数据源的引用。Spring提供了在Spring上下文中配置数据源Bean的多种方式,包括:
- 通过JDBC驱动程序定义的数据源
- 通过JNDI查找的数据源
- 连接池的数据源
5.2.1 使用JNDI数据源
JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互 |
完全可以在应用程序之外对数据源进行管理,应用程序只需在访问数据库的时候查找数据源就可以了。
利用Spring,可以像使用SpringBean那样配置JNDI中数据源的引用,并将其装配到需要的类中。jee命名空间下的<jee:jndi-lookup>元素可以用于检索JNDI中的任何对象包括数据源,并将其用于SpringBean中。
eg: <jee:jndi-lookup id="dataSource"
jndi-name="/jdbc/SpitterDS"
resource-ref="true"/>
jndi-name指定JNDI中资源的名称。如果只设置了jndi-name属性,那么就会根据指定的名称查找数据源。但是如果应用程序运行在Java应用程序服务器中,则需要将resource-ref属性设置为true,这样给定的jndi-name将会自动添加java:comp/env前缀
5.2.2 使用数据源连接池
Spring并没有提供数据源连接池实现
Spring推荐使用DBCP(Jakarta Commons Database Connection Pooling)
所需jar:commons-dbcp.jar、commons-pool.jar |
DBCP包含了多个提供连接池功能的数据源,其中BasicDataSource是最常用的,因为它易于在Spring中配置,而且类似于Spring自带的DriverManagerDataSource。
例如:
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost/spitter/spitter"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
<property name="initialSize" value="5"/>
<property name="maxActive" value="10"/>
</bean>
前4个属性是配置BasicDataSource所必须的。属性driverClassName制定了JDBC驱动类的全限定类名。此外还有多个属性用来配置数据源连接池。
池配置属性 | 所指定的内容 |
initialSize | 池启动时创建的连接数量 |
maxActive | 同一时间可从池中分配的最多连接数,设置为0表示无限制 |
maxIdle | 池里不会被释放的最多控线连接,设置为0表示无限制 |
maxOpenPreparedStatements | 在同一时间能够从语句池中分配的预处理语句的最大数量,设置为0表示无限制 |
maxWait | 在抛出 异常之前,池等待连接回收的最大时间(当没有可用连接时)。如果设置为-1,表示无限等待 |
minEvictableIdleTimeMillis | 连接在池中保持空闲而不被回收的最大时间 |
minIdle | 在不创建新连接的情况下,池中保持空闲的最小连接数 |
poolPreparedStatements | 是否对预处理语句进行池管理(布尔值) |
5.2.3基于JDBC驱动的数据源
Spring提供了两种数据源对象,均位于org.springframework.jdbc.datasource
- DriverManagerDataSource:在每个连接请求时都会返回一个新建的连接。与DBCP的BasicDataSource不同,由DriverManagerDataSource提供的连接并没有进行池化管理
- SingleConnectionDataSource:在每个连接请求时都会返回同一个连接。可以视为只有一个连接的池,但不是严格意义上的连接池数据源
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
<property name="url" value="jdbc:hsqldb:hsql://localhost/spitter/spitter"/>
<property name="username" value="sa"/>
<property name="password" value=""/>
</bean>
SingleConnectionDataSource有且只有一个数据库连接,所以不适用于多线程的应用程序。尽管DriverManagerDataSource支持多线程,但是在每次请求连接时都会创建新连接,这是以性能为代价的。因此推荐使用数据源连接池。
5.3 在Spring中使用JDBC
5.3.1 应对失控的JDBC代码
大致就是JDBC代码太长了、很麻烦、但很重要等等等等……
5.3.2 使用JDBC模板
Spring为JDBC提供了3个模板类供使用
- JDBCTemplate:最基本的Spring JDBC模板,这个模板支持最简单的JDBC数据库访问功能以及简单的索引参数查询
- NamedParameterJdbcTemplate:使用该模板类执行查询时,可以将查询值以命名参数的形式绑定到SQL中,而不是使用简单的索引参数
- SimpleJdbctemplate:该模板类利用Java 5的一些特性,如自动装箱、泛型以及可变参数列表来简化JDBC模板的使用。
Spring3.0之后SimpleJdbcTemplate是最好的选择
使用SimpleJdbcTemplate访问数据
在Spring4.2中,已经没有SimpleJdbcTemplate了。
只需要设置DataSource就能够让SimpleJdbcTemplate正常工作。
Spring中配置SimpleJdbcTemplate
< bean id= "jdbcTemplate" class= "org.springframework.jdbc.core.simple.SimpleJdbcTemplate" >
<constructor-arg ref = "dataSource"/>
</bean >
属性dataSource所引用的dataSource可以是javax.sql.DataSource的任意实现
使用命名参数
命名参数可以赋予SQL中的每个参数一个明确的名字,在绑定值到查询语句的时候就通过该名字来引用参数。
使用Spring的JDBC DAO支持类
在Spring4.2中,已经没有SimpleJdbcTemplateDaoSupport
DaoSupport类就是把每一个Dao类需要完成的相同工作抽离出来,形成一个父类,让所有的dao类去继承它,减少重复代码。
5.4 在Spring中集成Hibernate
- 延迟加载(Lazy loading):借助于延迟加载,可以只抓取需要的数据。
- 预先抓取(Eager fetching):与延迟加载相对。借助预先抓取,可以使用一个查询获取完整的关联对象。
- 级联(Cascading):更改数据库中的表会同时修改其他表
ORM框架(对象/关系映射 object-relational mapping,ORM)提供了这些服务。在持久层使用ORM工具可以节省代码数量和开发时间。
Spring对对多个持久化框架都提供了支持,包括Hibernate、iBATIS、JDO、Java持久化API。
与Spring对JDBC的支持那样,Spring对ORM框架的支持提供了与这些框架的集成店以及一些附加的服务:
- Spring声明式事务的继承支持
- 透明的异常处理
- 线程安全的、轻量级的模板类
- DAO支持类
- 资源管理
5.4.1 Hibernate概览
以前,在Spring应用程序中使用Hibernate是通过HibernateTemplate进行的,其职责之一是管理Hibernate的Session。HibernateTemplate的不足之处在于存在一定程度的侵入性。在DAO中使用HibernateTemplate(无论是直接使用还是通过HibernateDaoSupport)时,DAO类就会与SpringAPI产生了耦合。
Hibernate3中引入了上下文Session(Contextual session),这是Hibernate本身所提供的保证每个事务使用同一Session的方案,因此没有必要再使用HibernateTemplate来保证这一行为。这种方式可以让自定义的DAO类不包含特定的Spring代码。
5.4.2 声明Hibernate的Session工厂
使用Hibernate的主要接口是org.hibernate.Session,Session接口提供了基本的数据访问功能,通过Hibernate的Session接口,应用程序的DAO能够满足所有的持久化需求。
获取Hibernate Session对象的标准方式是借助于Hibernate的SessionFactory接口的实现类。除了一些其他的任务,SessionFactory主要负责HibernateSession的打开、关闭以及管理。
在Spring中,通过Spring的某一个Hibernate Session 工厂Bean来获取Hibernate的SessionFactory。可以在Spring上下文中,像配置其他Bean那样来配置Hibernate Session工厂。配置是要确定持久化域对象时通过XML文件还是通过注解来进行配置的。如果选择在XML中定义对象与数据库之间的映射,那么需要在Spring中配置LocalSessionFactoryBean
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mappingResources">
<list>
<value>Spitter.hbm.xml</value>
</list>
</property>
<property name="hibernateProperties">
<props>
<prop key="dialect">org.hibernate.dialect.HSQLDialect</prop>
</props>
</property>
</bean>
dataSource装配了一个DataSource Bean的引用。
mappingResources装配了一个或多个的Hibernate映射文件,在这些文件中定义了应用程序的持久化策略。
hibernateProperties属性配置了Hibernate如何进行操作的细节。
如果使用注解的方式来定义持久化,那需要使用AnnotationSessionFactoryBean来代替LocalSessionFactoryBean
< bean id= "sessionFactory"
class= "org.springframework.orm.hibernate3.annotaion.AnnotationSessionFactoryBean" >
<property name = "dataSource" ref= "dataSource" />
<property name = "packagesToScan" value= "com.habuma.spitter.domain" />
<property name = "hibernateProperties">
<props >
<prop key= "dialect" >org.hibernate.dialect.HSQLDialect </prop >
</props >
</property >
</bean >
dataSource和hibernateProperties属性生命了从哪里获取数据库连接以及要使用哪一种数据库。package说ToScan告诉Spring扫描一个或多个包以查找域类,这些类通过注解方式表明要使用Hibernate进行持久化。使用JPA的@Entity或@MappedSuperclass注解以及Hibernate的@Entity注解进行标注的类都会包含在内。
AnnotationSessionFactoryBean的package说ToScan属性使用一个String所组成的数组来表明要在那些包中查找域类。一般情况下会使用列表(<property name="pcakagesToScan"> <list> <value>).还可以通过使用annotatedClasses属性来将应用程序中所有的持久化类以权限定名的方式明确列出。
5.4.3 构建不依赖于Spring的Hibernate代码
没有上下文Session之时,Spring的Hibernate模板用于保证每个事务使用同一个Session。既然Hibernate自己能够对其进行管理,那就没有必要使用模板类了。这意味着,可以直接将HibernateSession装配到DAO类中。
import java.util.List;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import com.habuma.spitter.domain.Spitter;
import com.habuma.spitter.domain.Spittle;
@Repository
public class HibernateSpitterDao implements SpitterDao {
private SessionFactory sessionFacotory;
@Autowired
public HibernateSpitterDao(SessionFacoty sessionFactory) {
this.sessionFactory = sessionFactory;
}
private Session currentSession() {
return sessionFactory.getCurrentSession();
}
public void addSpitter(Spitter spitter) {
currentSession().save(spitter);
}
public Spitter getSpitterById(long id) {
return (Spitter) currentSession().get(Spitter.class,id);
}
public void saveSpitter(Spitter spitter) {
currentSession().update(spitter);
}
...
}
使用@Autowire的可以让Spring自动将一个SessionFactory注入到HibernateSpi忐忑日Dao的sessionFactory属性。在currentSession()方法中,使用这个SessionF擦头人员来获取当前事务的Session。
@Repository注解:首先,@Repository是Spring的一种构造型注解,可以被Spring的<context:component-scan>扫描到,因此可不用去声明这个Bean,只需配置<context:component-scan>的base-package属性为相应的包就可以了。
为了给不适用模板的HibernateDAO添加异常转换功能,需要在Spring应用上下文添加一个PersistenceExceptionTranslationPostProcessor Bean。
<bean class="org.springframework.dao.annotaion.PersistenceExceptionTranslationPostProcessor"/>
它是一个Bean的后置处理程序,会在所有拥有@Repository注解的类上添加一个通知器(advisor),这样就会补货任何平台相关的异常,并以Spring的非检查型数据访问异常的形式重新抛出。
5.5 Spring与Java持久化API
JPA是基于POJO的持久化机制,从Hibernate和Java数据对象上借鉴了很多理念并加入了Java5注解的特性。
5.5.1 配置实体管理器工厂
基于JPA的应用程序使用EntityManagerfactory的实现类来获取EntityManager实例。JPA定义了两种类型的实体管理器:
- 应用程序管理类型(Application-managed):当应用程序向实体管理器工厂直接请求实体管理器时,工厂会创建一个实体管理器。在这种模式下,程序要负责打开或关闭试题管理器并在事务中对其进行控制。这种方式的试题管理器适合于不运行在Java EE容器中的独立应用程序。
- 容器管理类型(Container-managed):试题管理器由JavaEE创建和管理。应用程序根本不予试题管理器工厂打交道。相反试题管理器直接通过注入或JNDI来获取。容器负责配置实体管理器工厂。这种类型的实体管理器最适合用于JavaEE容器,在这种情况下会希望在persistence.xml指定的JPA配置之外保持一些自己对JPA的控制。
两者都实现了EntityManager接口。区别在于EntityManager的创建和管理方式。无论使用哪一种EntityManagerFactory,Spring都会负责管理Entity-Manager。如果使用应用程序管理类型的实体管理器,Spring承担了应用程序的角色并以透明的方式处理EntityManager,在容器管理的场景下,Spring会担当容器的角色。
创建这两种试题管理器工厂的Spring工厂Bean:
- LocalEntityManagerFactoryBean生成应用程序管理类型的EntityManagerFactory;
- LocalContainerEntityManagerFactoryBean生成容器管理类型的EntityManagerFactory
使用应用程序管理类型的JPA
绝大部分配置文件来源于一个名为persistence.xml的配置文件。这个文件必须位于类路径下的META-INF目录下。persistence.xml的作用在于定义一个或多个持久化单元。持久化单元是同一个数据源下的一个或多个持久化类
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version=1.0>
<persistence-unit name="spitterPU">
<class>com.habuma.spitter.domain.Spitter</class>
<class>com.habuma.spitter.domain.Spittle</class>
<properties>
<property name="toplink.jdbc.driver" value="org.hsqldb.jdbcDriver"/>
<property name="toplink.jdbc.url" value="jdbc:hsqldb:hsql://localhost/spitter/spitter"/>
<property name="toplink.jdbc.user" value="sa"/>
<property name="toplink.jdbc.password" value=""/>
</properties>
</persistence-unit>
</persistence>
Spring中要配置的内容很少
<bean id="emf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
<property name="persistenceUnitName" value="spitterPU"/>
</bean>
persistenceUnitName的值就是在Persistence.xml中配置的持久化单元的名字
使用容器管理类型的JPA(相对较好)
当在容器中运行时,使用容器提供的信息来生成EntityManagerFactory。当我们使用Spring时,就可以在Spring的应用上下文中配置JPA了。
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
</bean>
datasource可以是任何javax.sql.DataSource的实现
jpaVendorAdapter属性用于致命所使用的是哪一个厂商的JPA实现。Spring提供了多个JPA厂商的适配器:
- EclipseLinkJpaVendorAdapter
- HibernateJpaVendorAdapter
- OpenJpaVendorAdapter
- TopLinkJpaVendorAdapter
使用Hibernate的例子:
<bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="database" value="HSQL"/>
<property name="showSql" value="true"/>
<property name="generateDdl" value="false"/>
<property name="databasePlatform" value="org.hibernate.dialect.HSQLDialect"/>
</bean>
Hibernate的JPA适配器的数据库
数据库平台 | 属性database的值 |
IBM DB2 | DB2 |
Apache Derby | DERBY |
H2 | H2 |
Hypersonic | HSQL |
Informix | INFORMIX |
MySQL | MYSQL |
Oracle | ORACLE |
PostgresQL | POSTGRESQL |
Microsoft SQL Server | SQLSERVER |
Sybase | SYBASE |
从JNDI获取实体管理器工厂
如果将Spring应用程序部署在应用服务器中,Spring可能已经创建了EntityManagerFactory并将其置于JNDI中等待查询使用。这种情况下,可以使用Spring的jee命名空间下的<jee:jndi-lookup>元素来获取对EntityManagerFactory 的引用:
<jee:jndi-lookup id="emf" jndi-name="persistence/spitterPU"/>
5.5.2 编写基于JPA的DAO
Spring对JPA继承也提供了JpaTemplate模板以及对应的支持类JpaDaoSupport。但是基于模板的JPA已经被弃用了(类似Hibernate)。
import java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.Persistencecontext;
import org.springframework.dao.DataaccessException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotaion.Transactional;
@Repository("spitterDao")
@Transactional
public class JpaspitterDao implements SpitterDao {
private static final String RECENT_SPITTLES = "SELECT s FROM Spittle s";
private static final String ALL_SPITTERS = "SELECT s FROM Spitter s";
private static final String SPITTER_FOR_USERNAME = "SELECT s FROM Spitter s WHERE s.username = :username";
private static final String SPITTLES_BY_USERNAME = "SELECT S FROM Spittle s WHERE s.spitter.username = :username";
@PersistenceContext
private EntityManager em;
public void addSpitter(Spitter spitter) {
em.persist(spitter);
}
public Spitter getSpitterById(long id) {
return em.find(Spitter.class,id);
}
public void saveSpitter(Spitter spitter) {
em.merge(spitter);
}
}
@PersistenceContext注解表明需要将一个EntityManager注入到所标注的成员上。为了在Spring中实现EntityManager注入,需要在Spring应用上下文中配置一个PersistenceAnotationBeanPostProcessor
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
@Repository与开发Hibernate上下文Session的DAO是一只的。由于没有模板类来处理异常,所以需要为DAO添加@Repository注解,这样PersistenceExceptionTranslationPostProcessor就会知道要将这个Bean产生的异常转换成Spring的同一数据访问。要使用PersistenceExceptionTranslationPostProcessor,需要将其作为一个Bean装配到spring中
<bean class="org.springframework.dao.annotaion.PersistenceExceptionTranslationPostProcessor"/>
第五章小结
Spring对JDBC和ORM的框架的支持简化了各种持久化机制都存在的样板代码,这是我们只需关注与应用程序相关的数据访问即可。
Spring简化数据访问方式之一就是管理数据库连接的生命周期和ORM框架的Session。通过这种方式,持久化机制的管理对应用程序代码时完全透明的。
Spring能够捕获框架的特定异常并将其转换成异常体系中的非检查型异常。包括将JDBC抛出的SQLException转换为含义更丰富的异常