Build bridges between struts and Hibernate

Source: Internet
Author: User
Hibernate and struts are one of the most popular open-source libraries on the market. They are very efficient and are the first choice for programmers to develop Java Enterprise applications and select several competing libraries. Although they are often used together, the design goal of Hibernate is not to be used together with Struts, and struts was released many years ago when hibernate was born. There are still many challenges for them to work together. This article points out some gaps between struts and hibernate, especially for object-oriented modeling. The article also describes how to build a bridge between the two and provides a solution based on extended struts. All Web applications built based on Struts and hibernate can benefit from this general extension.

In the hibernate in Action (Manning, 2004, October) book, authors Christian Bauer and Gavin King reveal object-oriented models and relational data models. The two world examples are inconsistent. Hibernate has been very successful in bonding the two on the storage layer (persistence layer. However, the domain model (model layer of Model-View-Controller) and the HTML page (view layer of MVC) are still inconsistent. In this article, we will check this inconsistency and explore solutions.

  When examples are inconsistent

Let's take a look at a classic parent-child relationship example (see the following code): product and category. The category class defines a long-type identifier ID and a string-type attribute name. The product class also has a long identifier ID and a category attribute, indicating the relationship between multiple to one (that is, many products can belong to one category)


The following is a reference clip:

/**
* @ 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;

/**
* @ Hibernate. ID generator-class = "native" column = "product_id"
*/
Public long GETID (){
Return ID;
}

Public void setid (long ID ){
This. ID = ID;
}

/**
* @ Hibernate. Allow-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;
}
}

We want a product to be changed to category, so our html provides a drop-down box to list all 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>

Here we see the inconsistency between the two: in the product domain object, the category attribute is of the category type, but the productform has only one categoryid of the long type. This mismatch not only adds inconsistencies, but also leads to unnecessary code for conversion between the primitive type identifier and the corresponding object.

This inconsistency is partly caused by HTML form itself: it only represents a relational model and cannot represent an object-oriented model. The inconsistency between the object-oriented model and the relational model is solved by the object relationship ing (O/RM) in the storage layer. However, similar issues still exist in the presentation layer. The key to the solution is to allow them to work seamlessly together.

 

 

  Struts functions and limitations

Fortunately, Struts can generate and interpret embedded object attributes. You can use struts page-construction (HTML) Tag library in the category drop-down box:

<HTML: Select Property = "category. ID">
<Option value = ""> NO Category </option>
<HTML: Options collection = "categories" property = "ID" labelproperty = "name"/>
</Html: Select>

Assume that categories is a list of category objects. So now we need to modify productform to make it more "Object-Oriented". We need to modify the categoryid of productform to the category type. This change will make copying attributes between product and productform more complicated because they have the same attributes.

Public class productform extends actionform {
Private long ID;
Private category;
...
}

When we finish the remaining struts action, configuration, validator, JSP, and hibernate layers and start testing, we immediately encounter nullpointerexception when accessing productform. Category. Id. This is expected! Because productform. category has not been set yet. At the same time, Hibernate will also set the reference of multiple-to-one objects to null (if the database field is empty. category is null. If this product is not associated with any category ). Struts requires that all objects be created before being displayed (generating HTML form) and propagated (submitting HTML form.

Let's see how to use actionform. Reset () to build a bridge.

  (Not So) the notorious struts actionform

When I first came into contact with Struts in the week, one of my biggest questions was: Why do I have to use properties, the getter method, and the setter method to maintain almost identical copies, one copy is in actionform bean and the other is in domainobject. This tedious step has become one of the major complaints of the struts community.

In my opinion, there is a reason for actionform. First, they can be different from domain objects because they have different roles. In MVC mode, domain object is a part of the model layer, and actionform is a part of the view layer. Because the field of webpage and database may be different, some special conversions are common. Second, the actionform. Validate () method can define very useful verification rules. Third, there may be other specific view behaviors, but they do not want to be implemented in the domain layer, especially when the persistence framework manages domain objects.

  Submit Form

Let's use the reset () method in actionform to solve the inconsistency between the view and model. This reset () method is called before the actionform attribute is copied during request processing by the struts controller servlet. The most common use of this method is that the checkbox must be explicitly set to false, so that the unselected checkbox is correctly recognized. Reset () is also a suitable place to initialize the view rending object. The code looks like this:

Public class productform extends actionform {
Private long ID;
Private category;
...
Public void reset (actionmapping mapping, httpservletrequest request)
{
Super. Reset (mapping, request );
If (Category = NULL) {Category = new category ();}
}
}

Before using the value submitted by the user to enter productform, Struts will call reset (), so that the category attribute will be initialized. Please note that you must check the category to see if it is null. We will discuss this later.

  Edit form

So far, we have solved the problem of Form submission. But what happens when we generate the form page? HTML: Select tag also requires a non-empty reference, so we will call reset () before the form generates the page (). We added a line in the action class:

Public class editproductaction extends action {
Public final actionforward execute (actionmapping mapping, actionform form,
Httpservletrequest request, httpservletresponse response) throws exception
{
...
Product = createorloadproduct ();
Productform = (productform) form;
Propertyutils. copyproperties (productform, product );
Productform. Reset (mapping, request );
...
}
}

I assume that you are familiar with the action class and Jakarta commons beanutils package. Createorloadproduct () creates a new product instance or loads an existing instance from the database, depending on which action creates or modifies the product. After productform is assigned a value (TRANSLATOR: Call propertyutils. copyproperties), productform. category has been removed from product. category is copied (TRANSLATOR: Actually, it only copies the reference of the category object, and there is no overhead). Then, productform can be used to generate pages. At the same time, we must ensure that the objects loaded by hibernate are not overwritten. Therefore, we must check whether (Category) is null.

Because the reset () method is defined in actionform, we can put the above code into a superclass, such as commoneditaction, to handle these tasks:

Product = createorloadproduct ();
Propertyutils. copyproperties (Form, product );
Form. Reset (mapping, request );

If you need a read-only form, you have two options: First, check whether the associated JSP object is null, and second, copy the domain object to the actionform and then call reset ()

 

  Save domain object

We solved the problem of submitting the form and generating the form page, so struts can satisfy it. But what about hibernate? When you select a null ID Option-in our example, "No category" option-and submit form, productform. Category points to a newly created hibernate object with ID null. When the category attribute is copied from productform to the product object controlled by Hibernate and stored, Hibernate will complain that product. category is a temporary object and needs to be stored before the product is stored. Of course, we know that it is null and does not need to be stored. So we need to set product. category to null, and then hibernate can store the product (TRANSLATOR: In this case, the database product. category is set to a null value ). We do not want to change the way hibernate works, so we chose to clear these temporary objects before copying them to the domain object. We added a method in productform:


Public class productform extends actionform {
Private long ID;
Private 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 ;}
}
}

We cleared these temporary objects before copyproperties. Therefore, if productform. category is only used to put null, set productform. category to null. Then the domain object category will be set to NULL:

Public class saveproductaction extends action {
Public final actionforward execute (actionmapping mapping, actionform form,
Httpservletrequest request, httpservletresponse response) throws exception
{
...
Product = new product ();
(Productform) Form). cleanupemptyobjects ();
Propertyutils. copyproperties (product, form );
Saveproduct (product );
...
}
}

  One-to-multiple relationship

I have not solved the one-to-multiple relationship between category and product. We add it to the metadata of category:

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-least
* Class = "product"
*/
Public set getproducts (){
Return products;
}

Public void setproducts (set products ){
This. Products = products;
}
}

