http://subclipse.tigris.org/update
精通Spring——Java轻量级架构开发实践 10.4 Spring对Hibernate的支持
http://book.youkuaiyun.com/ 2006-8-15 16:27:00
图书导读 当前章节:10.4 Spring对Hibernate的支持·4.2 工厂模式(Design Pattern:Factory Method)的精髓·4.3 单例模式(Design Pattern:Singleton)·10.3 Spring对IBatis的支持·10.5 小结·18.1 模仿对象·18.2 Spring Mock简介Java EE开源框架培训
蓝点世纪外企高端Java软件工程师培训助你实现月薪 1000-6000元的飞跃!4个月积累1年的经验值
www.fsailing.com/
Web2.0 个人桌面
i桌面,与微软Live争锋 网络桌面新概念
live.youkuaiyun.com
Oracle身份管理与萨班斯法案
Oracle身份管理的重要意义在哪里? 获得Oracle 10最新免费资源
ad.cn.doubleclick.ne...
如上文所述,Spring对DAO模式具有概念和风格上的一致性,所以无论使用哪种持久方案(比如JdbcTemplate、SqlMapClientTemplate以及稍后将要介绍的HibernateTemplate)都会感到非常亲切。
下文将通过Spring改写上一章给出的HibernateStockDao,并向其中添加一些额外的功能。
说明:本书中使用的的是Hibernate3,所以也将使用Spring对于Hibernate3支持的那部分功能(当然,Spring对早期版本的Hibernate也提供支持)。
10.4.1 在Spring上下文中配置SessionFactory
通过上文的描述,可以知道,Spring使用JdbcTemplate时必须和特定的数据源进行绑定。而在Hibernate中,数据源是对用户屏蔽的,它使用一个称为“Session”的强劲武器。
Session具有建立或取消对象的持久关联、同步对象状态和数据库以及事务管理等等复杂功能。Session是Hibernate的核心概念,就如同SqlMap与之IBatis一样。Session的创建依赖于Hibernate的SessionFactory,SessionFactory和Session一样,也是Hibernate的核心组件。
和IBatis的整合方式一样,Spring会将Hibernate基本配置文件中的数据源属性抽离到Spring自身的配置文件中,以提供统一的数据源管理和注射。
首先,给出Hibernate的配置文件,如代码10.19所示。
代码10.19 hibernate.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- Database connection settings -->
<!-- <property name="connection.driver_class">-->
<!-- org.postgresql.Driver-->
<!-- </property>-->
<!-- <property name="connection.url">-->
<!-- jdbc:postgresql://1210.0.0.1:5432/hibernate-->
<!-- </property>-->
<!-- <property name="connection.username">postgres</property>-->
<!-- <property name="connection.password">1111</property>-->
<!-- SQL dialect -->
<property name="dialect">
org.hibernate.dialect.PostgreSQLDialect
</property>
<!-- Echo all executed SQL to stdout -->
<property name="show_sql">true</property>
<!-- Drop and re-create the database schema on startup -->
<property name="hbm2ddl.auto">create</property>
<mapping resource="chapter7/hibernate/domain/Category.hbm.xml" />
<mapping resource="chapter7/hibernate/domain/Product.hbm.xml" />
</session-factory>
</hibernate-configuration>
注意,代码10.19中被注释掉的部分,它将被抽离到了Spring的上下文配置文件,而其余部分则暂时保持不变。
下面给出Spring的配置文件,如代码10.20所示。
代码10.20 dataAccessContext-local.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:jdbc.properties</value>
</list>
</property>
</bean>
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url"
value="jdbc:postgresql://127.0.0.1:5432/hibernate"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocations">
<value>chapter7/spring/hibernate/hibernate.cfg.xml</value>
</property>
<!-- <property name="mappingResources">-->
<!-- <list>-->
<!-- <value>chapter7/hibernate/domain/Category.hbm.xml</value>-->
<!-- <value>chapter7/hibernate/domain/Product.hbm.xml</value>-->
<!-- </list>-->
<!-- </property>-->
<!-- <property name="hibernateProperties">-->
<!-- <props>-->
<!-- <prop key="hibernate.dialect">
org.hibernate.dialect.PostgreSQLDialect
</prop>-->
<!-- <prop key="show_sql">true</prop>-->
<!-- <prop key="hbm2ddl.auto">create</prop>-->
<!-- </props>-->
<!-- </property>-->
</bean>
</beans>
可以发现,从代码10.19抽离出的数据源,现在换成了Apache的DBCP连接池。当然,也可以换作其他实现,比如从一个JNDI上获取的数据源:
<bean id="dataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName" value="java:comp/env/jdbc/myds"/>
</bean>
Spring提供了LocalSessionFactoryBean来创建本地的Hibernate SessionFactory,这就类似于上节中介绍的SqlMapClientFactoryBean(它用以创建IBatis的SqlMap)。当然也可以创建绑定在JNDI上的SessionFactory,不过这通常只在EJB环境下使用。
注意:代码10.20中被注释掉的部分,如果不使用LocalSessionFactoryBean的configLocations属性读取Hibernate的原生配置文件,可由Spring的LocalSessionFactoryBean负责配置Hibernate,它和hibernate.cfg.xml的作用完全一致,这时候就不需要Hibernate的原生配置了。
10.4.2 重建Hibernate进货DAO伪实现
有了以上对Hibernate SessionFactory的正确配置后,下文将重建一个Hibernate StockDao(曾在第九章9.7节给出过),其中主要展示了HinbernateTemplate和HibernateCallback(这两个类也是Spring给出的,它们用于简化Hibernate的操作)的使用方法。
首先为上一章代码9.31的HibernateStockDao抽象出一个接口,并且增设一个查询方法,如代码10.21所示。
代码10.21 StockDao.java
package chapter10.spring.hibernate;
import java.util.List;
import chapter10.hibernate.domain.Category;
public interface StockDao {
public void createCategoryCascade(Category category);
public void deleteCategoryCascade(Category category);
/**
* 通过Category,查找Product列表
*/
public List findProductByCategory(Category category);
}
接着撰写一个StockDao的伪实现,它暴露了一个HibernateTemplate属性,用以接受注射,如代码10.22所示。
代码10.22 HibernateStockDao.java
package chapter10.spring.hibernate;
import java.util.List;
import org.springframework.orm.hibernate3.HibernateTemplate;
import chapter10.hibernate.domain.Category;
public class HibernateStockDao implements StockDao {
private HibernateTemplate template;
public void setTemplate(HibernateTemplate template) {
this.template = template;
}
public void createCategoryCascade(Category category) {
}
public void deleteCategoryCascade(Category category) {
}
public List findProductByCategory(final Category category) {
return null;
}
}
10.4.3 TDD又来了:规划测试案例
有了以上这套DAO伪实现之后,先不急着撰写DAO的具体实现,回想一下第三章介绍的测试驱动开发(TDD),这里不妨再次运用它。
首先是规划测试,沿用上一章代码9.32的测试案例并改写,使其中的StockDao和JdbcTemplate接受IoC注射(这里引入JdbcTemplate目的只是为了方便测试),如代码10.23所示。
代码10.23 HibernateStockDaoTest.java
package chapter10.spring.hibernate;
import java.util.List;
import junit.framework.TestCase;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import chapter10.hibernate.domain.Category;
import chapter10.hibernate.domain.Product;
public class HibernateStockDaoTest extends TestCase {
private StockDao stockDao;
private JdbcTemplate jdbcTemplate;
private Product product1;
private Product product2;
private Category category;
protected void setUp() throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[]{"ch10/spring/hibernate/dataAccessContext-local.xml"});
stockDao = (StockDao)ctx.getBean("stockDao");
jdbcTemplate = (JdbcTemplate)ctx.getBean("jdbcTemplate");
category = new Category("RABBIT");
category.setName("Rabbit");
category.setDescn("Desciption of Rabbit");
product1 = new Product("RABBIT-01");
product2 = new Product("RABBIT-02");
product1.setName("WhiteRabbit");
product1.setDescn("Description of WhiteRabbit");
product2.setName("BlackRabbit");
product2.setDescn("Description of BlackRabbit");
category.addProducts(product1);
category.addProducts(product2);
}
public void testCreateCategory() {
stockDao.createCategoryCascade(category);
assertEquals(3, getCategoryAndProductSizeManually());
}
public void testDeleteCategory() {
if(getCategoryAndProductSizeManually()==0) {
stockDao.createCategoryCascade(category);
}
stockDao.deleteCategoryCascade(category);
assertEquals(0, getCategoryAndProductSizeManually());
}
public void testRetrieveProductByCategory() {
if(getCategoryAndProductSizeManually()==0) {
stockDao.createCategoryCascade(category);
}
List productList = stockDao.retrieveProductBy(category);
assertEquals(2, productList.size());
}
private int getCategoryAndProductSizeManually() {
int categorySize = jdbcTemplate.queryForList(
"SELECT * FROM CATEGORY WHERE CATID='RABBIT'").size();
int productSize = jdbcTemplate.queryForList(
"SELECT * FROM PRODUCT " +
"WHERE PRODUCTID='RABBIT-01' OR PRODUCTID='RABBIT-02'").size();
return (categorySize + productSize);
}
}
预期地,该测试案例运行后将全部失败,这正是TDD第一步的效果,如图10.2所示。
图10.2 Spring结合Hibernate首次测试完败
10.4.4 TDD又来了:完善基础设施
检查后发现,错误的原因是没有在Spring配置文件中定义stockDao。
向代码10.20中追加三个定义:stockDao、HibernateTemplate、jdbcTemplate,如下:
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
<bean id="hibernateTemplate"
class="org.springframework.orm.hibernate3.HibernateTemplate">
<constructor-arg ref="sessionFactory"/>
</bean>
<bean id="stockDao"
class="chapter10.spring.hibernate.HibernateStockDao">
<property name="template" ref="hibernateTemplate"/>
</bean>
再次运行测试案例,可以看到,基础设施已经搭建成功,现在依然存在的错误是未实现HibernateStockDao。
10.4.5 添加HibernateTemplate和HibernateCallback实现,交付测试
下面的任务就是使用HibernateTemplate和HibernateCallback来完成DAO的具体实现,如代码10.24所示。
代码10.24 HibernateStockDao.java
package chapter10.spring.hibernate;
import java.util.List;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.HibernateTemplate;
import chapter10.hibernate.domain.Category;
public class HibernateStockDao implements StockDao {
private HibernateTemplate template;
public void setTemplate(HibernateTemplate template) {
this.template = template;
}
public void createCategoryCascade(Category category) {
template.save(category);
}
public void deleteCategoryCascade(Category category) {
template.delete(category);
}
/**
* 使用HQL进行对象级别的查询
*/
public List retrieveProductBy(final Category category) {
final String catid = category.getCatid();
return (List)template.execute(new HibernateCallback() {
public Object doInHibernate(Session session)
throws HibernateException {
String category = "chapter10.hibernate.domain.Category";
List products =
session.createQuery("select category.products from "+
category+" category where category.catid='"+catid+"'").list();
return products;
}
});
}
}
HibernateTemplate提供了许多便利的方法,并且在代码中不需要处理包括Session的打开关闭,事务的开启,异常地捕捉等等操作。并且,该template也是线程安全的。通过HibernateCallback接口的回调,就可以和Hibernate的Session打交道了。
上例中,通过回调,可以在doInHibernate()方法内执行更多的处理,比如调用原生Sesssion上的方法。不必担心,在所有处理结束以后,HibernateTemplate仍会进行确保正确的Session打开和关闭以及事务的自动参与。其实对于简单的一步操作,如save、delete、load、find等,HibernateTemplate可以很好地替换这种繁琐的回调方法。比如上例中的retrieveProductBy()方法又可以改写如下:
public List retrieveProductBy(final Category category) {
String categoryAlias = "chapter10.hibernate.domain.Category";
return template.find("select category.products from "+
categoryAlias+" category where category.catid=?",
category.getCatid());
}
撰写完毕,运行早已准备好的测试案例,效果如图10.3所示。
图10.3 Spring结合Hibernate案例测试通过
除了以上对HibernateTemplate的使用方法,Spring还提供了一个便利的HibernateDaoSupport超类,这和用以支持JDBC的JdbcDaoSupport类以及用以支持IBatis的SqlMapClientDaoSupport类概念是一致的,改动的方法也大相径庭。
限于篇幅,这里不再列出,在随书源码中可以找到运用这个支持类的实现,它们分别是chapter10.spring.hibernate..HibernateStockDao2和dataAccessContext-support-local.xml。
10.4.6 声明式管理Hibernate本地事务
Spring提供了一种统一的IoC方式来管理Hibernate事务(本地或者分布式事务)。从Spring接手hibernate.cfg.xml(Hibernate的基本配置文件)起,Hibernate事务便轻易交由Spring拖管了。
说明:在上一章介绍IBatis和DAO的时候,曾经针对事务和DAO的关系简单的进行了探讨。通常DAO的粒度应该都是比较细的,即它们只是一些单步的CRUD操作,所以就需要引入一个业务对象来包裹DAO,这样,就可以在业务对象的基础上,提供更粗粒度的事务划分了(比如跨越多个DAO的方法调用进行事务管理)。
为了能对DAO进行更粗粒度的事务控制,需要为其增加一个业务对象。下面给出了该业务对象的接口和实现,如代码10.25~10.26所示。
代码10.25 StockFacade.java
package chapter10.spring.hibernate;
import chapter10.hibernate.domain.Category;
public interface StockFacade {
public void business1(Category category);
public void someOtherBusiness();
}
代码10.26 BusinessFacadeImpl.java
public class BusinessFacadeImpl implements StockFacade {
private StockDao stockDao;
public void setStockDao(StockDao stockDao) {
this.stockDao = stockDao;
}
public void business1(Category category) {
stockDao.createCategoryCascade(category);
stockDao.retrieveProductBy(category);
stockDao.deleteCategoryCascade(category);
}
public void someOtherBusiness() {
//other implemention
}
}
接着给出关于事务策略的配置,其中使用了Spring针对Hibernate3给出的HibernateTransactionManager,它提供了Hibernate的本地事务管理策略,如代码10.27所示。
代码10.27 transaction-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">①
<property name="sessionFactory" >
<ref bean="sessionFactory" />
</property>
</bean>
<bean id="business"
class="chapter10.spring.hibernate.BusinessFacadeImpl">
<property name="stockDao">
<ref bean="stockDao"/>
</property>
</bean>
<bean id="businessProxy"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<ref bean="business" />
</property>
<property name="transactionAttributes">
<props>
<!-- 运行在当前事务范围内,如果当前没有启动事务,那么创建一个新的事务-->
<prop key="business*">PROPAGATION_REQUIRED</prop>
<!-- 运行在当前事务范围内,如果当前没有启动事务,那么抛出异常-->
<prop key="someOtherBusiness*">PROPAGATION_MANDATORY</prop>
</props>
</property>
</bean>
</beans>
代码10.28 HibernateTransactionUsageTest.java
package chapter10.spring.hibernate;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import chapter10.hibernate.domain.Category;
import junit.framework.TestCase;
public class HibernateTransactionUsageTest extends TestCase {
private StockFacade stockBusiness;
protected void setUp() throws Exception {
String path = "ch10/spring/hibernate/";
ApplicationContext ctx = new ClassPathXmlApplicationContext(
new String[]{path+"dataAccessContext-support-local.xml",
path+"transaction-context.xml"});
stockBusiness = (StockFacade)ctx.getBean("businessProxy");
}
public void testTransctionUsage() {
Category category = new Category("RABBIT");
category.setName("Rabbit");
category.setDescn("Desciption of Rabbit");
stockBusiness.business1(category);
}
}
10.4.7 声明式管理Hibernate分布式事务
通过Spring,还可以很方便地切换至另一种事务管理策略。比如需要提供分布式事务管理策略时,只要替换一下配置即可,如代码10.29所示。
代码10.29 appContext-jta.xml
<beans>
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="sessionFactory" >
<ref bean="sessionFactory" />
</property>
</bean>
<bean id="myDataSource1"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jdbc/myds1</value>
</property>
</bean>
<bean id="myDataSource2"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jdbc/myds2</value>
</property>
</bean>
<bean id="sessionFactory1"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource1"/>
<property name="configLocations">
<value>hibernate.cfg1.xml</value>
</property>
</bean>
<bean id="sessionFactory2"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource" ref="myDataSource2"/>
<property name="configLocations">
<value>hibernate.cfg2.xml</value>
</property>
</bean>
<bean id="dao1"
class="daopackage1.DaoImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<bean id="dao2"
class="daopackage2.DaoImp2">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
<bean id="business" class="businesspackage.BusinessFacadeImpl">
<property name="dao1">
<ref bean="dao1"/>
</property>
<property name="dao2">
<ref bean="dao2"/>
</property>
</bean>
<bean id="businessProxy"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<ref bean="business" />
</property>
<property name="transactionAttributes">
<props>
<prop key="business*">PROPAGATION_REQUIRED</prop>
<prop key="someOtherBusiness*">PROPAGATION_MANDATORY</prop>
</props>
</property>
</bean>
</beans>
可以看到,对于横跨多个Hibernate SessionFacotry的分布式事务,只需简单地将JtaTransactionManager和LocalSessionFactoryBean的定义结合起来就可以了,其中每个DAO通过bean属性得到各自的SessionFactory引用。
说明:如果所有底层数据源都是支持事务的容器,那么只需要对一个业务对象应用JtaTransactionManager策略,该对象就可以横跨多个DAO和多个Session Factory来划分事务了。使用Spring的最大好处就是,可通过配置来声明式地管理事务,无需对应用代码作任何改动。