Table of Contents
I would like to demonstrate a real world web application using Hibernate. Step by step, you can create an application which allows to edit books.
We will implement
This should take you about 15 minutes, if everything runs fine and assuming that you have Maven installed and that you have some experience to deploy a web application to a servlet engine.
Maven is a dependency management solution and project build tool.
I would not replace my IDE build during development but it is a great help to manage dependencies and build a project for production deployment.
Setup the project (5 minute)
You need an Internet connection and you need to have Maven installed. http://maven.apache.org/ I have used Maven version 3.
In a shell input the following command. This needs to be on one line.
mvn -DarchetypeVersion=5.2.5 -Darchetype.interactive=false -DarchetypeArtifactId=quickstart \ -Dversion=1.0-SNAPSHOT -DarchetypeGroupId=org.apache.tapestry -DgroupId=de.laliluna \ -Dpackage=de.laliluna.helloworld -DartifactId=helloworld --batch-mode \ -DarchetypeRepository=http://tapestry.apache.org archetype:generate
It will setup a new quickstart project. If you prefer to setup the project manually, have a look in the Tapestry articles on my website http://www.laliluna.de
To have a first look at your application, tell Maven to download the dependencies. Type the following command in a shell.
cd helloworld mvn dependency:resolve # Start an internal web server mvn jetty:run
Visit http://localhost:8080/helloworld in your browser.
We are going to use the Tapestry Hibernate Module. In addition we need to add the JDBC driver for the database.
Edit the Maven build file myPathToTheProject/helloworld/pom.xml
In the section <dependencies> add the following dependency:
Maven pom.xml.
<dependencies> <dependency> <groupId>org.apache.tapestry</groupId> <artifactId>tapestry-hibernate</artifactId> <version>${tapestry-release-version}</version> </dependency> <dependency> <groupId>postgresql</groupId> <artifactId>postgresql</artifactId> <version>8.3-603.jdbc3</version> </dependency> <!-- more dependencies -->
If you use another database, you need to replace the postgresql dependency. You can find dependencies in Maven repository using search engines. For google use the search term: site:ibiblio.org mysql.
Change into the directory helloworld and let Maven resolve the libraries.
cd helloworld mvn dependency:resolve
Import and run the project
IntelliJ and Netbeans support Maven out of the box. Eclipse requires the M2Eclipse plugin.
Alternatively using Eclipse you may type the following to create normal eclipse project files.
mvn eclipse:eclipse
Inside of your IDE deploy the project to a webserver like Tomcat or Jetty.
Add a Hibernate configuration (1 minute)
src/main/resources/hibernate.cfg.xml.
<?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hbm2ddl.auto">create-drop</property> <property name="connection.url"> jdbc:postgresql://localhost:5432/play </property> <property name="connection.username">postgres</property> <property name="connection.password">p</property> <property name="connection.driver_class"> org.postgresql.Driver </property> <property name="dialect"> org.hibernate.dialect.PostgreSQLDialect </property> <property name="cache.provider_class"> org.hibernate.cache.NoCacheProvider </property> <mapping class="de.laliluna.helloworld.domain.Book"/> </session-factory> </hibernate-configuration>
Create the book entity (1 minute)
src/main/java/de/laliluna/helloworld/Book.java.
@Entity public class Book { @GeneratedValue @Id private Integer id; private String title; @Temporal(TemporalType.DATE) private Date published; @Override public String toString() { final StringBuilder sb = new StringBuilder(); sb.append("Book"); sb.append("{id=").append(id); sb.append(", title='").append(title).append('\''); sb.append(", published=").append(published); sb.append('}'); return sb.toString(); } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Date getPublished() { return published; } public void setPublished(Date published) { this.published = published; } }
Create a sortable data grid (3 minutes)
In the Index.tml page, we are going to add the grid.
Index.tml.
<html t:type="layout" title="newapp Index" t:sidebarTitle="Current Time" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" xmlns:p="tapestry:parameter"> <t:grid source="allBooks" /> ... snip ...
Tapestry has a concept of a template and a corresponding Java class. The template can access properties of the Java class. As the grid uses allBooks, we need to add a getAllBooks method to the Index.java class.
Index.java.
import org.apache.tapestry5.ioc.annotations.Inject; import org.hibernate.Session; import java.util.*; /** * Start page of application newapp. */ public class Index { @Inject private Session session; public List<Book> getAllBooks(){ return session.createQuery("select b from Book b").list(); } ... snip ...
Redeploy your application and have a look at your sortable data grid.
As it is annoying, to have an empty grid, we will add an action link to create random books. An action link calls an action method on the Java class.
Index.tml.
<html t:type="layout" title="helloworld Index" t:sidebarTitle="Current Time" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" xmlns:p="tapestry:parameter"> <div> <t:actionlink t:id="createRandomBook">Create a random book</t:actionlink> </div> <t:grid source="allBooks" /> ... snip ...
The corresponding action method in the Index.java file creates a book. Please take care that the method name corresponds to the t:id of the action link. Tapestry uses a lot of conventions like that. As the method returns null, you will stay on the same page.
Index.java.
public class Index{ @Inject private Session session; @CommitAfter public Object onActionFromCreateRandomBook(){ session.save(new Book("Hibernate " + new Random().nextInt(100), new Date())); return null; } ... snip ...
You should be able to create books, see them in the table and sort them by now.
Dialog to create a book (4 minutes)
Of course, you need to create real books as well and input the data.
Create a new template for the dialog.
/src/main/resources/de/laliluna/helloworld/pages/CreateBook.tml.
<html t:type="layout" title="Create a book" t:sidebarTitle="Current Time" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" xmlns:p="tapestry:parameter"> <t:beaneditform exclude="id" object="book"/> </html>
The t:beaneditform is a powerful Tapestry component which creates a dialog directly from your model. The model must be provided in the corresponding Java class. In addition, the CreateBook.java class has a onSuccess method being called, when the form is submitted.
/src/main/java/de/laliluna/helloworld/pages/CreateBook.java.
public class CreateBook { /* @Property makes the book available to the EditBook.tml page. */ @Property private Book book; @Inject private Session session; /* Inject a page. It is used later for navigation purpose. */ @InjectPage private Index index; /** * Commit the transaction using {@code session.getTransaction().commit()} * right after themethod was executed. */ @CommitAfter Object onSuccess() { session.saveOrUpdate(book); /* Return the injected page to navigate to the page. */ return index; } }
Finally, add a link to your index.tml to be able to navigate to the new page.
<div> <t:pagelink page="CreateBook">Create a new book</t:pagelink> </div>
The t:beaneditform component is powerful because it is flexible. You can override each input elements and adapt it to your needs. Let’s add a character LOB to the book and a <textarea> input to the dialog.
Book.java.
@Entity public class Book { @Lob private String description; public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } ... snip ...
We need to change the CreateBook.tml to override the description to make use of an textarea.
CreateBook.tml.
<html t:type="layout" title="Create a book" t:sidebarTitle="Current Time" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" xmlns:p="tapestry:parameter"> <t:beaneditform exclude="id" object="book"> <t:parameter name="description"> <t:label for="description">My description</t:label> <t:textarea t:id="description" value="book.description"/> </t:parameter> </t:beaneditform> </html>
Just check that everything is working.
The order of getter and setter in the Book class is relevant for the order of the form properties. You might consider to add the getter and setter of the description field at the end. Alternatively, there is an @Reorder annotation, which allows to define the order in the grid and in the t:beaneditform.
Editing a book (3 minutes)
In order to open an edit book dialog, we will modify the table displaying all books. Clicking on the book title in the grid, should allow you to edit a book. We need to modify how the title is rendered in the grid, add a new template for the edit dialog and a corresponding Java class with the Hibernate code to update the book.
The link first:
Modify the t:grid component. We override the cell for the title.
Index.tml.
<t:grid source="allBooks" row="book"> <p:titleCell> <t:pagelink page="EditBook" context="book.id">${book.title}</t:pagelink> </p:titleCell> </t:grid>
The grid makes use of a book property to store the current book while iterating. Therefor the corresponding Java class Index.java needs to provide such a property. Add the following code.
Index.java.
@Property private Book book;
Create a new template.
/src/main/resources/de/laliluna/helloworld/pages/EditBook.tml.
<html t:type="layout" title="Edit a book" t:sidebarTitle="Current Time" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd" xmlns:p="tapestry:parameter"> <t:beaneditform exclude="id" object="book"> <t:parameter name="description"> <t:label for="description">Description</t:label> <t:textarea t:id="description" value="book.description"/> </t:parameter> </t:beaneditform> </html>
Create the corresponding Java class.
/src/main/java/de/laliluna/helloworld/pages/EditBook.java.
public class EditBook { /* @Property makes the book available to the EditBook.tml page. */ @Property private Book book; @Inject private Session session; /* Inject a page. It is used later for navigation purpose. */ @InjectPage private Index index; /** * Is called before the page is rendered. A value encoder provided by the * Tapestry Hibernate module, knows how to convert the id at the end of the * URL localhost:8080/editbook/2 into an instance of {@link Book} * @param book the book to edit */ void onActivate(Book book){ this.book = book; } /** * On redirecting to the same page for example, if validation fails, it * is required to make Tapestry aware of the page context object. * * Otherwise you will loose the information which book was edited. * @return the edited book */ Book onPassivate(){ return book; } /** * Commit the transaction using {@code session.getTransaction().commit()} * right after the method was executed. */ @CommitAfter Object onSuccess() { session.saveOrUpdate(book); /* Return the injected page to navigate to the page. */ return index; } }
You have completed a first web application using Hibernate.
How does it work behind the scenes?
Tapestry provides it own Dependency Injection Framework. It is used internally to deal with the configuration, page and component handling. You can use it for your own code as well or make use of the integration with Spring, Google Guice, or EJB3.
Dependency injection inject all what you need into a page. The @Inject annotation in the following code let Tapestry inject a Hibernate Session.
public class EditBook { @Inject private Session session;
Where does the session come from?
The Tapestry Hibernate module makes use of distributed configuration supported by Tapestry. When Tapestry starts the module, it builds a Hibernate SessionFactory and registers a factory for the Hibernate session. Whenever you inject a Hibernate session it is created by the factory using the normal sessionFactory.openSession() you have already used in the first example.
The module registers a method interceptor as well. It is called whenever the Tapestry Dependency Injection framework finds a @CommitAfter annotation. It is pretty common to let the transaction be handled by dependency injection frameworks. You might have come across the term Aspect Oriented Programming (AOP). Transaction handling is an aspect, which can be provided by method interceptors and there is no need to bloat your code with it.
In fact many technologies (EJB, Spring, Google Guice, Pico-Container) makes use of this approach.
I hope you got a good impression, how Hibernate can be integrated into a web framework. Component based frameworks are great as you can use and develop powerful reusable components. I can only recommend to try out Tapestry. It is one of the best frameworks, I am aware of. Have you seen the live class reloading of Tapestry? You only need to save a file and reload the page in your browser to see your pages.
By the way, you will find a powerful Hibernate integration in the Wicket Framework as well.