Note: The cascade attribute of Hibernate is all-delete-orphan, indicating that hibernate automatically stores the product object when storing the included category object. It is not common to store child objects with parent objects. It is common to control the storage of child and parent respectively. In our example, we can easily do this if we allow users to edit category and products on the same HTML page. Using set to represent products is very intuitive:

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 = new productform ();
Productform. Reset (mapping, request );
Productforms. Add (productform );
}
}

Public void cleanupemptyobjects (){
For (iterator I = productforms. iterator (); I. hasnext ();){
Productform = (productform) I. Next ();
Productform. cleanupemptyobjects ();
}
}
}

 

  Further steps

We can check, edit, submit forms, and store related objects, but defining cleanupemptyobjects () and reset () methods for all actionform classes is cumbersome. We will use an abstract actionform to complete these tasks.

As a general implementation, we must traverse all domain objects managed by hibernate, find their identifier, and test the id value. Fortunately, the org. hibernate. Metadata Package already has two Utility Classes that can retrieve the metadata of the domain object. We use the classmetadata class to check whether the object is managed by hibernate. If it is: We take out their ID values. We use the Jakarta commons beanutils package to help operate the JavaBean metadata.

Import java. Beans. propertydescriptor;
Import org. Apache. commons. beanutils. propertyutils;
Import org. hibernate. Metadata. classmetadata;

Public abstract class extends actform 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 = 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 = 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 );
}

}
}
}
}

To make the code readable, The Exception Processing code is omitted.

Our new AbstractForm class inherits from the actionform class of struts and provides common behavior: reset and cleanup multiple-to-one correlated object. If the relationship is the opposite (that is, the one-to-many relationship), then each example will be different. Similar implementation in abstract class is a better method.

  Summary

Struts and Hibernate are very popular and powerful frameworks. They can effectively cooperate with each other and make up for the difference between the domain model and the MVC view. This article discusses a general solution to solve the struts/hibernate project, and does not need to modify a lot of existing code.

Author: Ted he; alilo (author's blog: http://blog.matrix.org.cn/page/alilo)
Original article: http://www.matrix.org.cn/resource/article/44/44391_Struts+Hibernate.html

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.