Using Hibernate-specific features with JPA

The Java Persistence API offers a powerful standard for persisting POJOs.It includes all the most important features which you would expect from an object-relational mapping tool, but there are still some areas where you might need to use vendor-specific features. In this tutorial, we will show how to use Hibernate-specific features for validating and querying while using the standard API for the rest.

Prerequisites

This tutorial assumes you have some basic knowledge of, or programming experience with, the following technologies:

  • Java Server Faces
  • Java Persistence API.
  • NetBeans IDE

This tutorial is partly based on the Using Hibernate With Java Persistence API tutorial, you might want to go through it first.

Software Needed for the Tutorial

Before you begin, you need to install the following software on your computer:

  • NetBeans IDE 5.5.1 (download)
  • Sun Java System Application Server, Platform Edition 9
  • Hibernate Core 3.2.1.GA, Hibernate EntityManager 3.2.1.GA(download)

Setting up your Environment

First we will create a library in the IDE for the Hibernate entity manager.

  1. Unzip the Hibernate JAR files to any temporary location
  2. Choose Tools > Library Manager from the main menu. Click New Library, name the library Hibernate, and click OK.
  3. In the Libraries pane of the Library Manager dialog box, select the Hibernate library you just created.
  4. Click the Classpath tab and then click Add JAR/Folder and locate the following JAR files:
  • From Hibernate EntityManager:
    • hibernate-entitymanager.jar
    • lib/hibernate-annotations.jar
    • lib/jboss-archive-browsing.jar
    • lib/javassist.jar
  • From Hibernate Core:
    • hibernate3.jar
    • lib/antlr-2.7.6.jar
    • lib/asm-attrs.jar
    • lib/asm.jar
    • lib/c3p0-0.9.0.jar
    • lib/cglib-2.1.3.jar
    • lib/commons-collections-2.1.1.jar
    • lib/concurrent-1.3.2.jar
    • lib/commons-logging-1.0.4.jar
    • lib/dom4j-1.6.1.jar
    • lib/ehcache-1.2.3.jar
    • lib/log4j-1.2.11.jar

Creating a Web Application

Next we will create a web application with entity classes and a JSF interface. To speed up the process, we will use the CRUD generators in the IDE.
  • Choose File > New Project (Ctrl-Shift-N). Select Web Application from the Web category.
  • Name the project HibernateWithJPA, set the server to Sun Java System Application Server, the Java EE version to Java EE 5 and make sure that the Set Source Level to 1.5 checkbox is checked. Click Next.
  • Select the JavaServer Faces framework and click Finish.
  • Add the Hibernate library we created earliead to the project by right-clicking the project node and choosing Properties > Libraries > Add Library > Hibernate
  • In the Projects window, right-click the project node and choose New > Entity Classes from Database.
  • In the Data Source drop-down, select the jdbc/sample database. If prompted for a user name and password, use app and app. Select the CUSTOMER table from the list, click Add and then click Next. * Leave the tables and class names as their default, and set the package to sample.entities.
  • Now it is time to create the persistence unit. Click the Create Persistence Unit button. Accept the default name, select Hibernate as the persistence provider, and select None as the table generation strategy. Click OK.
  • Click Finish in the Entity Classes from Database wizard. The IDE creates the persistence unit (persistence.xml under Configuration Files node) and the entity classes (under Source Packages).
  • Set a correct SQL dialect for Hibernate. Double-click persistence.xml and click the XML button at the top of the editor to switch to the XML view. Add the following property to the persistence unit:
        <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />

Coding the JSF Interface

Now we can quickly generate JSF pages for the entity classes with the NetBeans IDE CRUD generation.

  1. Right-click the project node and choose New > JSF Pages from Entity Class. Add both Customer.java and DiscountCode.java to the list of selected classes and click Next.
  2. Change the package to sample.controller and click Finish.

Testing the Project

At this point we will try to run the application and see whether everything is working as expected.

  1. Right-click the project and choose Run Project. The IDE starts the application server, builds and deploys the application, and shows index.jsp in the external web browser.
  2. Click the List of Customer link and you should see the following web page:
  • Use the New Customer link to create a customer

Adding validation

When creating a new customer you might have noticed that values were not validated before attempting to persist the newly created customer. We could use the validation facilities in JSF for ensuring that only correct values are inserted, but since this tutorial is about Hibernate we will instead demonstrate how to use the Hibernate validation framework. This approach has an additional advantage that the validation rules need to be specified only once even if another type of client is added.

Open Customer.java in the editor and add the following annotations on its member variables:

    @Column(name = "STATE")
    @Length(min=2, max=2, message="not a valid state")
    private String state;
    @Column(name = "EMAIL")
    @Email
    private String email;
