DAO:数据访问对象(data access object)的缩写。
如何你曾经编写过JDBC代码,你肯定会意识到如果不强制捕获SQLException,你几乎不能使用JDBC做任何事情。SQLException表示在尝试访问数据库时出现了问题,但是这个异常却没有告诉你哪里出错了以及如何进行处理。
可能导致抛出SQLException的常见问题包括:
1、应用程序无法连接数据库。
2、要执行的查询有语法错误。
3、查询中所使用的表和(或)列不存在。
4、试图插入或更新的数据违反了数据库的完整性约束。
它不是对每种可能的问题都会有不通的异常类型。而都是抛出SQLException。
一方面,JDBC的异常体系过于简单了---实际上,它算不上一个体系。另一方面,Hibernate的异常体系是其本身所独有的。我们需要的是,数据访问异常要具有描述性而且又与特定的持久化框架无关。
Spring几乎为读取和写入数据库的所有错误都提供了异常。Spring的数据访问异常很多。尽管Spring的异常体系比JDBC简单的SQLException丰富的多,但它没有与特定的持久化方式相关联。这意味着我们可以使用Spring抛出一致的异常,而不用关心所选择持久化方案。
Spring的异常大多继承自DataAccessException。这个异常的特殊指出在于它是一个非检查型异常。说白了不用写try catch。这把是否捕获异常的权利留给了开发人员。
Spring提供的数据访问模版,分别适用于不通的持久化机制。
下面分别是模版类和它的用途:
jca.cci.core.CciTemplate : JCA CCI连接
jdbc.core.JdbcTemplate : JDBC连接
jdbc.core.namedparam.NamedParameterJdbcTemplate : 支持命名参数的JDBC连接
jdbc.core.simple.SimpleJdbcTemplate : 通过java5简化后的JDBC连接
orm.hibernate.HibernateTemplate : Hibernate 2.x的Session
orm.hibernate3.HibernateTemplate : Hibernate 3.x的Session
orm.ibatis.SqlMapClientTemplate : iBATIS SqlMap客户端
orm.jdo.JdoTemplate : Java数据对象实现
orm.jpa.JpaTemplate : Java持久化API的实体管理器
配置数据源
1、使用JNDI数据源
Spring应用程序经常部署在Java EE应用服务器中,如WebSphere、JBoss或者像Tomcat这样的Web容器。这些服务器允许你配置通过JNDI获取数据源。这种配置的好处在于数据源完全可以在应用程序之外进行管理,这样应用程序只需在访问数据库的时候查找数据源就可以了。
使用<jee:jndi-lookup>元素装配到Spring中。
<!-- 其中jndi-name属性用于指定JDDI中资源的名称。如果只设置了jndi-name属性,那么就会根据指定的名称
查找数据源。但是,如果应用程序运行在Java应用程序服务器中,则需要将resource-ref属性设置为true,
这样给定的jndi-name将会自动添加java:comp/env/前缀
-->
<jee:jndi-lookup jndi-name="/jdbc/SpitterDS" id="dataSource" resource-ref="true" />
2、使用数据源连接池
<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所必需的。下面列出了另外的最有用的一些属性。
initialSize:池启动时创建的连接数量。
maxActive:同一时间可从池中分配的最多连接数。如果设置为0,表示无限制。
maxIdle:池里不会被释放的最多空闲连接数。如果设置为0,则表示无限制。
maxOpenPreparedStatements:在同一时间能够从语句池中分配的预处理语句的最大数量。如果设置为0,表示无限制。
maxWait:在抛出异常之前,池等待连接回收的最大时间(当没有可用连接时)。如果设置为-1,表示无限等待。
minEvictableIdleTimeMillis:连接在池中保持空闲而不回收的最大时间。
minIdle:在不创建新连接的情况下,池中保持空闲的最小连接数。
poolPreparedStatements:是否对预处理语句进行池处理(布尔值)
3、基于JDBC驱动的数据源
在Spring中,通过JDBC驱动定义数据源是最简单的配置方式。Spring提供了两种数据源对象。
DriverManagerDataSource:在每个连接请求时都会返回一个新建的连接。与DBCP的BasicDataSource不同,由DriverManagerDataSource提供的连接兵没有进行池化管理。
SingleConnectionDataSource:在每个连接请求时都会返回同一个连接。尽管SingleConnectionDataSource不是严格意义上的连接池数据源,但是你可以将其视为只有一个连接的池。
以上两个数据源的配置与BasicDataSource的配置类似:
<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>
唯一的区别在于DriverManagerDataSource和SingleConnectionDataSource都没有提供连接池功能,所以没有可配置的池相关的属性。
SingleConnectionDataSource有且只有一个数据库连接,所以不适用于多线程的应用程序。尽管DriverManagerDataSource支持多线程,但是在每次请求连接时都会创建新连接,这是以性能为代价的。鉴于以上的这些限制,我强烈建议应该使用数据源连接池。
使用JDBC模版
Spring为JDBC框架承担了资源管理和异常处理的工作,从而简化了JDBC代码,让我们只需编写从数据库读写数据的必须代码。
Spring将数据访问的样板式代码提取到模版类中。Spring为JDBC提供了3个模版类供使用。
1、JdbcTemplate:最基本的Spring的JDBC模版,这个模版支持最简答你的JDBC数据库访问功能以及简单的索引参数。
2、NamedParameterJdbcTemplate:使用该模版类执行查询时,可以将查询值以命名参数的形式绑定到SQL中,而不是使用简单的索引参数。
3、SImpleJdbcTemplate:该模版类利用Java5的一些特性,例如自动装箱、泛型以及可变参数列表来简化JDBC模版的使用。
使用SImpleJdbcTemplate访问数据
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.simple.SimpleJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
现在,可以将jdbcTemplate装配到DAO中兵使用了 SImpleJdbcTemplate:
public class JdbcSpitterDAO implements SpitterDAO {
private SimpleJdbcTemplate jdbcTemplate ;
public void setJdbcTemplate(SimpleJdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate ;
}
}
你还需要装配JdbcSpitterDAO的jdbcTemplate属性,如下:
<bean id="spitterDao" class="com.habuma.spitter.persistence.SimpleJdbcTemplateSpitterDao">
<property name="jdbcTemplate" ref="jdbcTemplate" />
</bean>
下面是基于SimpleJdbcTemplate的addSpitter()方法。
public void addSpitter(Spitter spitter) {
jdbcTemplate.update(SQL_INSERT_SPITTER,
spitter.getUsername(),
spitter.getPassword(),
spitter.getFullName(),
spitter.getEmail(),
spitter.isUpdateByEmail()) ;
spitter.setId(queryForIdentity()) ;
}
使用JdbcTemplate也简化了数据读取操作。下面是个新方法,在这个方法中使用SimpleJdbcTemplate回调将结果集映射成域对象。
public Spitter getSpitterById(long id) {
return jdbcTemplate.queryForObject(SQL_SELECT_SPITTER_BY_ID,
new ParameterizedRowMapper<Spitter>() {
@Override
public Spitter mapRow(ResultSet rs, int rowNum)
throws SQLException {
Spitter spitter = new Spitter() ;
spitter.setId(rs.getLong(1)) ;
spitter.setUsername(rs,getString(2)) ;
spitter.setPassword(rs.getString(3)) ;
spitter.setFullName(rs.getString(4)) ;
return spitter ;
return null;
}
}, id) ;
}
在getSpitterById()方法中使用了SimpleJdbcTemplate的queryForObject()方法来从数据库查询Spitter。queryForObject()方法有3个参数:
1、String,包含了要从数据库中查找数据的SQL。
2、parameterizedRowMapper对象,用来从ResultSet中提取值并构建域对象
3、可变参数列表,列出了要绑定到查询上的索引参数值。
使用命名参数
在上面的addSpitter()方法使用了索引参数。这意味着我们需要留意查询中参数的顺序,而在将值传递给update()方法的时候要保持正确的顺序。如果在修改SQL时更改了参数的顺序,那么我们还需要修改参数值的顺序。
除了这种方法之外,我们还可以使用命名参数。命名参数可以赋予SQL中的每个参数一个明确的名字,在绑定值到查询语句的时候就通过该名字来引用参数。例如:
private static final String SQL_INSERT_SPITTER =
"insert into spitter(username, password, fullname) " +
"values (:username, :password, :fullname)" ;
使用命名参数查询,绑定值的顺序就不重要了,我们可以按照名字来绑定值。如果查询语句发生了变化导致参数的顺序域之前不一致,我们不需要修改绑定的代码。
public void addSpitter(Spitter spitter) {
Map<String, Object> params = new HashMap<String, Object>() ;
params.put("username", spitter.getUserName()) ;
params.put("password", spitter.getPassword()) ;
params.put("fullname", spitter.getFullName()) ;
jdbcTemplate.update(SQL_INSERT_SPITTER, params) ;
spitter.setId(queryForIdentity()) ;
}
使用SPring 的JDBC DAO支持类
对于应用程序中的每一个JDBC DAO类,我们都需要添加一个SimpleJdbcTemplate属性以及对应的setter方法,并确保将SimpleJdbcTemplate Bean装配到每个DAO的SimpleJdbcTemplate属性中。如果应用程序中只有一个DAO,这并不算什么问题,但是如果有多个DAO的话,这就会产生大量的重复工作。
一种可行的解决方案就是为所有的DAO创建一个通用的父类,在其中会有SimpleJdbcTemplate属性。然后让所有的DAO类继承这个类兵使用父类的SimpleJdbcTemplate进行访问。
Spring提供了内置的基类。(JdbcDaoSupport、SimpleJdbcDaoSupport和NamedParameterJdbcDaoSupported)
下面是例子:
public class JdbcSpitterDao extends SimpleJdbcDaoSupport implements SpitterDao{
...
}
SimpleJdbcDaoSupport通过getSimpleJdbcTemplate()能够便捷地访问SimpleJdbcTemplate。例如,addSpitter()方法可以这样改写:
public void addSpitter(Spitter spitter) {
getSimpleJdbcTemplate().update(SQL_INSERT_SPITTER,
spitter.getUserName(),
spitter.getPassword(),
spitter.getFUllName(),
spitter.getEmail(),
spitter.isUpdateByEmail()) ;
spitter.setId(queryForIdentity()) ;
}
在Spring中继承Hibernate
在使用过jdbc后,我们还需要一些复杂的特性:
1、延迟加载:我们可以只抓去需要的数据。
2、预先抓取:这与延迟加载是相对的,借助于预先抓去,我们可以使用一个查询获取完整的关联对象。如果需要PurchaseOrder及其关联的LineItem对象,预先抓去的功能可以在一个操作中将它们全部从数据库中提取出来,这节省了多次查询的成本。
3、级联:有时,更改数据库中的表会同时修改其他表。
SPring对多个持久化框架都提供了支持,包括Hibernate、iBATIS、JAVA数据对象(Java Data Objects,JDO)以及java持久化API。
与Spring对JDBC的支持那样,Spring对ORM框架的支持提供了与这些框架的集成点以及一些附加的服务,如下所示:
1、Spring声明式事务的集成支持
2、透明的异常处理
3、线程安全的、轻量级的模版类
4、DAO支持类
5、资源管理
声明Hibernate的Session工厂
使用Hibernate的主要接口是org.hibernate.Session。Session接口提供了基本的数据访问功能,如保存、更新、删除以及从数据库加载对象的功能。通过Hibernate的Session接口,应用程序的DAO能够满足所有的持久化需求。
获取Hibernate Session对象的标准方式是借助于Hibernate的SessionFactory接口的实现类。除了一些其他的任务,SessionFactory主要负责Hibernate Session的打开、关闭以及管理。
1、XML配置
<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>
2、注解配置
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.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>
就像在LocalSessionFactoryBean中那样,dataSource和hibernateProperties属性声明了从哪里获取数据库连接以及要使用哪一种数据库。
这里不再列出Hibernate配置文件,而是使用packagesToScan属性告诉Spring扫描一个或多个包以查找域类,这些类通过注解方式表明要使用Hibernate进行持久化。使用JPA的@Entity或@MappedSuperclass注解以及Hibernate的@Entity注解进行标注的类都会包含在内。
如果愿意,我们还可以通过使用annotatedClassed属性来将应用程序中所有的持久化类以全限定名的方式明确列出:
<property name="annotatedClassed">
<list>
<value>com.habuma.spitter.domain.Spitter</value>
<value>com.habuma.spitter.domain.Spittle</value>
</list>
</property>
annotatedClassed属性对于准确指定少量的域类是不错的选择。如果你有很多的域类且不想将其全部列出。或者你想自由地添加或溢出域类而不想修改Spring配置的话,则使用packagesToScan属性更合适。
构建不依赖于Spring的Hibernate代码
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class HibernateSpitterDao implements SpitterDao{
private SessionFactory sessionFactory ;
@Autowired
public HibernateSpitterDao(SessionFactory 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);
}
}
我们在类上使用了@Repository注解,这会为我们做两件事情。首先,@Repository是Spring的另一种构造型注解,它能够像其他注解一样被Spring的<context:component-scan>所扫描到。这样就不必明确声明HibernateSpitterDao Bean了,只需在<context:component-scan>配置即可。
除了帮助简化XML配置以外,@Repository还有另外一个用处。让我们回想一下模版类,它有一项任务就是捕获平台相关的异常,然后以Spring的非检查型异常形式重新抛出。如果我们使用Hibernate上下文Session而不是Hibernate模版,那么异常转换会怎么处理呢?
为了给不使用模版的Hibernate DAO添加异常转换功能,我们只需在Spring应用上下文中添加一个PersistenceExceptionTranslationPostProcessorBean:
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor" />
PersistenceExceptionTranslationPostProcessor是一个Bean的后置处理程序,它会在所有拥有@Repository注解的类上添加一个通知器(advisor),这样就会捕获任何平台相关的异常并以Spring的非检查型数据访问异常的形式重新抛出。
Spring与Java持久化API
配置实体管理器工厂
简单来说,基于JPA的应用程序使用EntityManagerFactory的实现类来获取EntityManager实例。JPA定义了两种类型的实体管理器:
1、应用程序管理类型:当应用程序向实体管理器工厂直接请求实体管理器时,工厂会创建一个实体管理器。在这种模式下,程序要负责打开或关闭实体管理器并在事务中对其进行控制。这种方式的实体管理器适合于不运行在Java EE容器中的独立应用程序。
2、容器管理类型:实体管理器由Java EE创建和管理。应用程序根本不与实体管理器工厂打交道。相反,实体管理器直接通过注入或JNDI来获取。容器负责配置实体管理器工厂。这种类型的实体管理器最适合用于Java EE容器,在这种情况下会希望在persistence.xml指定的JPA配置之外保持一些自己对JPA的控制。
它们两个的区别在于EntityManager的创建和管理方式。应用程序管理类型的EntityManager是由EntityManagerFactory创建的。而后者是通过PersistenceProvider的createEntityManagerFactory()方法得到的。与此相对,容器管理类型的entityManagerFactory是通过PersistenceProvider的createContainerEntityManagerFactory()方法获得的。
在容器管理的场景下,SPring会担当容器的角色。
这两种实体管理器工厂分别由对应的Spring工厂Bean创建的:
1、LocalEntityManagerFactoryBean生成应用程序管理类型的EntityManagerFactory。
2、LocalContainerEntityManagerFactoryBean生成容器管理类型的EntityManagerFactory。
使用应用程序管理类型的JPA
对于应用程序管理类型的实体管理器工厂来说,它绝大部分配置文件来源于一个名叫persistence.xml的配置文件。这个文件必须位于类路径下的META-INF目录下。
persistence.xml的作用在于定义一个或多个持久化单元。持久化单元是同一个数据源下的一个或多个持久化类。简单来讲,persistence.xml列出了一个或多个的持久化类以及一些其他的配置,如数据源和基于XML的配置文件。以下是一个典型的persistence.xml文件,它用于Spitter应用程序。
<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>
因为在persistence.xml文件中包含了大量的配置信息,所以在Spring中需要配置的就很少了。可以通过以下的<bean>元素在Spring中声明LocalEntityManagerFactoryBean:
<bean id="emf" class="org.springframework.orm.jpa.LocalEntityFactoryBean">
<property name="persistenceUnitName" value="spitterPU" />
</bean>
赋给persistenceUnitName属性的值就是persistence.xml中持久化单元的名称。
使用容器管理类型的JPA
容器管理的JPA采取了一种不同的方式。当在容器中运行时,可以使用容器提供的信息来生成EntityManagerFactory。
你可以将数据源信息配置在Spring应用上下文中,而不是在persistence.xml中了。例如,下面的<bean>声明展现了在Spring中如何使用LocalContainerEntityManagerFactoryBean来配置容器管理类型的JPA。
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
</bean>
jpaVendorAdapter属性用于指明所使用的是哪一个厂商的JPA实现。Spring提供了多个JPA厂商适配器:
1、EclipseLinkJpaVendorAdapter
2、HibernateJpaVendorAdapter
3、OpenJpaVendorAdapter
4、TopLinkJpaVendorAdapter