6.6 问题:Spring如何支持Hibernate
Spring和Hibernate是目前非常流行的两个开源框架,Hibernate作为本书的重点,其在J2EE开源项目中的里程碑意义更是有目共睹,通过Spring和Hibernate的整合,将给应用程序带来高度的灵活性和强大的功能。设计师L在本节中,将以宠物店购物车中两个领域对象的关联关系展开,逐步介绍Spring集成Hibernate的方法。
6.6.1 ER分析
设计师L观察了一下宠物店购物车中的领域对象,发现有两个很适合本小节演示的领域对象,它们分别是代表商品种类的Category和代表该商品种类下具体产品的Product,这两个领域对象之间的关系相对比较简单,用Hibernate来映射这两个对象实体再恰当不过,于是L首先在宠物店的DB Schema中找到了这两个实体所对应的Table脚本,代码如下:
create table category (
catid varchar(10) not null,
name varchar(80) null,
descn varchar(255) null,
constraint pk_category primary key (catid)
);
create table product (
productid varchar(10) not null,
category varchar(10) not null,
name varchar(80) null,
descn varchar(255) null,
constraint pk_product primary key (productid),
constraint fk_product_1 foreign key (category)
references category (catid)
);
有了对领域模型的抽象理解,并且有了现成的Table脚本,这使得L非常容易便画出了相对应的E-R关系图,如图6.3所示。

