作者:Ted He;alilo(作者的blog:http://blog.matrix.org.cn/page/alilo)
摘要
Hibernate和struts是當前市面上幾個(gè)最流行的開(kāi)源的庫之一。它們很有效率,是程序員在開(kāi)發(fā)Java企業(yè)應用,挑選幾個(gè)競爭的庫的首選。雖然它們經(jīng)常被一起應用,但是Hibernate的設計目標并不是和Struts一起使用,而Struts在Hibernate誕生好多年之前就發(fā)布了。為了讓它們在一起工作,仍然有很多挑戰。這篇文章點(diǎn)明了Struts和Hibernate之間的一些鴻溝,尤其關(guān)系到面向對象建模方面。文章也描述了如何在兩者間搭起橋梁,給出了一個(gè)基于擴展Struts的解決方案。所有的基于Struts和Hibernate構建的Web應用都能從這個(gè)通用的擴展中獲益。
在Hibernate in Action(Manning,2004十月)這本書(shū)里,作者Christian Bauer和Gavin King揭示了面向對象世界的模型和關(guān)系數據模型,兩個(gè)世界的范例是不一致的。Hibernate非常成功地在存儲層(persistence Layer)將兩者粘合在一起。但是領(lǐng)域模型(domain model)(也就是Model-View-Controller的model layer)和HTML頁(yè)面(MVC的View Layer)仍然存在不一致。在這篇文章中,我們將檢查這種不一致,并且探索解決的方案。
范例不一致的再發(fā)現
讓我們先看一個(gè)經(jīng)典的parent-child關(guān)系例子(看下面的代碼):product和category。Category類(lèi)定義了一個(gè)類(lèi)型為long的標示符id和一個(gè)類(lèi)型為String的屬性name。Product類(lèi)也有一個(gè)類(lèi)型為long的標示符id和一個(gè)類(lèi)型為Category的屬性category,表示了多對一的關(guān)系(也就是說(shuō)很多product可以屬于一個(gè)Category)
/**
* @hibernate.class table="CATEGORY"
*/
public class Category {
private Long id;
private String name;
/**
* @hibernate.id generator-class="native" column="CATEGORY_ID"
*/
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
/**
* @hibernate.property column="NAME"
*/
public String getName() {
return name;
}
public void setName(Long name) {
this.name = name;
}
}
/**
* @hibernate.class table="PRODUCT"
*/
public class Product {
private Long id;
private Category category;
/**
* @hibernate.id generator-class="native" column="PRODUCT_ID"
*/
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
/**
* @hibernate.many-to-one
* column="CATEGORY_ID"
* class="Category"
* cascade="none"
* not-null="false"
*/
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
}
<select name="categoryId">
<option value="">No Category</option>
<option value="1">Category 1</option>
<option value="2">Category 2</option>
<option value="3">Category 3</option>
</select>
<html:select property="category.id">
<option value="">No Category</option>
<html:options collection="categories" property="id" labelProperty="name"/>
</html:select>
public class ProductForm extends ActionForm {
private Long id;
private Category category;
...
} public class ProductForm extends ActionForm {
private Long id;
private Category category;
...
public void reset(ActionMapping mapping, HttpServletRequest request)
{
super.reset( mapping, request );
if ( category == null ) { category = new Category(); }
}
} public class EditProductAction extends Action {
public final ActionForward execute( ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response ) throws Exception
{
...
Product product = createOrLoadProduct();
ProductForm productForm = (ProductForm)form;
PropertyUtils.copyProperties( productForm, product );
productForm.reset( mapping, request );
...
}
} Product product = createOrLoadProduct();
PropertyUtils.copyProperties( form, product );
form.reset( mapping, request );
public class ProductForm extends ActionForm {
private Long id;
private Category category;
...
public void reset(ActionMapping mapping, HttpServletRequest request) {
super.reset( mapping, request );
if ( category == null ) { category = new Category(); }
}
public void cleanupEmptyObjects() {
if ( category.getId() == null ) { category = null; }
}
}public class SaveProductAction extends Action {
public final ActionForward execute( ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response ) throws Exception
{
...
Product product = new Product();
((ProductForm)form).cleanupEmptyObjects();
PropertyUtils.copyProperties( product, form );
SaveProduct( product );
...
}
} public class Category {
...
private Set products;
...
/**
* @hibernate.set
* table="PRODUCT"
* lazy="true"
* outer-join="auto"
* inverse="true"
* cascade="all-delete-orphan"
*
* @hibernate.collection-key
* column="CATEGORY_ID"
*
* @hibernate.collection-one-to-many
* class="Product"
*/
public Set getProducts() {
return products;
}
public void setProducts(Set products) {
this.products = products;
}
} public class CategoryForm extends ActionForm {
private Set productForms;
...
public void reset(ActionMapping mapping, HttpServletRequest request) {
super.reset( mapping, request );
for ( int i = 0; i < MAX_PRODUCT_NUM_ON_PAGE; i++ ) {
ProductForm productForm = new ProductForm();
productForm.reset( mapping, request );
productForms.add( productForm );
}
}
public void cleanupEmptyObjects() {
for ( Iterator i = productForms.iterator(); i.hasNext(); ) {
ProductForm productForm = (ProductForm) i.next();
productForm.cleanupEmptyObjects();
}
}
} import java.beans.PropertyDescriptor;
import org.apache.commons.beanutils.PropertyUtils;
import org.hibernate.metadata.ClassMetadata;
public abstract class AbstractForm extends ActionForm {
public void reset(ActionMapping mapping, HttpServletRequest request) {
super.reset( mapping, request );
// Get PropertyDescriptor of all bean properties
PropertyDescriptor descriptors[] =
PropertyUtils.getPropertyDescriptors( this );
for ( int i = 0; i < descriptors.length; i++ ) {
Class propClass = descriptors[i].getPropertyType();
ClassMetadata classMetadata = HibernateUtil.getSessionFactory()
.getClassMetadata( propClass );
if ( classMetadata != null ) { // This is a Hibernate object
String propName = descriptors[i].getName();
Object propValue = PropertyUtils.getProperty( this, propName );
// Evaluate property, create new instance if it is null
if ( propValue == null ) {
PropertyUtils.setProperty( this, propName, propClass.newInstance() );
}
}
}
}
public void cleanupEmptyObjects() {
// Get PropertyDescriptor of all bean properties
PropertyDescriptor descriptors[] =
PropertyUtils.getPropertyDescriptors( this );
for ( int i = 0; i < descriptors.length; i++ ) {
Class propClass = descriptors[i].getPropertyType();
ClassMetadata classMetadata = HibernateUtil.getSessionFactory()
.getClassMetadata( propClass );
if ( classMetadata != null ) { // This is a Hibernate object
Serializable id = classMetadata.getIdentifier( this, EntityMode.POJO );
// If the object id has not been set, release the object.
// Define application specific rules of not-set id here,
// e.g. id == null, id == 0, etc.
if ( id == null ) {
String propName = descriptors[i].getName();
PropertyUtils.setProperty( this, propName, null );
}
}
}
}
}
聯(lián)系客服