Using Hibernate in the Sample Application

The SpringBlog application comes with an applicationContext-hibernate.xml file that allows you to use Hibernate as an implementation for the data access interfaces. If you want to use Hibernate, you need to modify the web.xml file to make sure you are using the applicationContexthibernate.xml file in the contextConfigLocation element (see Listing 9-31).

Listing 9-31: web.xml for Hibernate Data Access Layer Implementaion

image from book
<?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"     "http://java.sun.com/dtd/web-app_2_3.dtd"> <web-app>     <context-param>         <param-name>contextConfigLocation</param-name>         <param-value>             /WEB-INF/applicationContext.xml              /WEB-INF/applicationContext-db.xml              /WEB-INF/applicationContext-hibernate.xml          </param-value>     </context-param>          <!-- the rest of the file omitted -->      </web-app>
image from book

The implementation classes are located in the com.apress.prospring.data.hibernate package. All Hibernate DAO implementation classes extend the HibernateDaoSupport class to get access to the utility methods, namely getHibernateTemplate(). We need to configure LocalSessionFactoryBean to get access to the SessionFactory class, which maintains the Session instances Hibernate uses to perform the database operations. The declaration of the sessionFactory bean is shown in Listing 9-32.

Listing 9-32: sessionFactory Bean Definition

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"      "http://www.springframework.org/dtd/spring-beans.dtd"> <beans>     <bean           bold">LocalSessionFactoryBean">         <property name="dataSource"><ref bean="dataSource"/></property>         <property name="mappingResources">             <list>                 <value>Entry.hbm.xml</value>                 <value>Comment.hbm.xml</value>                 <value>User.hbm.xml</value>                 <value>Attachment.hbm.xml</value>             </list>         </property>         <property name="hibernateProperties">             <props>                 <prop key="hibernate.dialect">                     net.sf.hibernate.dialect.MySQLDialect</prop>                 <prop key="hibernate.max_fetch_depth">3</prop>             </props>         </property>     </bean> </beans>
image from book

The sessionFactory bean sets the mappingResources that contain the Hibernate mappings for the domain objects. Even though you can keep all the mappings in one large file, we decided to split them into several files to keep the code clear.

Next, we set the hibernate.dialect property in hibernateProperties to MySQLDialect, indicating that the database server is MySQL. Finally, we set the hibernate.max_fetch_depth property; this specifies that Hibernate will construct the SQL statements to select the data with no more than three outer joins.

The mapping files are not particularly complex; the most complicated one is the Entry.hbm.xml for the Entry domain object. In Listing 9-33, you can see that it references mapping from the Comments.hbm.xml and Attachment.hbm.xml files, whereas Comment.hbm.xml references mapping from the User.hbm.xml mapping file.

Listing 9-33: Entry Domain Object Mappings

image from book
 // Entry.hbm.xml <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"     "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping>          <class name="com.apress.prospring.domain.Entry" table="Entries">         <id name="entryId" type="int" unsaved-value="0" >             <generator />         </id>              <property name="subject" type="string" not-null="true"/>         <property name="body" type="string" not-null="true"/>         <property name="postDate" type="timestamp" not-null="true"/>         <set name="comments">             <key column="Entry"/>             <one-to-many bold">com.apress.prospring.domain.Comment"/>         </set>              <set name="attachments" table="EntryAttachments">             <key column="Entry"/>             <many-to-many                 bold">com.apress.prospring.domain.Attachment"                 column="Attachment" foreign-key="Attachment"/>         </set>     </class>      </hibernate-mapping>      // Comment.hbm.xml <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"     "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping>          <class name="com.apress.prospring.domain.Comment" table="Comments">         <id name="commentId" type="int" unsaved-value="0" >             <generator />         </id>              <property name="subject" type="string" not-null="true"/>         <property name="body" type="string" not-null="true"/>         <property name="postDate" type="timestamp" not-null="true"/>         <property name="entry" type="int" not-null="true"/>         <property name="replyTo" type="int" not-null="false"/>         <many-to-one name="postedBy"              bold">com.apress.prospring.domain.User" column="PostedBy">             <column name="UserId"/>         </many-to-one>         <set name="attachments" table="CommentAttachments">             <key column="Comment"/>             <many-to-many bold">com.apress.prospring.domain.Attachment"                   column="Attachment" foreign-key="Attachment"/>         </set>             </class>      </hibernate-mapping>      // User.hbm.xml <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"     "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"> <hibernate-mapping>          <class name="com.apress.prospring.domain.User" table="Users">         <id name="userId" type="int" unsaved-value="0" >             <generator />         </id>              <property name="username" type="string" not-null="true"/>         <property name="password" type="string" not-null="true"/>         <property name="email" type="string" not-null="true"/>         <property name="type" type="int" not-null="true"/>     </class>      </hibernate-mapping>
image from book

Let's take a look at the mapping in more detail. First of all, the Entry domain object has the Set comments property and inherits the Set attachments property. The mapping specifies that the data for the attachments property should be the Attachment objects for rows selected from the Attachments table joined with the EntryAttachments table where EntryAttachments.Entry = Entry.EntryId and Attachment.AttachmentId = EntryAttachments.Attachment. This is effectively a many-to-many relationship.

