HibernateRelationships |
|
Your trail: |
This is version 86.
It is not the current version, and thus it cannot be edited.
[Back to current version]
[Restore this version]
About this tutorial
This is a tutorial to show how to create and manage Hibernate relationships within AppFuse. This tutorial was written using AppFuse 1.8. All of the code for this tutorial is downloadable. To begin, I created a new application with an app/db name of blog/blog.
- For further details about the specifics regarding creating objects, XDoclet tags for Hibernate, DAO development, etc., please refer to Creating new DAOs and Objects in AppFuse.
NOTE: Copying the Java code in this tutorials doesn't work in Firefox. A workaround is to CTRL+Click (Command+Click on OS X) the code block and then copy it.
Table of Contents
- Create Weblog.java, Entry.java and add XDoclet tags
- [Many-to-One] Create a new Category object and modify Entry.java to use it
- [One-to-Many] A Weblog object can have many Entries
- [Many-to-Many] Shared Weblogs are possible for multiple Users
- [DAO Layer] Setting up Unit Tests and DAO classes for basic CRUD
- Lazy-Loading Issues
- Managing relationships and indexed properties in the UI
Create Weblog.java, Entry.java and add XDoclet tags
In this example, the Weblog object is used to indentify a person's blog. This class has the following properties:
- weblogId
- username
- blogTitle
- dateCreated
The Entry object is used to contain a listing of a person's blog entries in their Weblog. This class contains the following properties:
NOTE: The primary keys are prefixed with their entity name to avoid confusion. I generally recommend using "id" for your entities, but wanted to make thing clearer in this tutorial.
The first thing you need to do in this tutorial is these two object to persist. Create both a Weblog.java class and an Entry.java class (in the src/dao/**/model directory). The necessary XDoclet tags for these entities is included on the getter method's javadoc. You can download these files using the links below. Note that javadocs have been eliminated for brevity.
- Rather than fill up this tutorial with large blocks of Java code, the necessary files are attached and linked to. Small code snippets are used where appropriate. You should be able to easily download the files by right-clicking on them and selecting "Save Target As...".
Tip: The equals(), hashCode() and toString() methods can be generated by your IDE. If you're using Eclipse, get the Commonclipse plugin. For IDEA, the first two are built into the "Generate..." dialog, and toString() is a plugin you can download. Make sure you don't have primary key's in your equals() and hashCode() methods (as recommended by Hibernate in Action).
[Many-to-One] Create a new Category object and modify Entry.java to use it
A category object will act as an entity for persisting category information about weblog entries. A category object will consist of an id (primary key), category name, and a description. Each category can have many entries.
The many-to-one relationship between Entry and Category can be established using XDoclet tags. Hibernate relationships can be established on either side of the relationship or bi-directionally. For this tutorial, this relationship will be maintained by an extra property and collection held by Entry. The list of possible categories for a weblog entry will eventually be represented as a drop-down on the UI. Add the following code to your Entry.java class.
private Long categoryId;
private Category category;
/**
* @hibernate.many-to-one insert="false" update="false" cascade="none"
* column="category_id" outer-join="true"
*/
public Category getCategory() {
return category;
}
public void setCategory(Category category) {
this.category = category;
}
/**
* @hibernate.property column="category_id"
*/
public Long getCategoryId() {
return categoryId;
}
public void setCategoryId(Long categoryId) {
this.categoryId = categoryId;
}
|
[One-to-Many] A Weblog object can have many Entries
Now you'll the Weblog object and Entry object to represent the multiplicity of a weblog that can have many entries. This relationship is set on the Weblog class using a java.util.List. XDoclet tags are used to establish this relationship using a bag as the Hibernate collection type. Add the following code to your Weblog.java class.
private List entries;
/**
* @return Returns the entries.
*
* @hibernate.bag name="entries" lazy="false" inverse="true" cascade="delete"
* @hibernate.collection-key column="weblog_id"
* @hibernate.collection-one-to-many class="org.appfuse.model.Entry"
*/
public List getEntries() {
return entries;
}
public void setEntries(List entries) {
this.entries = entries;
}
|
Modify the Entry class is so it contains a foreign key to it's parent Weblog class.
private Long weblogId;
/**
* @hibernate.property column="weblog_id"
*/
public Long getWeblogId() {
return weblogId;
}
public void setWeblogId(Long weblogId) {
this.weblogId = weblogId;
}
|
[Many-to-Many]
The Weblog system in this tutorial allows one additional bit of functionality. A particular Weblog can have many Users. Basically the idea is of a shared weblog that is a place where many users can express themselves about a particular topic of interest. For this bit of functionality the User object will be modified to have a many-to-many relationship with Weblog.
Add the following users property and accessor methods to Weblog.java.
private Set users = new HashSet();
/**
* @hibernate.set table="weblog_user" cascade="none" lazy="false"
* @hibernate.collection-key column="weblog_id"
* @hibernate.collection-many-to-many class="org.appfuse.model.User" column="username"
*/
public Set getUsers() {
return users;
}
public void addUser(User user) {
getUsers().add(user);
}
public void setUsers(Set users) {
this.users = users;
}
|
User modifications
In addition to allow navigation from a Weblog object to their list of users, you need to add a Set of Weblogs to the User object - allowing that path of navigation as well. Modify User.java to add a weblogs Set and accessor methods.
protected Set weblogs;
public Set getWeblogs() {
return weblogs;
}
public void setWeblogs(Set weblogs) {
this.weblogs = weblogs;
}
|
[DAO Layer]
In order to test that all of this works, you need to create some test classes, add some sample data, develop some DAO interfaces, and create Hibernate DAO classes.
Test Classes
There are a number of ways to construct the DAO layer for this application. You could have a WeblogDAO that handles CRUD for all entities, or you could have separate DAOs for each entity. This tutorial uses the separate DAO approach because this seems to be the most common method used by AppFuse users. Personally, I tend to group my logical features into a single DAO with many methods. Download the following three test classes and put them in your test/dao/**/dao directory.
All of these tests rely on some sample data, so add the following XML to metadata/sql/sample-data.xml.
<table name='weblog'>
<column>weblog_id</column>
<column>blog_title</column>
<column>date_created</column>
<column>username</column>
<row>
<value>1</value>
<value><![CDATA[Sponge Bob is Cool]]></value>
<value>2004-03-31</value>
<value>tomcat</value>
</row>
<row>
<value>2</value>
<value><![CDATA[Java Development = Fun]]></value>
<value>2005-01-05</value>
<value>mraible</value>
</row>
</table>
<table name='weblog_user'>
<column>weblog_id</column>
<column>username</column>
<row>
<value>1</value>
<value>tomcat</value>
</row>
<row>
<value>1</value>
<value>mraible</value>
</row>
<row>
<value>2</value>
<value>mraible</value>
</row>
</table>
<table name='category'>
<column>category_id</column>
<column>category_name</column>
<column>category_description</column>
<row>
<value>1</value>
<value><![CDATA[Struts v. Spring MVC]]></value>
<value><![CDATA[Comparing implementations of the MVC Design Pattern]]></value>
</row>
<row>
<value>2</value>
<value><![CDATA[Cycling Notes]]></value>
<value><![CDATA[All about cycling in the US.]]></value>
</row>
<row>
<value>3</value>
<value><![CDATA[Cyclocross]]></value>
<value><![CDATA[Bog Trotters Unite!]]></value>
</row>
</table>
<table name='entry'>
<column>entry_id</column>
<column>entry_text</column>
<column>time_created</column>
<column>weblog_id</column>
<column>category_id</column>
<row>
<value>1</value>
<value><![CDATA[Testing]]></value>
<value>2005-04-11</value>
<value>1</value>
<value>1</value>
</row>
<row>
<value>2</value>
<value><![CDATA[Test Value]]></value>
<value>2005-04-12</value>
<value>1</value>
<value>1</value>
</row>
<row>
<value>3</value>
<value><![CDATA[Test Value 3]]></value>
<value>2005-04-12</value>
<value>2</value>
<value>3</value>
</row>
</table>
DAO Interfaces
Before any of these tests will compile, you need to create the DAO interfaces and their implementations.
WeblogDAO Interface
Create a WeblogDAO.java interface in src/dao/**/dao:
package org.appfuse.dao;
import java.util.List;
import org.appfuse.model.Weblog;
public interface WeblogDAO extends Dao{
public Weblog getWeblog(Long weblogId);
public List getWeblogs(Weblog weblog);
public void saveWeblog(Weblog weblog);
public void removeWeblog(Long weblogId);
public List getEntries(Long weblogId);
}
|
EntryDAO Interface
package org.appfuse.dao;
import java.util.List;
import org.appfuse.model.Entry;
public interface EntryDAO {
public Entry getEntry(Long entryId);
public List getEntries(Entry entry);
public void saveEntry(Entry entry);
public void removeEntry(Long entryId);
}
|
CategoryDAO Interface
package org.appfuse.dao;
import java.util.List;
import org.appfuse.model.Category;
public interface CategoryDAO {
public Category getCategory(Long categoryId);
public List getCategories(Category category);
public void saveCategory(Category category);
public void removeCategory(Long categoryId);
}
|
DAO Implementation
Then create the Hibernate implementations of the interfaces in the src/dao/**/dao/hibernate directory.
WeblogDAOHibernate
package org.appfuse.dao.hibernate;
import java.util.List;
import org.appfuse.dao.WeblogDAO;
import org.appfuse.model.Weblog;
import org.springframework.orm.ObjectRetrievalFailureException;
public class WeblogDAOHibernate extends BaseDaoHibernate implements WeblogDAO {
public Weblog getWeblog(Long weblogId) {
Weblog weblog = (Weblog) getHibernateTemplate().get(Weblog.class,
weblogId);
if (weblog == null) {
log.warn("uh oh, weblog '" + weblogId + "' not found...");
throw new ObjectRetrievalFailureException(Weblog.class, weblogId);
}
return weblog;
}
public List getWeblogs(Weblog weblog) {
return getHibernateTemplate().find(
"from Weblog wl order by upper(wl.blogTitle)");
}
public void saveWeblog(final Weblog weblog) {
getHibernateTemplate().saveOrUpdate(weblog);
}
public void removeWeblog(Long weblogId) {
getHibernateTemplate().delete(getWeblog(weblogId));
}
public List getEntries(Long weblogId) {
return getHibernateTemplate().find("from Entry e where e.weblogId=?",
weblogId);
}
}
|
EntryDAOHibernate
package org.appfuse.dao.hibernate;
import java.util.List;
import org.appfuse.dao.EntryDAO;
import org.appfuse.model.Entry;
import org.springframework.orm.ObjectRetrievalFailureException;
public class EntryDAOHibernate extends BaseDaoHibernate implements EntryDAO {
public Entry getEntry(Long entryId) {
Entry entry = (Entry) getHibernateTemplate().get(Entry.class, entryId);
if (entry == null) {
log.warn("uh oh, weblog '" + entryId + "' not found...");
throw new ObjectRetrievalFailureException(Entry.class, entryId);
}
return entry;
}
public List getEntries(Entry entry) {
return getHibernateTemplate().find("from Entry e order by upper(e.timeCreated)");
}
public void saveEntry(final Entry entry) {
getHibernateTemplate().saveOrUpdate(entry);
}
public void removeEntry(Long entryId) {
getHibernateTemplate().delete(getEntry(entryId));
}
}
|
CategoryDAOHibernate
package org.appfuse.dao.hibernate;
import java.util.List;
import org.appfuse.dao.CategoryDAO;
import org.appfuse.model.Category;
import org.springframework.orm.ObjectRetrievalFailureException;
public class CategoryDAOHibernate extends BaseDaoHibernate implements CategoryDAO {
public Category getCategory(Long categoryId) {
Category category = (Category) getHibernateTemplate().get(Category.class, categoryId);
if (category == null) {
log.warn("uh oh, weblog '" + categoryId + "' not found...");
throw new ObjectRetrievalFailureException(Category.class, categoryId);
}
return category;
}
public List getCategories(Category category) {
return getHibernateTemplate().find("from Category cat order by upper(cat.categoryName)");
}
public void saveCategory(final Category category) {
getHibernateTemplate().saveOrUpdate(category);
}
public void removeCategory(Long categoryId) {
getHibernateTemplate().delete(getCategory(categoryId));
}
}
|
Modifications need to be made in src/org/appfuse/dao/hibernate/applicationContext-hibernate.xml for the 3 new entities you created in this tutorial.
<property name="mappingResources">
<list>
<value>org/appfuse/model/Role.hbm.xml</value>
<value>org/appfuse/model/User.hbm.xml</value>
<value>org/appfuse/model/UserCookie.hbm.xml</value>
<value>org/appfuse/model/Weblog.hbm.xml</value>
<value>org/appfuse/model/Entry.hbm.xml</value>
<value>org/appfuse/model/Category.hbm.xml</value>
</list>
</property>
|
Also, you need to add the "weblogDAO", "entryDAO", and "categoryDAO" bean definitions.
<!-- WeblogDAO: Hibernate implementation -->
<bean id="weblogDAO" class="org.appfuse.dao.hibernate.WeblogDAOHibernate">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<!-- EntryDAO: Hibernate implementation -->
<bean id="entryDAO" class="org.appfuse.dao.hibernate.EntryDAOHibernate">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
<!-- CategoryDAO: Hibernate implementation -->
<bean id="categoryDAO" class="org.appfuse.dao.hibernate.CategoryDAOHibernate">
<property name="sessionFactory"><ref local="sessionFactory"/></property>
</bean>
|
After making these changes, you should be able to run ant test-dao. All of the tests should pass successfully.
Yeah baby, Yeah!
BUILD SUCCESSFUL
Total time: 12 seconds
Lazy-Loading Issues
Karl Baum has an excellent article on lazy-loading.
Managing relationships and indexed properties in the UI
Attachments:
|