For simplicity's sake we will only add validation to state and email properties. The @Length annotation validates that the length of the property will be within the range specified by min and max attributes. We apply it on the state property to make sure that it is exactly 2 characters long. We also specify a value for the message attribute, it will be the shown error message when the validation fails. The @Email annotation in turn validates that the property represents a valid email address, as you might have guessed.

Now that we have our domain object annotated, we need to add handling of validation errors to our controller class. So open CustomerController.java and add the following method there:

    /**
     * Validates the given customer.
     * @return true if there were no validation errors, false otherwise.
     */ 
    private boolean validate(Customer customer){
        ClassValidator customerValidator = new ClassValidator(Customer.class);
        // get the invalid values 
        InvalidValue[] msgs = customerValidator.getInvalidValues(customer);
        if (msgs.length > 0){
            for(InvalidValue msg : msgs){
                // add an error message for each invalid value, these
                // messages will be shown to the user
                addErrorMessage(msg.getMessage());
            }
            return false;
        }
        return true;
    }
This method will first create a class validator for our Customer class and then process the validation rules we specified earlier when. We collect the invalid value messages and add each of them as error messages to the FacesContext (this is done by the addErrorMessage method). If there were no validation errors the will return true, false otherwise. Of course, as such this method is not very useful unless we invoke it in the right places. We probably want to validate the values both when a new customer is created and when an existing customer is edited. So let's first modify the create method to check whether there were any validation errors before attempting to persist:
    public String create() {
        if (!validate(customer)){
            // return to the input page if there were any validation errors
            return null;
        }
        EntityManager em = getEntityManager();
        try {
            utx.begin();
            em.joinTransaction();
            em.persist(customer);
 ... 

As you can see, we return null if any errors were found - this means JSF will display the same page again. Make a similar modification to the edit method as well and run the application. Try to create a new customer with an invalid email address and with a 3 characters long state code. This is what you should see:

Query by Example

While the Java Persistence QL is an impressive query language, there are cases when a different kind of API is more suitable. Luckily, in addition to JPQL support, Hibernate features a criteria query API which you can leverage for the cases it is needed and stick to the standard API elsewhere in the application. In the following example we will demonstrate the Query By Example approach using Hibernate's Criteria API.

First we need to create a new page for our new query functionality. Create a new page named Query.jsp in the customer folder and paste the following to it:

<%@page contentType="text/html"%>
<%@page pageEncoding="UTF-8"%>

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Query By Example</title>
    </head>
    <body>
        <f:view>
            <h1>Query By Example</h1>
            <h:messages errorStyle="color: red" infoStyle="color: green" layout="table"/>
            <h:form>
                <h:panelGrid columns="2">
                    <h:outputText value="Zip:"/>
                    <h:inputText id="zip" value="#{customer.customer.zip}" converter="stringConverter" title="Zip" />
                    <h:outputText value="Name:"/>
                    <h:inputText id="name" value="#{customer.customer.name}" converter="stringConverter" title="Name" />
                    <h:outputText value="State:"/>
                    <h:inputText id="state" value="#{customer.customer.state}" converter="stringConverter" title="State" />
                </h:panelGrid>
                <h:commandLink action="#{customer.queryByExample}" value="Search"/>
            </h:form>
            <h:form>
                <a href="/HibernateWithJPA/index.jsp">Back to index</a>
                <br>
                <h:dataTable value='#{customer.model}' var='item' border="1" cellpadding="2" cellspacing="0">
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="CustomerId"/>
                        </f:facet>
                        <h:commandLink action="#{customer.detailSetup}" value="#{item.customerId}"/>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="Zip"/>
                        </f:facet>
                        <h:outputText value="#{item.zip}"/>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="Name"/>
                        </f:facet>
                        <h:outputText value="#{item.name}"/>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="Addressline1"/>
                        </f:facet>
                        <h:outputText value="#{item.addressline1}"/>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="Addressline2"/>
                        </f:facet>
                        <h:outputText value="#{item.addressline2}"/>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="City"/>
                        </f:facet>
                        <h:outputText value="#{item.city}"/>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="State"/>
                        </f:facet>
                        <h:outputText value="#{item.state}"/>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="Phone"/>
                        </f:facet>
                        <h:outputText value="#{item.phone}"/>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="Fax"/>
                        </f:facet>
                        <h:outputText value="#{item.fax}"/>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="Email"/>
                        </f:facet>
                        <h:outputText value="#{item.email}"/>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="CreditLimit"/>
                        </f:facet>
                        <h:outputText value="#{item.creditLimit}"/>
                    </h:column>
                    <h:column>
                        <f:facet name="header">
                            <h:outputText value="DiscountCode"/>
                        </f:facet>
                        <h:outputText value="#{item.discountCode}"/>
                    </h:column>
                    <h:column>
                        <h:commandLink value="Destroy" action="#{customer.destroy}">
                            <f:param name="customerId" value="#{item.customerId}"/>
                        </h:commandLink>
                        <h:outputText value=" "/>
                        <h:commandLink value="Edit" action="#{customer.editSetup}">
                            <f:param name="customerId" value="#{item.customerId}"/>
                        </h:commandLink>
                    </h:column>
                </h:dataTable>
            </h:form>
        </f:view>
    </body>
</html>
There are a couple of things to note here. Firstly, we have specified "stringConverter" converter for the input fields. Secondly, the 'search' link will try to execute customer.queryByExample method, which we haven't yet implemented. Thirdly, the data table uses customer.model as its underlying model. We will get back to these in a minute, but before that we still need to create a link to our new page. To keep things simple, we will just add it to the customer/List.jsp page, right after the link to the New Customer page:
<h:commandLink action="#{customer.createSetup}" value="New Customer"/>
<br>
<h:commandLink action="#{customer.querySetup}" value="Query Customers"/>

Now, this link will cause querySetup method to be invoked in the CustomerController.java, so let's implement it next. To do that, add the following to the CustomerController.java:

   public String querySetup(){
        this.customer = new Customer();
        this.model = null;
        return "customer_query";
    }

And to complete the UI side of things, we still need to add a navigation rule to faces-config.xml:

<navigation-rule>
    <navigation-case>
      <from-outcome>customer_query</from-outcome>
      <to-view-id>/customer/Query.jsp</to-view-id>
    </navigation-case>
  </navigation-rule>

What is still missing is the actual implementation of the query, and the converter we mentioned earlier. Let's tackle the implementation of the query method first. In the Query.jsp page we defined that the 'Search' link will invoke customer.queryByExample method and that customer.model will be used for the data table. To satisfy the latter, we just need to create a getter for the model in CustomerController.java - press ctrl-space and choose 'create getter getModel for field model'. After that, add the following method:

    /**
     * Queries customers based on the values in our <code>customer</code>.
     */
    public String queryByExample(){
        // get the native hibernate session
        Session session = (Session) getEntityManager().getDelegate();
        // create an example from our customer, exclude all zero valued numeric properties 
        Example customerExample = Example.create(customer).excludeZeroes();
        // create criteria based on the customer example
        Criteria criteria = session.createCriteria(Customer.class).add(customerExample);
        // perform the query and set the result to our model.
        this.model = new ListDataModel(criteria.list());
        return "customer_query";
    }

You can see how easily you can access Hibernate's native API - just invoke getDelegate() on the entity manager and cast it to org.hibernate.Session. Once we have the access to Session we can take advantage of the Criteria API. In the above method we create an example criteria based on the customer and execute the query. If you are not familiar with the Criteria API, it is probably worth explaining a bit. By default, all null values will be excluded, which means that the properties on our customer that were null, will not be part of the criteria. In addition, we specify that all zero valued numeric properties will be excluded as well. Here it gets a bit complicated: since JSF by default converts strings without values to empty strings (instead of nulls), we need to create a special converter for dealing with the conversion of strings from the query page. The implementation of the converter is as simple as:

package sample.controller;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
/**
 * A converter for string values that does not 
 * convert <code>null</code> values to empty strings.
 */ 
public class StringConverter implements Converter{
    /** Creates a new instance of StringConverter */
    public StringConverter() {
    }
    public Object getAsObject(FacesContext context, UIComponent component, String value) {
        if(value == null || "".equals(value)){
            return null;
        }
        return value;
    }
    public String getAsString(FacesContext context, UIComponent component, Object value) {
        return value != null ? value.toString() : null;
    }
}

Don't forget to register the converter in faces-config.xml:

<converter>
    <converter-id>stringConverter</converter-id>>
    <converter-class>sample.controller.StringConverter</converter-class>
  </converter>

Finally, we are ready to run the application once again and test the new functionality:

Troubleshooting

In case you are using NetBeans 5.5 instead of 5.5.1, you might run into issue 90031, which causes silent failures when inserting data. The workaround is to upgrade to 5.5.1 or to manually add em.joinTransaction() after each call to utx.begin() in the generated controller classes.

Download the source code for this tutorial (info)

Attachments

HibernateWithJPA.zip Info on HibernateWithJPA.zip 46352 bytes
jsf-from-entities.JPG Info on jsf-from-entities.JPG 50178 bytes
jsf-from-entities.png Info on jsf-from-entities.png 151411 bytes
list-of-customers.JPG Info on list-of-customers.JPG 185621 bytes
list-of-customers.png Info on list-of-customers.png 314586 bytes
not-valid-customer.JPG Info on not-valid-customer.JPG 32670 bytes
not-valid-customer.png Info on not-valid-customer.png 84493 bytes
query-by-example.JPG Info on query-by-example.JPG 81683 bytes
query-by-example.PNG Info on query-by-example.PNG 152412 bytes
query-by-example.png Info on query-by-example.png 152355 bytes

NetBeans


Referenced by: