
Professional Java.JDK.5.Edition (Wrox)
.pdf
Chapter 8
Now, before you start dismissing the two models as inconsistent, realize that this is a hiccup in two otherwise very compatible techniques. So, there is clearly a motivation to try to make the two work together more cleanly.
This is where the HibernateInterceptor comes into play. The short answer to what it does is allow you to maintain an open Hibernate session during the rendering of your view, because it intercepts the request model going out and closes the session cleanly.
Here is what the HibernateInterceptor looks like:
package org.advancedjava.ch08.interceptor; import net.sf.hibernate.HibernateException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.advancedjava.ch08.HibernateAction;
import org.advancedjava.ch08.component.HibernateSession; import com.opensymphony.xwork.Action;
import com.opensymphony.xwork.ActionInvocation; import com.opensymphony.xwork.interceptor.Interceptor;
public class HibernateInterceptor implements Interceptor { private static final Log LOG =
LogFactory.getLog(HibernateInterceptor.class); public void destroy() {
}
public void init() {
}
public String intercept(ActionInvocation invocation) throws Exception {
Action action = invocation.getAction(); if (!(action instanceof HibernateAction))
return invocation.invoke(); HibernateSession hs =
((HibernateAction) action).getHibernateSession(); try {
return invocation.invoke();
}
//Note that all the cleanup is done
//after the view is rendered, so we
//have an open session in the view catch (Exception e) {
hs.setRollBackOnly(true);
if (e instanceof HibernateException) { LOG.error(“HibernateException in execute()”, e); return Action.ERROR;
}else {
LOG.error(“Exception in execute()”, e); throw e;
}
}finally { try {
hs.disposeSession();
}catch (HibernateException e) { LOG.error(“HibernateException in dispose()”, e);
return Action.ERROR;
}
376

Developing Web Applications Using the Model 2 Architecture
}
}
}
This brings the discussion back to the aforementioned HibernateAction class. Simply extending this class will allow your other actions to use these advantages rather transparently:
package org.advancedjava.ch08;
import net.sf.hibernate.HibernateException; import net.sf.hibernate.Session;
import org.advancedjava.ch08.component.HibernateSession; import org.advancedjava.ch08.component.HibernateSessionAware; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.opensymphony.xwork.ActionSupport;
public abstract class HibernateAction extends ActionSupport
implements HibernateSessionAware { private static final Log LOG =
LogFactory.getLog(HibernateAction.class); private HibernateSession session;
public String execute() throws Exception { if (hasErrors()) {
LOG.debug(“action not executed, field or action errors”); LOG.debug(“Field errors: “ + getFieldErrors()); LOG.debug(“Action errors: “ + getActionErrors());
return INPUT;
}
LOG.debug(“executing action”); return go();
}
protected abstract String go() throws HibernateException; public void setHibernateSession(HibernateSession session) {
this.session = session;
}
public HibernateSession getHibernateSession() { return session;
}
/**
* Get the Hibernate Session instance */
protected Session getSession() throws HibernateException { return session.getSession();
}
protected void setRollbackOnly() { session.setRollBackOnly(true);
}
}
Now that you have seen all that the WebWork2 and Hibernate frameworks have to offer, it is time to move on to a more concrete example of using them — your contact manager.
377

Chapter 8
Defining Your Domain Model
One of the first things you will do in your project is get your hands around what things your system will manage. Whether you call them entities or objects, and no matter where you store them (in a database or file system), there are still a set of conceptual classes that hold your system (and your business) together.
When it comes to defining your domain model, you have three things to consider:
What domain objects already exist in the form of databases, documents, and so on?
If I don’t have them, what is available to help me make them up?
What if my domain already exists, but is unsatisfactory for the users?
In essence, you generally fall into one of these two scenarios: Either you already are managing this data, in which case you are probably already in possession of a database, or you are starting from something new. The third way, having something already and needing something new, is the most painful.
Since data modeling and data migration are beyond the scope of this chapter (and book), focus instead on the domain on a basic model. Figure 8-6 demonstrates the conceptual classes for our small contact management system.
Contact |
|
|
|
|
|
|
|
-id : Long |
|
|
|
|
|
Phone |
|
-lastName : String |
|
|
|
|
|
|
|
|
|
-id : Long |
|
-firstName : String |
|
|
|
-im : String |
|
|
-phoneNumber : String |
|
|
||
-email : String |
1 |
1 |
-phoneType : String |
-phone : Phone |
|
||
|
|
|
|
-expertises : Set |
|
|
|
|
|
|
|
|
|
|
|
*
*
Expertise
-id : Long
-title : String
-description : String
Figure 8-6
378

