Chapter 2. Getting Oriented

This chapter provides a non-trivial example of how a developer might use Hibernate in the course of building a simple JSP application. The application in the example is a partial implementation of a simple weblog system. (For an example of a full-blown weblog system, check out http://blogger.com/ or the Hibernate-based http://www.rollerweblogger.org/.)

The example shows how to build an application by starting with Hibernate mapping files (*.hbm.xml). These mapping files are used to generate the database schema and the persistent Java code; as the mapping files are updated, the schema and the persistent Java code are updated as well. This isn't the only way to build an application in Hibernate. In Chapter 3, an application will be built by starting from Java source files, and in Chapter 4, an existing database schema is used to generate both the mapping files and the Java source.

Application Architecture

The application in this chapter is a classic three-tier application—a browser accesses an application server, which in turn accesses a database. Figure 2.1 shows the files used by the application. Note that the application works with persistent objects, not directly with the database. Handcrafted Hibernate mapping files generate the persistent objects and the database definition. The mapping files provide development-time information and also runtime configuration information.

Application Architecture

Figure 2.1. Application Architecture

The JSP pages in the application are typical, insofar as each page contains a small amount of persistent logic corresponding to its action. In a larger-scale application, it would be best to move the logic in the JSP pages into Java classes, but for an application of this size the present approach works reasonably well.

The handcrafted portions of the application are the JSP pages, the single Java class, and the two mapping files (*.hbm.xml). The heart of the application is described by two mapping files. As a reminder, you are strongly encouraged to start by downloading the source for this example from http://www.cascadetg.com/hibernate/ and then use it to follow along.

Mapping Files

A mapping file is principally concerned with the binding between Java objects and a database schema. In this application, the mapping files are generating both the Java objects and the database schema.

The mapping files define two classes, a Post class and an Author class. The idea is simple—an author can write one or more posts. Our application will show how to create, update, insert, and delete records using these two basic classes. Finally, this application will use Hibernate's built-in support for version management to manage situations in which a submitted edit for a post would overwrite changes made by another author.

Mapping Files in Depth

The Post and Author classes are modeled using two *.hbm.xml files, named Post.hbm.xml and Author.hbm.xml. Each is placed in the proper package folder corresponding to the desired class name. Therefore, the path of the Post.hbm.xml file would be com/cascadetg/ch02/Post.hbm.xml, next to the corresponding source file. Inspecting Listing 2.1, the Post class is mapped to a post table. A unique identifier[1] is automatically generated when a Post is created. In this example, the unique identifier is a string, but it could be an integer as well. A column is declared in the post table, revision, which allows Hibernate to keep track of the version number of the Post. By using Hibernate's built-in versioning system, our application can easily handle conflicting concurrent modifications.

The most confusing part of the Post mapping file is the many-to-one element. Each Post has one (not optional) author. The many-to-one element is used to indicate that each Post will have a single column pointing to the unique identifier of the author.

For more information on the tags used in a mapping file, see Chapter 5.

Example 2.1. Post Mapping

<?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>

<class name="com.cascadetg.ch02.Post" table="post">

    <id name="id" type="string" column="ID">
        <meta attribute="finder-method">findByID</meta>
        <generator class="uuid.hex" />
    </id>

    <version column="revision" name="revision" />

    <property name="title" type="string"
            column="title" length="100" />

    <property name="summary" type="string"
            column="summary" length="255" />

    <property name="content" type="text" column="content" />

    <property name="date" type="timestamp" column="date" />

    <many-to-one name="author"
        class="com.cascadetg.ch02.Author">

        <!-- Used by code generator -->
        <meta attribute="finder-method">findByAuthorID</meta>

        <!-- Used as a DDL hint -->
        <column name="authorID" not-null="true" />
    </many-to-one>
</class>
</hibernate-mapping>

Turning to the Author.hbm.xml file shown in Listing 2.2, several elements are seen in common with the Post.hbm.xml mapping. The most significant change is the introduction of the set tag, which defines the pointer back from an author to zero, one, or more posts. The set tag means that an implementation of java.util.Set will be used to represent the posts.

Several attributes are used to control how the java.util.Set is managed by Hibernate. The lazy="true" attribute means that the posts associated with a particular Author object won't be automatically fetched with the same SQL query as the Author object. The inverse="true" attribute is used to indicate that the Post is responsible for managing the relationship between authors and posts. The cascade="delete" attribute is used to indicate that when a Author is deleted, all of the Author's posts should automatically be deleted as well (a powerful and potentially dangerous feature if not used carefully).

Note the precise declaration of the set tag. A key column is used to indicate the column in the Post table used to refer to the author. The one-to-many tag is used to indicate the Post object (and therefore, implicitly, the post table).

Example 2.2. Author Mapping

<?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>

<class name="com.cascadetg.ch02.Author" table="author">

    <id name="id" type="string" column="ID">
        <meta attribute="finder-method">findByID</meta>
        <generator class="uuid.hex" />
    </id>

    <property name="firstName" type="java.lang.String"
        column="first" length="100" />

    <property name="lastName" type="java.lang.String"
        column="last" length="100" />

    <property name="email" type="java.lang.String"
        column="email" length="100">
          <meta attribute="finder-method">findByEmail</meta>
    </property>

    <!-- bi-directional one-to-many association to Post -->
    <set name="posts" lazy="true" inverse="true"
            cascade="delete" >
        <key>
            <column name="authorID" />
        </key>
        <one-to-many class="com.cascadetg.ch02.Post"/>
    </set>

</class>
</hibernate-mapping>

As can be seen from the mapping files shown in Listing 2.1 and Listing 2.2, Hibernate possesses a wide (even overwhelming) set of features. The name attributes (used to specify the object-oriented, Java portions of the system) and the table/column attributes (used to specify the database side of affairs) constitute the heart of the mappings. Everything else, more or less, is used to describe additional functionality such as the management of collections, unique identifier generation, and version management.

If you wish to explore the meaning of any of these tags or attributes in more detail before continuing, see Chapter 5.

Generating Java Source

Given the *.hbm.xml files shown in Listing 2.1 and Listing 2.2, the next step is to generate the corresponding Java code. This is done with Ant. Listing 2.3 shows the complete Ant build.xml file used for this application.

Example 2.3. Ant Build File

<?xml version="1.0"?>
<project name="ch03" default="all">

      <target name="all" depends="build_hbm,deploy" />

      <description>Hibernate starting with Java</description>

    <!-- project name -->
    <property name="name" value="chapter2" />

    <!-- installation configuration -->
    <!-- You'll need to set these depending on your library
       installation directories -->
    <property name="hibernate_path"
        value="C:devenvhibernate-2.1.2"/>
    <property name="hibernate_tool_path"
        value="C:devenvhibernate-extensions-2.1	ools"/>

    <!-- the generated package info -->
    <property name="package" value="com.cascadetg.ch02" />
    <property name="package.dir" value="comcascadetgch02" />

    <property name="tomcat.app"
        value="C:Tomcat5webappsweblog" />
    <property name="tomcat.classes.dir"
        value="C:Tomcat5webappsweblogWEB-INFclasses" />

    <path id="project.class.path">
        <pathelement
            location="${hibernate_path}hibernate2.jar"/>
        <pathelement
            location="${hibernate_tool_path}hibernate-
            tools.jar"/>
        <pathelement location=
            "${hibernate_path}libcommons-collections-2.1.jar"
            />
        <pathelement location=
            "${hibernate_path}libcommons-logging-1.0.3.jar"
            />
        <pathelement location=
            "${hibernate_path}libcommons-lang-1.0.1.jar" />
        <pathelement location=
            "${hibernate_path}libxerces-2.4.0.jar" />
        <pathelement location=
            "${hibernate_tool_path}libjdom.jar"/>
    </path>
       <!-- Normally, Ant build files are stored at the root of
       the tree.
          The builds for this book are on a per-chapter basis -->
       <property name="base_dir" value="......" />

    <!-- creates the Java sources from the HBM files. -->
    <target name="build_hbm" description="builds the Java
    sources">
        <taskdef name="hbm2java"
            classname="net.sf.hibernate.tool.hbm2java
                 .Hbm2JavaTask">
            <classpath refid="project.class.path"/>
        </taskdef>

        <mkdir dir="${build.src.dir}"/>
        <hbm2java config="hbm2java_config.xml"
        output="${base_dir}">
            <fileset dir="." includes="*.hbm.xml"/>
        </hbm2java>
    </target>

    <!-- Builds and copies files into Tomcat WEB-INF classes -->
    <target name="deploy" >
        <echo message="compiling..." />
            <javac srcdir="${base_dir}"
            destdir="${tomcat.classes.dir}"
                     debug="on" includes="**/ch02/**">
            <classpath refid="project.class.path"/>
        </javac>

        <echo message="copying..." />
        <copy todir="${tomcat.classes.dir}${package.dir}">
            <fileset dir="." casesensitive="yes">
              <include name="**/*.hbm.xml"/>
            </fileset>
        </copy>
        <copy todir="${tomcat.classes.dir}">
            <fileset dir="${base_dir}" casesensitive="yes">
              <include name="*.properties"/>
            </fileset>
        </copy>
    </target>

    <!-- Used to copy JSP files back into CVS managed folder. -->
    <target name="copy_from_jsp">
        <copy todir="${base_dir}webapp">
            <fileset dir="${tomcat.app}" casesensitive="yes" />
        </copy>
    </target>
</project>

Listing 2.3 contains several tasks. The task of principal interest is the build_hbm task, broken out into a separate listing in Listing 2.4. Note that this task relies on the net.sf.hibernate.tool.hbm2java.Hbm2JavaTask class, part of the Hibernate hbm2java tool suite. The actual function of the tool suite is straightforward; it reads the *.hbm.xml files and produces Java files according to rules specified in the hbm2java_config.xml file.

Example 2.4. Building the Java Source

<!-- creates the Java sources from the HBM files. -->
<target name="build_hbm" description="builds the Java
sources">
    <taskdef name="hbm2java"
        classname="net.sf.hibernate.tool.hbm2java
             .Hbm2JavaTask">
        <classpath refid="project.class.path"/>
    </taskdef>

    <mkdir dir="${build.src.dir}"/>
    <hbm2java config="hbm2java_config.xml"
    output="${base_dir}">
        <fileset dir="." includes="*.hbm.xml"/>
    </hbm2java>
</target>

The hbm2java_config.xml file is used to configure additional code-generation features. As shown in Listing 2.5, in addition to the standard Java code generation it will also generate a set of “finder” classes, utility functions that more easily retrieve objects by a specific property value.

Example 2.5. Configuring hbm2java

<?xml version="1.0"?>
<codegen>
    <generate renderer="net.sf.hibernate.tool.hbm2java
    .BasicRenderer"/>
    <generate
        package="com.cascadetg.ch02.finder"
        suffix="Finder"
        renderer="net.sf.hibernate.tool.hbm2java
        .FinderRenderer"/>
</codegen>

Running the Ant task will automatically generate (or regenerate) the Java classes as described in the mapping file.

Generated Persistent Classes

You may be surprised to find that the source generated by Hibernate is simply a set of ordinary JavaBeans (sometimes referred to as plain old Java objects or POJO). Figure 2.2 shows an overview of the generated source (the full argument constructors are omitted for readability).

Generated Classes

Figure 2.2. Generated Classes

For the sake of completeness, Listing 2.6 shows the code generated for the com.cascadetg.ch02.Author class. Probably the most shocking thing about the generated source is the lack of any especially unusual information. The class does implement java.io.Serializable, but there is no base class (although implements or extends can optionally be set by the mapping file). This makes Hibernate useful in a wide variety of situations, from an EJB 2.X BMP environment to a Swing client.

Example 2.6. Author Persistent Object

package com.cascadetg.ch02;

import java.io.Serializable;
import java.util.Set;
import org.apache.commons.lang.builder.ToStringBuilder;


/** @author Hibernate CodeGenerator */
public class Author implements Serializable {

    /** identifier field */
    private String id;

    /** nullable persistent field */
    private String firstName;

    /** nullable persistent field */
    private String lastName;

    /** nullable persistent field */
    private String email;

    /** persistent field */
    private Set posts;

    /** full constructor */
    /*public Author(String firstName, String lastName,
    String email, Set posts) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.posts = posts;
    }*/

    /** default constructor */
    public Author() {
    }

    /** minimal constructor */
    public Author(Set posts) { this.posts = posts; }

    public String getId() { return this.id; }

    public void setId(String id) { this.id = id; }

    public String getFirstName() { return this.firstName; }

    public void setFirstName(String firstName) {
         this.firstName = firstName; }

    public String getLastName() { return this.lastName; }

    public void setLastName(String lastName) {
         this.lastName = lastName; }

    public String getEmail() { return this.email; }

    public void setEmail(String email) { this.email = email; }

    public Set getPosts() { return this.posts; }

    public void setPosts(Set posts) { this.posts = posts; }

    public String toString() { return
        new ToStringBuilder(this).append("id",
        getId()).toString(); }

}

Now that the application has a set of Java classes corresponding to the database, it needs classes to actually perform work against the database.

Application Configuration

Given the Java source and a set of mapping files, the application needs code in order to actually perform the needed database operations. A set of Hibernate classes (principally those in the net.sf.hibernate.* package) is used to handle database operations. The initialization() method in Listing 2.7 shows how a Hibernate net.sf.hibernate.cfg.Configuration object is used to create an instance of net.sf.hibernate.SessionFactory.

The net.sf.hibernate.cfg.Configuration object needs a set of mapping files. By using the Configuration.addClass(Object.class) method, Hibernate will search the class path for a directory/*.hbm.xml file that corresponds to the class name. So, given a com.cascadetg.ch02.Author class, Hibernate will look for a com/cascadetg/ch02/Author.hbm.xml file somewhere on the current class path. When the application is run and the Configuration object is first created, Hibernate will verify that the mapping file and the associated Java class files can actually be bound together.

After a successful configuration, Hibernate returns a SessionFactory. A SessionFactory is a thread-safe object intended to be shared throughout the application. Later in this chapter, the application will obtain net.sf.hibernate.Session objects from this SessionFactory.

You may have noticed the reference to net.sf.hibernate.tool.hbm2ddl.SchemaUpdate. This single line of code connects to the database and attempts to create tables, column, and key constraints corresponding to the declared mapping files.

Example 2.7. Hibernate Application Configuration

package com.cascadetg.ch02;

/** Various Hibernate-related imports */
import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;

import net.sf.hibernate.tool.hbm2ddl.SchemaUpdate;

public class AppSession
{

    static String fileSep =
         System.getProperty("file.separator");

    /** We use this session factory to create our sessions */
    public static SessionFactory sessionFactory;

    public static Session getSession()
    {
        if (sessionFactory == null) initialization();
        try
        {
            return sessionFactory.openSession();
        } catch (Exception e)
        {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Loads the Hibernate configuration information, sets up the
     * database and the Hibernate session factory.
     */
    public static void initialization()
    {
        try
        {
            Configuration myConfiguration = new
                 Configuration();
            myConfiguration.addClass(Post.class);
            myConfiguration.addClass(Author.class);

            // This is the code that updates the database to the
            // current schema.
            new SchemaUpdate(myConfiguration)
                    .execute(true, true);

            // Sets up the session factory (used in the rest
            // of the application).
            sessionFactory = myConfiguration
                    .buildSessionFactory();

            // MySQL only
            setInnoDB();
            // MySQL only
            new SchemaUpdate(myConfiguration)
                    .execute(true, true);


        } catch (Exception e)
        {
            e.printStackTrace();
        }
    }

    public static void main(String[] args)
    {
        initialization();
    }

    /** MySQL only */
    public static void setInnoDB()
    {
        Session hibernateSession = null;
        Transaction myTransaction = null;

        try
        {
            hibernateSession = sessionFactory.openSession();
            myTransaction =
                 hibernateSession.beginTransaction();

            java.sql.Statement myStatement = hibernateSession
                    .connection().createStatement();
            myStatement
                    .execute("ALTER TABLE Author TYPE=InnoDB");
            myStatement.execute("ALTER TABLE Post
                 TYPE=InnoDB");
            myTransaction.commit();

            System.out.println();
        } catch (Exception e)
        {
            e.printStackTrace();
            try
            {
                myTransaction.rollback();
            } catch (Exception e2)
            {
                // Silent failure of transaction rollback
            }
        } finally
        {
            try
            {
                if (hibernateSession != null)
                        hibernateSession.close();
            } catch (Exception e)
            {
                // Silent failure of session close
            }
        }

    }

}

Using this class to initialize the application makes it easy to port the application to a variety of application servers. The connection underlying the SessionFactory can be set to point to many different data sources, from a JNDI connection to a direct JDBC pool (as described in Chapter 6), and can even add a performance monitor (as described in Chapter 10).

Generated Database Schema

The first time a session is obtained (using the code shown in Listing 2.7), the configuration and SessionFactory are set up and the database schema is updated. Listing 2.8 shows the schema for the tables as generated by this application's mapping files. In particular, note the authorID column, pointing back to the author table.

Example 2.8. Generated Schema

mysql> desc author;
+-------+--------------+------+-----+---------+-------+
| Field | Type         | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| ID    | varchar(255) |      | PRI |         |       |
| first | varchar(100) | YES  |     | NULL    |       |
| last  | varchar(100) | YES  |     | NULL    |       |
| email | varchar(100) | YES  |     | NULL    |       |
+-------+--------------+------+-----+---------+-------+
4 rows in set (0.11 sec)

mysql> desc post;
+----------+--------------+------+-----+---------+-------+
| Field    | Type         | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+-------+
| ID       | varchar(255) |      | PRI |         |       |
| revision | int(11)      |      |     | 0       |       |
| title    | varchar(100) | YES  |     | NULL    |       |
| summary  | varchar(255) | YES  |     | NULL    |       |
| content  | text         | YES  |     | NULL    |       |
| date     | datetime     | YES  |     | NULL    |       |
| authorID | varchar(255) |      | MUL |         |       |
+----------+--------------+------+-----+---------+-------+

Web Application

After the mapping files and Java classes are assembled, the application still needs a user interface. A Web application defines a specific packaging format. Figure 2.3 shows the various assets in their proper relative directories. All of these files are included in the download at http://www.cascadetg.com/hibernate/.

Web Application Files

Figure 2.3. Web Application Files

Only a subset of the full Hibernate libraries (as described in Chapter 1) is required for this application. The contents of the WEB-INF/lib directory are:

  • c3p0-0.8.3.jar

  • cglib-2.0-rc2.jar

  • dom4j-1.4.jar

  • ehcache-0.6.jar

  • hibernate2.jar

  • jdbc2_0-stdext.jar

  • jta.jar

  • log4j-1.2.8.jar

  • mysql-connector-java-3.0.10-stable-bin.jar

  • odmg-3.0.jar

  • sitemesh-2.0.1.jar

The application includes the JSP pages listed below. The index.jsp page simply redirects the user to the list_posts.jsp page. The meaning of the pages is self-evident from the name of the file.

  • index.jsp

  • list_posts.jsp

  • create_author.jsp

  • list_author.jsp

  • create_post.jsp

  • edit_author.jsp

  • edit_post.jsp

  • view_post.jsp

  • delete_author.jsp

  • delete_post.jsp

JSP Interface

A few simple conventions are used in the development of the JSP files used by this application. Identifiers are passed using the values authorID and postID. All of these files start with a header containing a bit of Java logic, and the rest of the page is concerned with the HTML formatting of the displayed results.

List Posts

Our first JSP lists the currently available posts. When the page is first visited, there are no posts. As shown in Figure 2.4, the only real user action at this point is to add a post (which, in turn, requires an author).

No Posts Listed

Figure 2.4. No Posts Listed

After a few authors and posts have been added, the page shows additional functionality for editing and deleting posts, as can be seen in Figure 2.5.

Authors and Posts Added

Figure 2.5. Authors and Posts Added

Reviewing the code for this JSP, as shown in Listing 2.9, provides an introduction to Hibernate object retrieval using the Criteria API. A session is retrieved from our AppSession factory, a transaction is started, and a query in the form of a Criteria object is executed. The post java.util.Iterator holds the returned Post objects. By setting the sole criteria to the Post.class, the application indicates that it wants all of the Post objects.

For more information on the Criteria API, see Chapter 8.

Example 2.9. Listing Posts Source

<%@ page contentType="text/html; charset=utf-8" language="java"
      import="com.cascadetg.ch02.*,net.sf.hibernate.*" %><%

String error = "";
Session hibernateSession = null;
Transaction myTransaction = null;
java.util.Iterator posts = null;
try
{

      hibernateSession = AppSession.getSession();
      myTransaction = hibernateSession.beginTransaction();

      Criteria query =
           hibernateSession.createCriteria(Post.class);

      posts = query.list().iterator();

      myTransaction.commit();
      hibernateSession.close();

} catch (Exception e) {
      error = e.getMessage(); e.printStackTrace();
      try{ myTransaction.rollback(); }
      catch (Exception e2) {;}
}
finally
{
      try{hibernateSession.close();}
      catch (Exception e) {;}
}
%>
<HTML>
<HEAD>
<TITLE>List Posts</TITLE>
</HEAD>
<BODY>
<%= error %>
<% if(posts == null) { %>
<p>No posts available.</p>
<% } else { %>
<table width="100%"  border="0">
  <tr>
    <td><strong>Author</strong></td>
    <td><strong>Post</strong></td>
  <td><strong>Date</strong></td>
    <td><strong>Summary</strong></td>
  <td>&nbsp;</td>
  <td>&nbsp;</td>
  </tr>
<% while(posts.hasNext()) {
    Post myPost = (Post)posts.next();
%>
  <tr>
    <td><%

      if(myPost.getAuthor() != null)
      {
            %><a href="edit_author.jsp?authorID=<%=
            myPost.getAuthor().getId()%>"><%=
            myPost.getAuthor().getFirstName()%> <%=
            myPost.getAuthor().getLastName() %></a><%
      }
 %>&nbsp;</td>
    <td><a href="view_post.jsp?postID=<%=
      myPost.getId()%>"><%=myPost.getTitle()%></a>&nbsp;</td>
  <td><%=myPost.getDate().toString()%></td>
    <td><%=myPost.getSummary()%>&nbsp;</td>
  <td>
    <a href="edit_post.jsp?postID=<%=
  myPost.getId()%>">Edit</a></td>
  <td>
    <a href="delete_post.jsp?postID=<%=
            myPost.getId()%>">Delete</a></td>
  </tr>
<% }
}
%>
</table>
<hr />
<p align="right"><a href="create_post.jsp">Add Post </a> </p>
</BODY>

Create Author

Before the user can add a post, it is necessary to add an author. Figure 2.6 shows the simple form a user fills out to add an author.

Adding an Author

Figure 2.6. Adding an Author

As shown in Listing 2.10, the page simply displays the form unless a user clicks the Submit button. If the page detects the Submit button, the application uses a Hibernate Session to save the object. Note that the Author object is simply created with a new operation, the various properties are set from the request, and the object is passed to the Session.save() method. Very straightforward.

Example 2.10. Creating Author Source

<%@ page contentType="text/html; charset=utf-8" language="java"
      import="com.cascadetg.ch02.*,net.sf.hibernate.*" %><%
String error = "";
Session hibernateSession = null;
Transaction myTransaction = null;
java.util.Iterator authors = null;

if(request.getParameter("Submit") != null)
{
      try
      {
            hibernateSession = AppSession.getSession();
            myTransaction = hibernateSession.beginTransaction();

            Author myAuthor = new Author();
            myAuthor.setFirstName(request.getParameter("first"));
            myAuthor.setLastName(request.getParameter("last"));
            myAuthor.setEmail(request.getParameter("email"));

            hibernateSession.save(myAuthor);

            myTransaction.commit();

            response.sendRedirect("list_author.jsp");
            return;
      } catch (Exception e) {
            error = e.getMessage(); e.printStackTrace();
            try{ myTransaction.rollback(); }
            catch (Exception e2) {;}
      }
      finally
      {
            try{hibernateSession.close();}
            catch (Exception e) {;}
      }
}
%>
<HTML>
<HEAD>
<TITLE>Create Author</TITLE>
<meta name="no_print" content="true" />
</HEAD>
<BODY>
<form name="create_author"
      method="post" action="create_author.jsp">
  <p><%=error%></p>
  <table width="50%"  border="0">
    <tr>
      <td>First Name </td>
      <td><input name="first" type="text" id="first"></td>
    </tr>
    <tr>
      <td>Last Name </td>
      <td><input name="last" type="text" id="last"></td>
    </tr>
    <tr>
      <td>Email</td>
      <td><input name="email" type="text" id="email"></td>
    </tr>
    <tr>
      <td>&nbsp;</td>
      <td><input type="submit" name="Submit" value="Submit"></td>
    </tr>
  </table>
</form>
</BODY>

List Authors

After an author is created, the user is returned to a list of authors. Figure 2.7 shows the author list after a few authors have been created.

Listing Authors

Figure 2.7. Listing Authors

As can be seen from Listing 2.11, this application uses the Criteria API to retrieve results. The most notable difference is that the results are ordered by last name and first name.

Example 2.11. Listing Authors Source

<%@ page contentType="text/html; charset=utf-8" language="java"
      import="com.cascadetg.ch02.*,net.sf.hibernate.*" %><%
String error = "";
Session hibernateSession = null;
Transaction myTransaction = null;
java.util.Iterator authors = null;
try
{
       hibernateSession = AppSession.getSession();
       myTransaction = hibernateSession.beginTransaction();

       Criteria query =
            hibernateSession.createCriteria(Author.class);
       query.addOrder(
             net.sf.hibernate.expression.Order.asc("lastName"));
       query.addOrder(
             net.sf.hibernate.expression.Order.asc("firstName"));

       authors = query.list().iterator();

       myTransaction.commit();
       hibernateSession.close();
}
catch (Exception e) {
       error = e.getMessage(); e.printStackTrace();
       try{ myTransaction.rollback(); }
       catch (Exception e2) {;}
}
finally
{
       try{hibernateSession.close();}
       catch (Exception e2) {;}
}

%><HTML>
<HEAD>
<TITLE>List Authors</TITLE>
</HEAD>
<BODY><%=error%><% if(authors == null) { %>
No authors defined.
<% } else { %>
<table width="100%"  border="0">
  <tr>
    <td><strong>Name</strong></td>
    <td><strong>Email</strong></td>
    <td>&nbsp;</td>
  <td>&nbsp;</td>
  </tr>
  <% while (authors.hasNext()) {
  Author myAuthor = (Author)authors.next(); %>
  <tr>
    <td><a href="edit_author.jsp?authorID=<%=
            myAuthor.getId()%>"><%=
            myAuthor.getLastName()%>, <%=
            myAuthor.getFirstName()%></a></td>
    <td><a href="mailto:<%=
            myAuthor.getEmail()%>"><%=myAuthor.getEmail()%>
    </a></td>
    <td><a href="edit_author.jsp?authorID=<%=
              myAuthor.getId()%>">Edit</a></td>
  <td><a href="delete_author.jsp?authorID=<%=
              myAuthor.getId()%>">Delete</a></td>
  </tr>
  <% } %>
</table>
<% } %>
<hr />
<p align="right"><a href="create_author.jsp">Add Author </a></p>
</BODY>

Edit Author

Everybody makes mistakes, and so it's important to provide a mechanism for fixing mistakes. Figure 2.8 shows the form that enables a user to correct author information.

Updating an Author

Figure 2.8. Updating an Author

As can be seen from Listing 2.12, updating an Author object is similar to creating one. The biggest changes are the use of the Session.update() method instead of Session.save() and the use of the authorID to identify the record to update.

Example 2.12. Updating an Author Source

<%@ page contentType="text/html; charset=utf-8" language="java"
      import="com.cascadetg.ch02.*,net.sf.hibernate.*" %><%
String error = "";
Session hibernateSession = null;
Transaction myTransaction = null;
Author myAuthor = null;

if(request.getParameter("Submit") != null)
{
      boolean done=false;
      try
      {
            hibernateSession = AppSession.getSession();
            myTransaction =
                 hibernateSession.beginTransaction();

            myAuthor = new Author();
            myAuthor.setId(request.getParameter("authorID"));
            myAuthor.setFirstName(request.getParameter("first"));
            myAuthor.setLastName(request.getParameter("last"));
            myAuthor.setEmail(request.getParameter("email"));

            hibernateSession.update(myAuthor);

            myTransaction.commit();
            hibernateSession.close();
            done=true;
      } catch (Exception e) {
            error = e.getMessage(); e.printStackTrace();
            try{ myTransaction.rollback(); }
            catch (Exception e2) {;}
      }
      finally
      {
            try{hibernateSession.close();}
            atch (Exception e) {;}
      }


      if(done)
      {
            response.sendRedirect("list_author.jsp");
            return;
 }
}

try
{
      hibernateSession = AppSession.getSession();
      myTransaction = hibernateSession.beginTransaction();

      myAuthor = (Author)hibernateSession.load(
            Author.class, request.getParameter("authorID"));

      myTransaction.commit();
      hibernateSession.close();
} catch (Exception e) { error = e.getMessage(); }
finally
{     try{ myTransaction.rollback(); }
      catch (Exception e) {;}
      finally { hibernateSession.close(); }
}
%>
<HTML>
<HEAD>
<TITLE>Edit Author</TITLE>
<meta name="no_print" content="true" />
</HEAD>
<BODY><%=error%>
<form name="edit_author" method="post" action="edit_author.jsp">
  <table width="50%"  border="0">
    <tr>
      <td>First Name</td>
      <td><input type="text" name="first" value="<%=
            myAuthor.getFirstName()%>"></td>
    </tr>
    <tr>
      <td>Last Name </td>
      <td><input type="text" name="last" value="<%=
            myAuthor.getLastName()%>"></td>
    </tr>
    <tr>
      <td>Email Address </td>
      <td><input type="text" name="email" value="<%=
            myAuthor.getEmail()%>"></td>
    </tr>
    <tr>
      <td>&nbsp;</td>
      <td><input name="authorID" type="hidden"
        value="<%=request.getParameter("authorID")%>"><input
        type="submit" name="Submit" value="Save Changes"></td>
    </tr>
  </table>
</form>
</BODY>

Create Post

Once a user has created an author, the author can make posts. Figure 2.9 shows a simple form for the user, with a pop-up to select the author of the post.

Creating a Post

Figure 2.9. Creating a Post

Listing 2.13 shows the code for creating a post with this form. Looking over the source, there are two main blocks of Java code at the start of the page. The first is only executed if the form has been submitted. As with the code shown for creating a new author, the most notable change is the use of an Author object to indicate the post's author. The second block uses the Criteria API to retrieve the list of authors.

Example 2.13. Source for Creating a Post

<%@ page contentType="text/html; charset=utf-8" language="java"
      import="com.cascadetg.ch02.*,net.sf.hibernate.*" %><%
String error = "";
Session hibernateSession = null;
Transaction myTransaction = null;
java.util.Iterator authors = null;

if(request.getParameter("authorID") != null)
if(request.getParameter("authorID").compareTo("null") != 0)
if(request.getParameter("Submit") != null)
{
      boolean done = false;
      String redirect_page = "view_post.jsp?postID=";

      try
      {
           hibernateSession = AppSession.getSession();
           myTransaction = hibernateSession.beginTransaction();

           Post myPost = new Post();
           myPost.setTitle(request.getParameter("title"));
           myPost.setSummary(request.getParameter("summary"));
           myPost.setContent(request.getParameter("content"));
           myPost.setDate(new java.util.Date());

           Author myAuthor = new Author();
           myAuthor.setId(request.getParameter("authorID"));
           myPost.setAuthor(myAuthor);

           hibernateSession.save(myPost);

           myTransaction.commit();

           redirect_page = redirect_page + myPost.getId();
           done=true;
      } catch (Exception e) {
           error = e.getMessage(); e.printStackTrace();
           try{ myTransaction.rollback(); }
           catch (Exception e2) {;}
      }
      finally
      {
           try{hibernateSession.close();}
           catch (Exception e) {;}
      }


      if(done)
      {
           response.sendRedirect(redirect_page);
           return;
 }
}


try
{
      hibernateSession = AppSession.getSession();
      myTransaction = hibernateSession.beginTransaction();

      Criteria query =
           hibernateSession.createCriteria(Author.class);
      query.addOrder(
           net.sf.hibernate.expression.Order.asc("lastName"));
      query.addOrder(
           net.sf.hibernate.expression.Order.asc("firstName"));

      authors = query.list().iterator();

      myTransaction.commit();
      hibernateSession.close();
} catch (Exception e) { error = e.getMessage(); }
finally
{     try{ myTransaction.rollback(); }
      catch (Exception e) {;}
      finally { hibernateSession.close(); }
}

%>
<HTML>
<HEAD>
<TITLE>Create Post</TITLE>
<meta name="no_print" content="true" />
</HEAD>
<BODY><%=error%>
<form name="create_post" method="post" action="create_post.jsp">
  <table width="50%"  border="0">
    <tr>
      <td>Author</td>
      <td><select name="authorID">
        <option default value="null">Please select an
        author</option>
                  <% if(authors != null) { %>
             <% while(authors.hasNext()) {
             Author myAuthor = (Author)authors.next();%>
        <option default value="<%=
                 myAuthor.getId()%>"><%=
                 myAuthor.getLastName()%>, <%=
                 myAuthor.getFirstName()%></option>
           <% }
           } %>
      </select></td>
    </tr>
    <tr>
      <td>Title</td>
      <td><input name="title" type="text" id="title"></td>
    </tr>
    <tr>
      <td>Summary</td>
      <td><input name="summary" type="text" id="summary"></td>
    </tr>
    <tr>
      <td>Content</td>
      <td><textarea name="content"
      id="content"></textarea></td>
    </tr>
    <tr>
      <td>&nbsp;</td>
      <td><input type="submit" name="Submit"
            value="Create Post"></td>
    </tr>
  </table>
</form>
</BODY>
</HTML>

View Post

After the post has been created, the user is given a chance to view it. This allows the user to read the post and optionally click the Edit link to make changes.

Viewing a Post

Figure 2.10. Viewing a Post

Listing 2.14 shows the code for viewing a post. The Session.get() method is used to retrieve an object by the supplied identifier.

Example 2.14. Viewing a Post

<%@ page contentType="text/html; charset=utf-8" language="java"
      import="com.cascadetg.ch02.*,net.sf.hibernate.*" %><%

String error = "";
Session hibernateSession = null;
Transaction myTransaction = null;
Post myPost = null;
try
{
      hibernateSession = AppSession.getSession();
      myTransaction = hibernateSession.beginTransaction();

      myPost = (Post)hibernateSession.load(
            Post.class, request.getParameter("postID"));

      myTransaction.commit();
      hibernateSession.close();

} catch (Exception e) {
            error = e.getMessage(); e.printStackTrace();
            try{ myTransaction.rollback(); }
            catch (Exception e2) {;}
}
finally
{
      try{hibernateSession.close();}
      catch (Exception e) {;}
}
%><HTML>
<HEAD>
<TITLE>View Post</TITLE>
</HEAD>
<BODY><%=error%>
<table width="100%"  border="0">
  <tr>
    <td width="20%"><strong>Title</strong></td>
    <td><%=myPost.getTitle()%>&nbsp;</td>
  </tr>
  <tr>
    <td width="20%"><strong>Version</strong></td>
    <td><%=myPost.getRevision()%></td>
  </tr>
  <tr>
    <td width="20%"><strong>Date</strong></td>
    <td><%=myPost.getDate()%></td>
  </tr>
  <tr>
    <td width="20%"><strong>Summary</strong></td>
    <td><%=myPost.getSummary()%>&nbsp;</td>
  </tr>
  <tr>
    <td width="20%"><strong>Content</strong></td>
    <td><%=myPost.getContent()%>&nbsp;</td>
  </tr>
</table>

<p align="right"><a href="edit_post.jsp?postID=<%=
      request.getParameter("postID")%>">Edit Post</a> </p>
</BODY>

Edit Post

Figure 2.11 shows the standard interface presented when a user chooses to edit a post.

Editing a Post

Figure 2.11. Editing a Post

One of the more advanced features of this particular application is the use of Hibernate's built-in support for version management (as specified by the version tag in Listing 2.1). This allows Hibernate to verify that no intermediate changes have been made to an object before conducting the update. This powerful feature allows for an optimistic locking strategy (as described in more detail in Chapter 9).

In brief, imagine that two users are isaccessing this application at the same time. The first, Bob, goes to the edit page, and proceeds to spend several hours making lengthy changes. The second, Mary, opens the edit page and makes a minor spelling fix. When Bob attempts to save his revision, the application automatically detects that the version number of Bob's update is out of date (with a single UPDATE statement).

In our application, when this sort of conflict occurs, Bob is presented with both the revision currently in the database and the changes he has submitted. It's up to Bob to take these two revisions and submit a single merged post. Figure 2.12 shows the user interface presented to resolve this conflict. The conflict itself is detected with a net.sf.hibernate.StaleObjectStateException.

Conflicting Post Edits

Figure 2.12. Conflicting Post Edits

Listing 2.15 shows the code for editing a post. Pay close attention to the catch of the net.sf.hibernate.StaleObjectStateException. This is the mechanism whereby Hibernate informs the application that the attempt to UPDATE the post has failed because of a problem with the Post version.

Example 2.15. Editing a Post

<%@ page contentType="text/html; charset=utf-8" language="java"
      import="com.cascadetg.ch02.*,net.sf.hibernate.*" %><%

String error = "";
boolean conflict = false;
Session hibernateSession = null;
Transaction myTransaction = null;
Post myPost = null;
if(request.getParameter("Submit") != null)
{
      boolean done = false;
      String redirect_page = "view_post.jsp?postID=";
      try
      {
            hibernateSession = AppSession.getSession();
            myTransaction = hibernateSession.beginTransaction();

            myPost = new Post();
            myPost.setId(request.getParameter("postID"));
            myPost.setRevision(
                  new Integer(
                        request.getParameter("revision"))
                             .intValue());
            myPost.setTitle(request.getParameter("title"));
            myPost.setDate(new java.util.Date());
            myPost.setSummary(request.getParameter("summary"));
            myPost.setContent(request.getParameter("content"));

            Author myAuthor = new Author();
            myAuthor.setId(request.getParameter("authorID"));
            myPost.setAuthor(myAuthor);

            hibernateSession.update(myPost);

            myTransaction.commit();
            hibernateSession.close();

            redirect_page =
                  redirect_page + request.getParameter("postID");
            done = true;
      }
      catch (net.sf.hibernate.StaleObjectStateException stale)
      {
            error =
            "This post was updated by another transaction. " +
            "You may either update the existing " +
            "data, or resubmit your changes.";
            conflict=true;
      }
      catch (Exception e) {
            error = e.getMessage(); e.printStackTrace();
            try{ myTransaction.rollback(); }
            catch (Exception e2) {;}
      }
      finally
      {
            try{hibernateSession.close();}
            catch (Exception e) {;}
      }

      if(done)
      {
            response.sendRedirect(redirect_page);
            return;
      }
}

try
{

      hibernateSession = AppSession.getSession();
      myTransaction = hibernateSession.beginTransaction();

      Criteria query =
           hibernateSession.createCriteria(Post.class);

      myPost = (Post)com.cascadetg.ch02.finder.
            PostFinder.findByID(hibernateSession,
            request.getParameter("postID")).iterator().next();

      myTransaction.commit();
      hibernateSession.close();

} catch (Exception e)
      { error = e.getMessage(); e.printStackTrace(); }
finally
{     try{ myTransaction.rollback(); }
      catch (Exception e) {;}
      finally { hibernateSession.close(); }
}

%>
<HTML>
<HEAD>
<TITLE>Edit Post</TITLE>
<meta name="no_print" content="true" />
</HEAD>
<BODY><%=error %>
<% if(conflict) { %>
<p><strong>Current Saved Post</strong></p>
<% } %>
<form name="edit_post" method="post" action="edit_post.jsp">
  <table width="100%"  border="0">
    <tr>
      <td width="25%"><strong>Title</strong></td>
      <td><input name="title" type="text" value="<%=
            myPost.getTitle()%>"></td>
    <td>&nbsp;</td>
    </tr>
    <tr>
      <td width="25%"><strong>Summary</strong></td>
      <td><input name="summary" type="text" id="summary"
            value="<%=myPost.getSummary()%>"></td>
    <td>&nbsp;</td>
    </tr>
    <tr>
      <td width="25%"><strong>Content </strong></td>
      <td><textarea name="content" id="content"><%=
            myPost.getContent()%></textarea></td>
    <td>&nbsp;</td>
    </tr>
    <tr>
      <td width="25%"><strong>Date / Revision</strong></td>
      <td><%=myPost.getDate()%> / <%=myPost.getRevision()%>
          <input name="revision" type="hidden" id="revision"
            value="<%=myPost.getRevision()%>"></td>
      <td>&nbsp;</td>
    </tr>
    <tr>
      <td width="25%">&nbsp;</td>
      <td><input name="authorID" type="hidden"
            value="<%=myPost.getAuthor().getId()%>"><input
            name="postID" type="hidden"
            value="<%=request.getParameter("postID")%>">
      <input type="submit" name="Submit"
  value="Save Changes"></td>
    <td>&nbsp;</td>
    </tr>
  </table>
</form>
  <% if(conflict) { %>
  <hr>
  <p>Newly submitted post.</p>
  <form name="edit_post" method="post" action="edit_post.jsp">
  <table width="100%" border="0">
  <tr>
    <td width="25%"><strong>Submitted Title Change
  </strong></td>
    <td>
      <input type="text" name="title" value="<%=
            request.getParameter("title") %>"></td>
  </tr>
  <tr>
    <td width="25%"><strong>Submitted Summary Change
  </strong></td>
    <td><input type="text" name="summary" value="<%=
            request.getParameter("summary")%>"></td>
  </tr>
  <tr>
    <td width="25%"><strong>Submitted Content Change
  </strong></td>
    <td><textarea name="content"><%=
                  request.getParameter("content")%></textarea>
    <input name="revision" type="hidden"
            id="revision" value="<%=myPost.getRevision()%>">
    <input name="authorID" type="hidden"
            value="<%=myPost.getAuthor().getId()%>">
    <input name="postID" type="hidden"
            value="<%=request.getParameter("postID")%>"></td>

  </tr>
  <tr>
    <td>&nbsp;</td>
    <td><input type="submit" name="Submit"
            value="Save Submitted Changes"></td>
  </tr>
</table>
</form>
  <% } %>

</BODY>

Delete Post

Sometimes a post is no longer of interest or covers a topic that the writer wishes had never been brought up. Figure 2.13 shows the simple HTML confirmation form presented when a user wishes to delete a post.

Deleting a Post

Figure 2.13. Deleting a Post

Listing 2.16 shows how the post is deleted. As can be seen, deleting a persistent object is a simple one-step method call to the Session.

Example 2.16. Deleting a Post

<%@ page contentType="text/html; charset=utf-8" language="java"
      import="com.cascadetg.ch02.*,net.sf.hibernate.*" %><%

String error = "";
Session hibernateSession = null;
Transaction myTransaction = null;
Post myPost = null;
boolean done = false;
try
{

      hibernateSession = AppSession.getSession();
      myTransaction = hibernateSession.beginTransaction();

      myPost = (Post)hibernateSession.load(Post.class,
            request.getParameter("postID"));
      if(request.getParameter("Submit") != null)
      {
            hibernateSession.delete(myPost);
            done=true;
      } else
      {

      }
      myTransaction.commit();

} catch (Exception e) {
            error = e.getMessage(); e.printStackTrace();
            try{ myTransaction.rollback(); }
            catch (Exception e2) {;}
      }
      finally
      {
            try{hibernateSession.close();}
            catch (Exception e) {;}
      }


if(done)
{
      response.sendRedirect("list_posts.jsp");
      return;
}

%>
<HTML>
<HEAD>
<TITLE>Delete Post</TITLE>

<meta name="no_print" content="true" />
</HEAD>
<BODY><%=error%>
<p>Are you sure you want to delete the post "<b><%=
          myPost.getTitle()%></b>"?</p>
<form name="delete_post" method="post" action="delete_post.jsp">
<input name="postID" type="hidden" value="<%=
          request.getParameter("postID")%>">
  <input type="submit" name="Submit" value="Delete Post">
</form>
</BODY>

Delete Author

Finally, a user may wish to delete an author, as shown in Figure 2.14. Note the warning that all of the author's posts will be deleted as well.

Deleting an Author

Figure 2.14. Deleting an Author

Every post must be assigned to an author. Deleting the author means that any posts pointing to that author are left in an inconsistent state—the foreign key on the authorID column would be violated. Hibernate takes care of this automatically. The mapping file in Listing 2.2 declares that the posts are to be deleted when the owning author is deleted (the cascade="delete" attribute). Therefore, the code in Listing 2.17 merely issues a single delete and Hibernate automatically deletes both the Author and all associated Post records.

WARNING

As you can imagine, cascading deletes are both powerful and very dangerous. Make sure that you fully understand (and test) your cascading operations to ensure that the action you get is the action you expect.

Example 2.17. Deleting an Author

<%@ page contentType="text/html; charset=utf-8" language="java"
      import="com.cascadetg.ch02.*,net.sf.hibernate.*" %><%

String error = "";
Session hibernateSession = null;
Transaction myTransaction = null;
Author myAuthor = null;
try
{

      hibernateSession = AppSession.getSession();
      myTransaction = hibernateSession.beginTransaction();

      if(request.getParameter("Submit") != null)
      {
            myAuthor = new Author();
            myAuthor.setId(request.getParameter("authorID"));
            System.out.println(myAuthor.getId());
            hibernateSession.refresh(myAuthor);
            hibernateSession.delete(myAuthor);
            myTransaction.commit();
            response.sendRedirect("list_author.jsp");
            return;
      } else
      {
            myAuthor = (Author)hibernateSession.load(Author.class,
                  request.getParameter("authorID"));
            myTransaction.commit();
      }


      } catch (Exception e) {
            error = e.getMessage(); e.printStackTrace();
            try{ myTransaction.rollback(); }
            catch (Exception e2) {;}
      }
      finally
      {
            try{hibernateSession.close();}
            catch (Exception e) {;}
      }

%>
<HTML>
<HEAD>
<TITLE>Delete Author</TITLE>
<meta name="no_print" content="true" />
</HEAD>
<BODY><%=error%>
<p>Are you sure you want to delete the author and
all posts written by the author
<b><%=myAuthor.getFirstName()%>
<%=myAuthor.getLastName()%></b>?</p>
<form name="delete_author" method="post" action="delete_author.jsp">

  <input type="hidden" name="authorID"
      value="<%= request.getParameter("authorID") %>">
  <input type="submit" name="Submit" value="Delete Author">
</form>
</BODY>
</HTML>

Next Steps

Now that you've gotten a taste for Hibernate, your next steps depend on your needs and situation. In this chapter the application was built “from the middle out”—a mapping file generated both the persistent Java objects and the database definition. In Chapter 3, an application is built starting from Java, and in Chapter 4, an existing database definition is used. Even if you don't intend to use either of these approaches yourself, the examples shown in Chapters 3 and 4 are used throughout this book as examples. Note that the remaining examples in the book are provided as simple command-line operations and not as full-blown Web applications. This serves to keep the focus on Hibernate as a tool rather than on the idiosyncrasies of Web application development.



[1] In database terminology, a primary key

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.12.160.66