Next, the mapping specifies that the data for the comments property should be the Comment domain objects. Hibernate knows that the Comment objects are stored in the Comments table, so the only thing we need to specify is the foreign key, Entry. The mapping for the Comment object specifies the attachments property as an Attachment object that is created from rows selected from the Attachments table joined with the CommentAttachments table where CommentAttachment.Comment = Comments.CommentId and Attachment.AttachmentId = CommentAttachments.Attachment.

Finally, the Comment objects contain a reference to the User who created the comment. This is represented by the many-to-one mapping to the User object selected from the Users table where Users.UserId = Comment.PostedBy.

Very little remains to be written about the DAO implementations, because all the work associated with selecting, updating, and deleting data is done by Hibernate. This means that the implementation files contain a lot less code than their JDBC versions. Take a look at Listing 9-34, which shows a complete implementation of the UserDao interface.

Listing 9-34: HibernateUserDao Implementation

image from book
package com.apress.prospring.data.hibernate;      import java.util.List;      import org.springframework.orm.hibernate.support.HibernateDaoSupport;      import com.apress.prospring.data.UserDao; import com.apress.prospring.domain.User;      public class HibernateUserDao extends HibernateDaoSupport implements UserDao {          public User getByUsernameAndPassword(String username, String password) {         List users = getHibernateTemplate().findByNamedParam(             "select u from User as u where u.username=:username and " +             "u.password=:password", new String[] { "username", "password" },                 new Object[] { username, password });         if (users.size() == 1) {             return (User)users.get(0);         }         return null;     }          public void save(User user) {         getHibernateTemplate().saveOrUpdate(user);     }          public void delete(int userId) {         User user = new User();         user.setUserId(userId);         getHibernateTemplate().delete(user);     }          public List getAll() {         return getHibernateTemplate().find("select u from User as u");     }      }
image from book

The only slightly complex method in this code is getByUsernameAndPassword() because it needs to pass two named parameters (username and password) to Hibernate. Hibernate executes the query and returns a List of matching User objects. We need to check that the number of objects in the List is 1 and return the first object from the List. If there are zero or more than one result, we return null. Because the HibernateTemplate.delete() method requires an Object parameter representing the domain object to be deleted, we need to construct an instance of the User object and set its userId property before passing it as a parameter to the HibernateTemplate.delete() method.

There is one more interesting point to mention in the implementation of the Hibernate data access layer: in HibernateEntryDao.save(), we need to make sure the class of the entry parameter passed to the saveOrUpdate() method is Entry.class. The reason why is because in the web layer, we use an object that extends the Entry domain object and passes it to the Blog- Manager.saveEntry() method, which, in turn, calls the EntryDao.save() method implemented in HibernateEntryDao.save(). Unfortunately, Hibernate does not know how to persist the subclass of the Entry object and throws an exception. Listing 9-35 shows how we deal with the situation.

Listing 9-35: HibernateEntryDao.save() Implementation

image from book
package com.apress.prospring.data.hibernate;      import java.util.List;      import org.springframework.orm.hibernate.support.HibernateDaoSupport;      import com.apress.prospring.data.EntryDao; import com.apress.prospring.domain.Entry;      public class HibernateEntryDao extends HibernateDaoSupport implements EntryDao {          public void save(Entry entry) {         Entry ex = new Entry();         ex.setEntryId(entry.getEntryId());         ex.setBody(entry.getBody());         ex.setEntryId(entry.getEntryId());         ex.setPostDate(entry.getPostDate());         ex.setSubject(entry.getSubject());         getHibernateTemplate().saveOrUpdate(ex);     }          // other methods omitted }
image from book

Here, we actually create an instance of the Entry object and set its properties from the entry parameter; we then use the local Entry instance in the call to the saveOrUpdate() method.

The last problem we have to solve in the HibernateEntryDao implementation is how to limit the number of Entry objects returned. We do not want to get all the rows from the Entries table and then create another List and copy the first count objects. Instead, we want to tell Hibernate not to return more than count objects. Listing 9-36 shows how we finally solve this problem using the HibernateTemplate.execute() method and implementing the HibernateCallback interface.

Listing 9-36: Limiting the Maximum Number of Objects Returned

image from book
package com.apress.prospring.data.hibernate;      import java.sql.SQLException; import java.util.List;      import net.sf.hibernate.HibernateException; import net.sf.hibernate.Query; import net.sf.hibernate.Session;      import org.springframework.orm.hibernate.HibernateCallback; import org.springframework.orm.hibernate.support.HibernateDaoSupport;      import com.apress.prospring.data.EntryDao; import com.apress.prospring.domain.Entry;      public class HibernateEntryDao      extends HibernateDaoSupport implements EntryDao {          // other methods omitted          public List getMostRecent(final int count) {         return (List) getHibernateTemplate().execute(new HibernateCallback() {             public Object doInHibernate(Session session)                  throws HibernateException, SQLException {                 Query query = session.createQuery("from Entry");                 query.setMaxResults(count);                 return query.list();             }                      });     } }
image from book

Even though the implementation of the data access layer using Hibernate is very simple, the complexity of the queries the database needs to process for a complex object structure, such as the Entry object, is very large. Because of this, the overall performance of the application suffers, mainly because the web layer does not know that once it retrieves a List of Entry domain objects, all dependant objects are also loaded. We can change the Web Tier to reflect the fact that the entire object tree is loaded, but doing so introduces dependency on a particular DAO implementation, and we do not want to do that.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net