Developing Web Applications Using the Model 2 Architecture
This model provides you with a basic capability to track a person and their relevant phone number and expertise. A person can be related to many expertise objects, but only one phone number, as well as the inverse being true. There are many experts in Java, and someone can be an expert in many things.
There are no methods represented in this diagram, although you could assign behaviors to domain objects if they were to make sense. An example of where you may want to capture a behavior would be something like a calculator object where you would have obvious domain behaviors. The diagram in Figure 8-6 demonstrates what is the primary domain of this application — storing information on contacts and their expertise.
Models really embody the core concept behind Object Oriented Programming, creating software objects that represent the system-relevant attributes and behaviors of a real-world object.
Now, the model is turned into a set of JavaBeans, with accessor and mutator methods (the “getters and setters”). For the sake of brevity, you will not have to look through all of them here, but instead they are provided with the source on the companion Web site.
Hibernate is going to handle our object persistence to a database, so you should take a look at the Hibernate mapping file to see how the object and the database model are resolved. In this first section, you declare your mapping package, and the first part of your first class, Contact. In there you define your basic properties and to which columns they bind. An interesting thing to note here: You are letting the database handle creating unique Ids for your contact objects, so you should leave the id property alone in your code. It will be null until the database assigns it an identifier:
<?xml version=”1.0”?>
<!DOCTYPE hibernate-mapping PUBLIC “-//Hibernate/Hibernate Mapping DTD 2.0//EN”
“http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd”> <hibernate-mapping package=”org.advancedjava.ch08.model”>
<class name=”Contact” table=”contacts”> <id name=”id” column=”ID”
unsaved-value=”null”> <generator class=”increment”/>
</id>
<property name=”firstName” column=”FIRST_NAME” /> <property name=”lastName” column=”LAST_NAME”/> <property name=”email” column=”EMAIL”/> <property name=”im” column=”IM”/>
In this next section of code, you are mapping the set of Expertise objects for a given Contact. Note how it specifies your conventional many-to-many join table, with the key column referring to the key of the containing object and the many-to-many column referring to the key of the related class:
<set name=”expertises” table=”contact_expertise” cascade=”save-update”>
<!-- the foreign key of the Contact -->
<key column=”CONTACT_ID”/> <many-to-many column=”EXPERTISE_ID”
class=”Expertise”/>
</set>
379

Chapter 8
The last section shows an example of mapping a complex type with a one-to-one relationship (phone), and then gives the mapping definitions for the other classes in our domain model. Note that generally mappings are defined each in its own file, but for brevity, they are defined together here. It is important to recognize that you must not define the same class twice:
<one-to-one name=”phone” cascade=”all”/> </class>
<class name=”Expertise” table=”expertise”> <id name=”id” column=”ID”
unsaved-value=”null”> <generator class=”increment”/>
</id>
<property name=”title” column=”TITLE” />
<property name=”description” column=”DESCRIPTION”/> </class>
<class name=”Phone” table=”phone”> <id name=”id” column=”ID”
unsaved-value=”null”> <generator class=”increment”/>
</id>
<property name=”phoneNumber” column=”PHONENUMBER” /> <property name=”phoneType” column=”PHONETYPE”/>
</class> </hibernate-mapping>
Wait a second! You may be thinking that now you have to go and create the database, being careful to set everything up to match this mapping file. You may also be thinking: “There is no way I am going to do this myself for this little sample application, where is the SQL script to load this database?”
Not so fast. Now that you have defined the semantics of how the database should look, you can just use Hibernate’s SchemaExport tool to create the database for you!
Along with the Hibernate distribution, there is a Windows batch file called SchemaExport.bat that looks a little like this (the actual paths are changed in this one from the one that ships with Hibernate):
@echo off
rem -------------------------------------------------------------------
rem Execute SchemaExport tool
rem -------------------------------------------------------------------
set JDBC_DRIVER=C:\jakarta-tomcat-4.1.24-LE-jdk14\webapps\contact\WEB- INF\lib\mysql-connector-java-3.0.9-stable-bin.jar
set HIBERNATE_HOME=..
set LIB=%HIBERNATE_HOME%\lib
set PROPS=C:\jakarta-tomcat-4.1.24-LE-jdk14\webapps\contact\WEB-INF\classes
set CP=%JDBC_DRIVER%;%PROPS%;%HIBERNATE_HOME%\hibernate2.jar;%LIB%\commons-logging- 1.0.3.jar;%LIB%\commons-collections-2.1.jar;%LIB%\commons-lang-1.0.1.jar;%LIB%\cgli b-2.0-rc2.jar;%LIB%\dom4j-1.4.jar;%LIB%\odmg-3.0.jar;%LIB%\xml- apis.jar;%LIB%\xerces-2.4.0.jar;%LIB%\xalan-2.4.0.jar
java -cp %CP% net.sf.hibernate.tool.hbm2ddl.SchemaExport C:\jakarta-tomcat-4.1.24- LE-jdk14\webapps\contact\WEB-INF\classes\org\advancedjava\ch08\model\Model.hbm.xml
380

