Chapter 4. A more complex example – web application

Table of Contents

Summary

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.

What is Maven?

Maven is a dependency management solution and project build tool.

  • It allows to define libraries which are required by your application.
  • It downloads the libraries and libraries required by those libraries from Maven repositiories on the Internet.
  • It can setup an Eclipse, Netbeans or IntelliJ project.
  • It can package your own projects for production deployment as well.
  • The central Maven repositiories provide nearly all Java Open Source libraries.

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.

images/introduction/tapestry-helloworld.png

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

Other JDBC driver

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.

images/introduction/tapestry-helloworld-grid.png

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>
images/introduction/tapestry-helloworld-create.png

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.

Order of properties

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.

Summary

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.

Note

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.