图6.3 Category和Product 对象的E-R图
可以看到,Category和Product之间是一对多的关系。
6.6.2 领域对象映射
在有了以上的ER分析后,L明显感觉到,原来宠物店提供的Category和Product模型,已经不适合作为Hibernate的领域对象,这里假设读者已经熟悉Hibernate的相关概念。于是L动手重写了Category和Product这两个领域对象,主要的改变是在其中加入了一些对象关联代码,见例6.48和例6.49。
例6.48:Category.java
package springhibernate;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Set;
public class Category implements Serializable {
private String catid;
private String name;
private String descn;
/**
* 持有Product对象的集合并由Hibernate负责同步数据库
* 此处还表示了Category和Product间的one-to-many关联
*/
private Set products = new HashSet();
public Category() {
}
public Category(String catid) {
this.catid = catid;
}
public String getCatid() {
return this.catid;
}
public void setCatid(String catid) {
this.catid = catid;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getDescn() {
return this.name;
}
public void setDescn(String descn) {
this.descn = descn;
}
public void addProduct(Product product) {
//这里表示Product和Category是一个双向关联
product.setCategory(this);
//这个方法是一个透明持久化动作,Hibernate Session会
//同步对象状态和数据库
this.getProducts().add(product);
}
}
例6.49:Product.java
package springhibernate;
import java.io.Serializable;
public class Product implements Serializable {
private String productid;
private Category category;
private String name;
private String descn;
public Product() {
}
public Product(String productid) {
this.setProductid(productid);
}
public String getProductid(String productid) {
return this.productid;
}
public void setProductid(String productid) {
this.productid = productid;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getDescn() {
return this.name;
}
public void setDescn(String descn) {
this.descn = descn;
}
public Category getCategory() {
return this.category;
}
public void setCategory(Category category) {
this.category = category;
}
}
有了领域对象的实体代码后,还需要给出它们各自的Hibernate映射文件,见例6.50和例6.51。
例6.50:Category.hbm.xml
<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="springhibernate">
<class name="Category" table="category">
<id name="catid" column="catid"/>
<property name="name" column="name"/>
<property name="descn" column="descn"/>
<!-- 映射集合和一对多关联
cascade="all"代表了级联所有的操作,包括新增、修改或者删除等
-->
<set name="products" inverse="true" cascade="all">
<key column="category" />
<one-to-many class="Product" />
</set>
</class>
</hibernate-mapping>
例6.51:Product.hbm.xml
<?xml version="1.0" encoding='UTF-8'?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" >
<hibernate-mapping package="springhibernate">
<class name="Product" table="product">
<id name="productid" column="productid"/>
<property name="name" column="name"/>
<property name="descn" column="descn"/>
<!-- many-to-one元素,
这里定义了一种与另一个持久化类的关联,即Product和Category是双向关联-->
<many-to-one name="category"
column="category"
class="Category"
not-null="true"/>
</class>
</hibernate-mapping>
至此,领域对象的映射工作就算完成了,在下文中L将通过DAO模式作进一步的封装,并且通过Spring作进一步的整合、简化和完善。
6.6.3 使用Spring简化DAO
和上文中所举的JDBC DAO辅助类JdbcDaoSupport以及IBatis DAO辅助类SqlMapClientDaoSupport一致,Spring针对Hibernate也提供了DAO辅助类,即HibernateDaoSupport。同理,继承自HibernateDaoSupport的子类可以通过调用父类方法getHibernateTemplate()来获取HibernateTemplate,从而使用其上的便利方法。
有了以上的认知后,L首先给出了一个假想的DAO接口,见例6.52。
例6.52:BusinessDao.java
package springhibernate;
import java.util.List;
public interface BusinessDao {
public Category createCategoryAndItsProduct(Category category);
public void deleteCategoryAndItsProduct(Category category);
/**
* 根据Category,查找Product集合
*/
public List findProductsBy(Category category);
}
接着,L给出继承自HibernateDaoSupport的具体DAO实现子类,见例6.53。
例6.53:BusinessDaoImpl.java
package springhibernate;
import java.util.List;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
public class BusinessDaoImpl extends HibernateDaoSupport
implements BusinessDao {
public Category createCategoryCascade(Category category) {
return (Category)getHibernateTemplate().save(category);
}
public void deleteCategoryCascade(Category category) {
getHibernateTemplate().delete(category);
}
public List findProductsBy(Category category) {
String categoryAlias = "springhibernate.Category";
return
getHibernateTemplate().find("select category.products from "+
categoryAlias+" category where category.catid=?",
category.getCatid());
}
}
6.6.4 整合配置
在备齐了以上这些代码、配置后,L将给出最重要的一些关于如何整合Spring和Hibernate的配置。熟悉Hibernate的读者应该知道,Hibernate中有两个最为重要的组件:Session以及它的工厂SessionFactory,在Hibernate中最常见的配置方式是使用hibernate.cfg.xml来进行SessionFactory和一些基本参数的配置。那么在Spring介入之后,hibernate.cfg.xml将会产生什么变化,又会额外增加一些其它什么配置呢?在研究了相关的资料后,L首先对hibernate.cfg.xml进行了一些改变,见例6.54。
例6.54: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>
<!-- 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="springhibernate/Category.hbm.xml" />
<mapping resource="springhibernate/Product.hbm.xml" />
</session-factory>
</hibernate-configuration>
可以发现,基本的Hibernate配置中移除了关于JDBC数据源的配置,这些配置将交由Spring进行配置,这和L猜想的也基本吻合,顺着这个思路,于是L撰写了Spring的相关配置,见例6.55。
例6.55:dao-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="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/springhibernate"/>
<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>springhibernate/hibernate.cfg.xml</value>
</property>
</bean>
<bean id="businessDao"
class="springhibernate.BusinessDaoImpl">
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
6.6.5 事务控制
最后,和上文的方式一样,为了演示事务控制,L增加出了业务对象,以提供事务边界方法,这些方法通常是跨越多个DAO方法调用的,见例6.56和例6.57。
例6.56:Business.java
package springhibernate;
public interface Business {
public void doSomeBusiness1(Category category, List productList);
//省略...
}
例6.57:BusinessImpl.java
package springhibernate;
public class BusinessImpl implements Business {
private BusinessDao businessDao;
public void setBusinessDao(BusinessDao businessDao) {
this.businessDao = businessDao;
}
public void doSomeBusiness1(Category category, List productList) {
Category createdCategory = businessDao.createCategory(category);
for(Iteration iter = productList.iterator();iter.hasNext()) {
Product product = (Product)iter.next();
createdCategory.addProduct(product);
}
businessDao.findProductsBy(category);
businessDao.deleteCategory(category);
}
//省略...
}
接着,L给出了关于事务控制的配置,其中使用了Spring给出的Hibernate TransactionManager,见例6.58。
例6.58: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="businessBean"
class="springhibernate.BusinessImpl">
<property name="businessDao">
<ref bean="businessDao"/>
</property>
</bean>
<bean id="businessProxy"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="target">
<ref bean="businessBean" />
</property>
<property name="transactionAttributes">
<props>
<!-- 如果当前没有启动事务,那么创建一个新的事务 -->
<prop key="doSomeBusiness*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
</beans>
最后,L给出相关的客户测试代码,见例6.59。
例6.59:SpringHibernateTest.java
package springhibernate;
import java.util.List;
import java.util.ArrayList;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class SpringHibernateTest {
public static void main(String[] args) {
String path = "springhibernate/";
ApplicationContext springContext = new ClassPathXmlApplicationContext(
new String[]{path+"dao-context.xml", path+"transaction-context.xml"});
Business = (Business)springContext.getBean("businessProxy");
Category category = new Category("Food1");
category.setName("Fast Food");
category.setDescn("Desciption of Fast Food");
product1 = new Product("Fast Food-01");
product2 = new Product("Fast Food-02");
product1.setName("KFC");
product1.setDescn("Description of KFC");
product2.setName("McDonalds");
product2.setDescn("Description of McDonalds");
List productList = new ArrayList();
productList.add(product1);
productList.add(product2);
business.doSomeBusiness1(category, productList);
}
}