Developing Web Applications Using the Model 2 Architecture
If you are using Linux or Unix, it probably goes without saying that you would have to change this script for the shell that you use. It is incredibly unlikely that you would choose to use Linux or Unix without understanding any of the shells. In essence, you are just building a big Java command for the tool, so conceivably you could type all of this by hand (and probably still save time over writing the DDL by hand!)
Note that somewhere in the classpath it will look for a properties file to tell it how to configure Hibernate for your purposes. The one that comes with the Hibernate distribution has a tremendous number of options and examples, so this one is simplified for our purposes. Here is what that properties file will look like for your MySQL implementation:
hibernate.query.substitutions true 1, false 0, yes ‘Y’, no ‘N’ hibernate.dialect net.sf.hibernate.dialect.MySQLDialect hibernate.connection.driver_class com.mysql.jdbc.Driver hibernate.connection.url jdbc:mysql:///contact hibernate.connection.username root hibernate.connection.password
hibernate.connection.pool_size 5
#Comment this out as soon as you have seen the SQL hibernate.show_sql true
#Left these in here to set properties for hbm2ddl #hibernate.hbm2ddl.auto create-drop #hibernate.hbm2ddl.auto create #hibernate.hbm2ddl.auto update
hibernate.jdbc.batch_size 0 hibernate.jdbc.use_streams_for_binary true hibernate.max_fetch_depth 1
#If you are having Hibernate problems, set this to true #very helpful for debug. #hibernate.cglib.use_reflection_optimizer false hibernate.cache.use_query_cache true
hibernate.cache.provider_class net.sf.ehcache.hibernate.Provider
Of course, most of the examples for other databases have been taken out for the sake of brevity, but you could easily substitute another database for this one. You will reuse this properties file later to configure your Web application:
<!DOCTYPE hibernate-configuration PUBLIC “-//Hibernate/Hibernate Configuration DTD//EN”
“http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd”>
<hibernate-configuration> <session-factory>
<mapping resource=”org/advancedjava/ch08/model/Model.hbm.xml”/> </session-factory>
</hibernate-configuration>
381

