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.
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.
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.
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.
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.
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.
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).
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.
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).
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 | | | +----------+--------------+------+-----+---------+-------+
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/.
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:
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
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.
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).
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.
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> </td> <td> </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><% } %> </td> <td><a href="view_post.jsp?postID=<%= myPost.getId()%>"><%=myPost.getTitle()%></a> </td> <td><%=myPost.getDate().toString()%></td> <td><%=myPost.getSummary()%> </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>
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.
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> </td> <td><input type="submit" name="Submit" value="Submit"></td> </tr> </table> </form> </BODY>
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.
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> </td> <td> </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>
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.
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> </td> <td><input name="authorID" type="hidden" value="<%=request.getParameter("authorID")%>"><input type="submit" name="Submit" value="Save Changes"></td> </tr> </table> </form> </BODY>
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.
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> </td> <td><input type="submit" name="Submit" value="Create Post"></td> </tr> </table> </form> </BODY> </HTML>
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.
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()%> </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()%> </td> </tr> <tr> <td width="20%"><strong>Content</strong></td> <td><%=myPost.getContent()%> </td> </tr> </table> <p align="right"><a href="edit_post.jsp?postID=<%= request.getParameter("postID")%>">Edit Post</a> </p> </BODY>
Figure 2.11 shows the standard interface presented when a user chooses to edit 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
.
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> </td> </tr> <tr> <td width="25%"><strong>Summary</strong></td> <td><input name="summary" type="text" id="summary" value="<%=myPost.getSummary()%>"></td> <td> </td> </tr> <tr> <td width="25%"><strong>Content </strong></td> <td><textarea name="content" id="content"><%= myPost.getContent()%></textarea></td> <td> </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> </td> </tr> <tr> <td width="25%"> </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> </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> </td> <td><input type="submit" name="Submit" value="Save Submitted Changes"></td> </tr> </table> </form> <% } %> </BODY>
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.
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>
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.
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.
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>
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.
3.12.160.66