Chapter 8
It is very interesting to see what happens when you run the SchemaExport utility, as it offers much insight into how Hibernate operates. In this first section, Hibernate does its setup and configuration:
C:\hibernate-2.1.4\bin>SchemaExport
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Environment <clinit> INFO: Hibernate 2.1.4
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Environment <clinit>
INFO: loaded properties from resource hibernate.properties: {hibernate.connectio n.driver_class=com.mysql.jdbc.Driver, hibernate.cglib.use_reflection_optimizer=t rue, hibernate.cache.provider_class=net.sf.ehcache.hibernate.Provider, hibernate
.cache.use_query_cache=true, hibernate.max_fetch_depth=1, hibernate.dialect=net. sf.hibernate.dialect.MySQLDialect, hibernate.jdbc.use_streams_for_binary=true, h
ibernate.jdbc.batch_size=0, hibernate.query.substitutions=true 1, false 0, yes ‘ Y’, no ‘N’, hibernate.connection.username=root, hibernate.connection.url=jdbc:my sql:///contact, hibernate.connection.password=, hibernate.connection.pool_size=5
}
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Environment <clinit> INFO: using java.io streams to persist binary types
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Environment <clinit> INFO: using CGLIB reflection optimizer
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Configuration addFile
Now that it has configured the environment, it starts picking up the mapping files or, in this case, the only mapping file. On the first pass, it maps all of the entities, and then it does a second pass to map the relationships and constraints:
INFO: Mapping file: C:\jakarta-tomcat-4.1.24-LE-jdk14\webapps\contact\WEB-INF\cl asses\org\advancedjava\ch08\model\Model.hbm.xml
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Binder bindRootClass INFO: Mapping class: org.advancedjava.ch08.model.Contact -> contacts Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Binder bindCollection
INFO: Mapping collection: org.advancedjava.ch08.model.Contact.expertises -> cont act_expertise
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Binder bindRootClass INFO: Mapping class: org.advancedjava.ch08.model.Expertise -> expertise Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Binder bindRootClass INFO: Mapping class: org.advancedjava.ch08.model.Phone -> phone
Jun 12, 2004 3:15:46 PM net.sf.hibernate.dialect.Dialect <init> INFO: Using dialect: net.sf.hibernate.dialect.MySQLDialect
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Configuration secondPassCompile INFO: processing one-to-many association mappings
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Configuration secondPassCompile INFO: processing one-to-one association property references
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Configuration secondPassCompile INFO: processing foreign key constraints
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Configuration secondPassCompile INFO: processing one-to-many association mappings
Jun 12, 2004 3:15:46 PM net.sf.hibernate.cfg.Configuration secondPassCompile INFO: processing one-to-one association property references
Jun 12, 2004 3:15:47 PM net.sf.hibernate.cfg.Configuration secondPassCompile INFO: processing foreign key constraints
382

Developing Web Applications Using the Model 2 Architecture
Here it sets up its database connection, using the specified parameters (This is a good place to check if your database isn’t where you expect it.):
Jun 12, 2004 3:15:47 PM net.sf.hibernate.tool.hbm2ddl.SchemaExport execute INFO: Running hbm2ddl schema export
Jun 12, 2004 3:15:47 PM net.sf.hibernate.tool.hbm2ddl.SchemaExport execute INFO: exporting generated schema to database
Jun 12, 2004 3:15:47 PM net.sf.hibernate.connection.DriverManagerConnectionProvi der configure
INFO: Using Hibernate built-in connection pool (not for production use!)
Jun 12, 2004 3:15:47 PM net.sf.hibernate.connection.DriverManagerConnectionProvi
der configure
INFO: Hibernate connection pool size: 5
Jun 12, 2004 3:15:47 PM net.sf.hibernate.connection.DriverManagerConnectionProvi der configure
INFO: using driver: com.mysql.jdbc.Driver at URL: jdbc:mysql:///contact
Jun 12, 2004 3:15:47 PM net.sf.hibernate.connection.DriverManagerConnectionProvi der configure
INFO: connection properties: {user=root, password=}
Finally, it spits out the SQL that it will execute and reports on its success with the export. It then reports that it is cleaning up after itself:
drop table if exists phone drop table if exists contacts
drop table if exists contact_expertise drop table if exists expertise
create table phone (ID BIGINT not null, PHONENUMBER VARCHAR(255), PHONETYPE VARC HAR(255), primary key (ID))
create table contacts (ID BIGINT not null, FIRST_NAME VARCHAR(255), LAST_NAME VA RCHAR(255), EMAIL VARCHAR(255), IM VARCHAR(255), primary key (ID))
create table contact_expertise (CONTACT_ID BIGINT not null, EXPERTISE_ID BIGINT not null, primary key (CONTACT_ID, EXPERTISE_ID))
create table expertise (ID BIGINT not null, TITLE VARCHAR(255), DESCRIPTION VARC
HAR(255), primary key (ID))
alter table contact_expertise add index FK750E78B22540BDBA (CONTACT_ID), add con straint FK750E78B22540BDBA foreign key (CONTACT_ID) references contacts (ID) alter table contact_expertise add index FK750E78B2B1D44A29 (EXPERTISE_ID), add c onstraint FK750E78B2B1D44A29 foreign key (EXPERTISE_ID) references expertise (ID
)
Jun 12, 2004 3:15:47 PM net.sf.hibernate.tool.hbm2ddl.SchemaExport execute INFO: schema export complete
Jun 12, 2004 3:15:47 PM net.sf.hibernate.connection.DriverManagerConnectionProvi der close
INFO: cleaning up connection pool: jdbc:mysql:///contact
In this case, you are using the MySQL database, so you can execute the Show Tables command and view that they were actually created:
mysql> show tables;
+------------------- |
+ |
| Tables_in_contact | |
|
+------------------- |
+ |
| contact_expertise |
(continued)
383

Chapter 8
| contacts |
| |
| expertise |
| |
| phone |
| |
+------------------- |
+ |
5 rows in set (0.01 sec)
Now that you have handled the domain model for this application, it is time to bring this application to life and actually do something by implementing your Action classes.
Implementing Your Use Cases with Actions
So now what is it that your system does? Your use cases describe what a user hopes to achieve through interacting with your system. They describe the behavior of your system, or the actions that your system can provide. Now, the chicken and egg argument is only slightly older than the old software argument concerning whether you should describe your system’s behavior or structure first. In this case, you have described the structure first for two reasons:
1.This sample is an overwhelmingly data-centric application.
2.Because it is a data-centric application, the easiest way to restrict the scope is to define the data first.
Now, you develop the use cases that comprise this application. Here is a set of use cases for the system.
Use Case |
Description |
|
|
Browse Contacts |
If you specify a given expertise, it will display the contacts that have |
|
that expertise. |
Add Contact |
Gather the relevant information to add a contact to the contact manager. |
Remove Contact |
Remove a contact from the contact manager. |
|
|
Each of these use cases maps into an XWork Action. Here is the XWork Action used to handle the Browse Contacts use case. The interesting points about it are
It is just one plain old Java object; its simplicity is that it has methods for accessing and mutating its member variables and a go method to handle executing its intended function.
It extends HibernateAction providing easy access to the Hibernate framework.
It only takes a handful of lines of code to implement the use case. Even novice developers could start doing the basics very quickly. In this case, you execute a query based on the Id of the expertise for which you seek to find Contacts, and assign it to your List of contacts. Simply return SUCCESS; if anything should fail in terms of the database, query, and so on, it will be handled by the HibernateInterceptor:
package org.advancedjava.ch08; import java.util.List;
import net.sf.hibernate.HibernateException; import net.sf.hibernate.Query;
public class BrowseContactAction extends HibernateAction {
384

Developing Web Applications Using the Model 2 Architecture
private Integer expertiseId; private List contacts;
public String go() throws HibernateException { Query q =
getSession().createQuery(
“select con from Contact con join con.expertises as exp where exp.id =
:ids”);
q.setParameter(“ids”, expertiseId); contacts = q.list();
return SUCCESS;
}
/**
* @return */
public List getContacts() { return contacts;
}
/**
* @return */
public Integer getExpertiseId() { return expertiseId;
}
/**
* @param list */
public void setContacts(List list) { contacts = list;
}
/**
* @param integer */
public void setExpertiseId(Integer integer) { expertiseId = integer;
}
}
Of course, there is nothing to browse if you do not add contacts to the database. Here is the Action that adds a Contact into the database. Note a couple of interesting things here:
You are not handling the individual form elements or parameters and mapping them into the domain objects. WebWork is doing that for you, along with the tedious type conversion code.
Since you only got the Ids for the types of expertise, you need to pull the actual objects from the database and assign them as a set to your Contact object.
package org.advancedjava.ch08; import java.util.HashSet;
import net.sf.hibernate.HibernateException; import net.sf.hibernate.Query;
import org.advancedjava.ch08.model.Contact; import org.advancedjava.ch08.model.Phone;
public class AddContactAction extends HibernateAction { private Integer[] selectedExpertises;
private Contact contact;
385