Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Querying for persistent objects without a query language

A powerful combination of Hibernate and JavaServer Faces

  • Print
  • Feedback

Permanent storage of information is a standard feature of software applications, and several methods and technologies for Java-based applications achieve such persistence. Each method has its advantages and tradeoffs. For example, querying for objects via SQL and Java Database Connectivity (JDBC) allows dynamic queries, but has the following downside: each query type (create, read, update, delete) must be maintained individually when the database scheme changes. In some persistence strategies, queries can be named and separated from the source code so all queries are in a centralized location.

Like other persistence frameworks, Hibernate has its own object-oriented query language and supports native SQL. In addition, it includes techniques for avoiding query language statements and for executing queries in an object-oriented way. These may be the preferred techniques for developers used to the object-oriented paradigm. The Hibernate criteria query API features two query types that allow programmatic fetching of persistent objects: query by criteria (QBC) and query by example (QBE). This article illustrates a powerful application of both query APIs (see Hibernate reference documentation for more information on the query APIs). Code examples include a Web-based query, with a full roundtrip from a JSP (JavaServer Pages) page to the persistence layer and back without any query language or select statements. This functionality is achieved by combining JavaServer Faces with Hibernate.

The domain model

Before digging deeper, let's first examine the preconditions of this example's use of the criteria query API.

The domain model in Figure 1 illustrates the two query techniques.

Figure 1. Overview of persistent POJOs. Click on thumbnail to view full-sized image.

The support for mapping definitions by Java annotations was still in beta when this article was written. Therefore, the mapping is defined in XML files, as shown in the listing below.

Listing 1. Hibernate mapping definition

 <hibernate-mapping>
  <class name="com.bachlmayr.hibcrit.pojo.Customer" 
      table="customer">
    <!-- further mapping definitions see source code -->
    <set name="contracts" table="customer_contract" 
          lazy="true" cascade="save-update">
    <key column="customerId"/>
    <many-to-many class="com.bachlmayr.hibcrit.pojo.Contract" 
          column="contractId"/>
    </set>
</hibernate-mapping>



Query by criteria

To use the criteria query API, the object mapping definition must be finalized. According to the programmatically defined criteria and restrictions, a result list is fetched from the database. A simple restriction of results can be achieved by defining a class corresponding to the expected objects. Criteria queries are polymorphic by default, meaning that, not only objects of the defined class are fetched, but also instances of its subclasses. The code example below queries objects of type Customer and receives objects of type BusinessCustomer and PrivateCustomer:

Listing 2. A simple query by criteria

 Criteria crit = session.createCriteria(Customer.class);
List result = crit.list();
for(Iterator <Customer>i=result.iterator(); i.hasNext();) {
    Customer c = i.next();
    assertTrue( (c instanceof BusinessCustomer) 
            || (c instanceof PrivateCustomer));
}



Multiple criteria can be combined to reduce the result size, where several createCriteria() methods are defined within the session interface to create a Criteria object. Depending on the chosen domain model, a query for objects of type Customer may also include objects of type BusinessCustomer and PrivateCustomer (see Figure 1).

Another example for criteria is the definition of a string or a number value for an attribute. In addition, constraints, such as "greater than" or "like," can be applied by using Hibernate restrictions. A restriction is added to a criterion as visualized in the following listing:

Listing 3. Query by criteria with constraint

 Criteria crit = session.createCriteria(Customer.class);
crit.add(Restrictions.gt("customerSince", date));
Criteria crit2 = crit.createCriteria("address");
crit2.addOrder(Order.asc("zip"));
crit.addOrder(Order.asc("id"));
List result = crit.list();



The sequence of the result list can be ordered as ascending or descending by adding an Order attribute to a Criterion. Multiple Orders can be combined as shown in the example: First, the result list is ordered as ascending by ZIP codes. Then, within the same ZIP codes, the objects are ordered by ID. The two criteria are combined to use associations as an order parameter. In the example, the associated criterion Address is added to the criterion Customer.class to use the attributes of Address for sorting. The constraint "greater than"—Restrictions.gt()—is applied to reduce the number of returned clients with a date value. Further constraints like le() (less than or equal to) or like() and ilike() (case-insensitive "like") exist.

The criteria query technique features flexible ways of conducting queries, such as using reflection for criteria creation, where the queries executed during runtime do not need to be specified before compilation or deployment. Instead of building criteria objects programmatically, existing object instances can be used for querying as illustrated below.

Query by example

An existing object can be used as a pattern to search for similar objects by using example queries. Attributes with a non-null value are used as a comparison parameter by default, but it is possible to ignore existing properties by calling the method excludeProperty() with the appropriate property.

A query by example is demonstrated in the following code, which queries for contracts assigned by customers attended by a certain key account manager. In other words: "The turnovers must be analyzed—please show me all contracts of customers assigned to key account manager Ralph Morris."

Listing 4. Query by example

 KeyAccountMgr kam = new KeyAccountMgr();
kam.setName("Ralph Morris");
Criteria crit = session.createCriteria(Contract.class);
Criteria crit2 = crit.createAlias("customers", "customer");
Criteria crit3 = crit2.createCriteria("customer.keyAccountMgr");
crit3.add(Example.create(kam));
List result = crit3.list();



The code snippet visualizes the use of an example query; however, it does not demonstrate a reasonable use of a query by example, because the object is hard coded immediately before the query. In a real-life scenario, an example query would use an already existing object, such as one created by a user or another application, as demonstrated in the case below.

Query by example applied

To demonstrate an effective use of an example query, a walkthrough from a Web form to the database and back to the graphical user interface is illustrated here. To concentrate on the key points, the form is kept simple, with form validation omitted. A search criterion can be chosen via a select box, and a case-insensitive compare string can be inserted in a text input field. After submitting the form, a bean of type KeyAccountManager is instantiated. The search string is associated with the appropriate attribute of the bean (see Listings 5 and 7). The object, which has been created in this scenario by a Web request, is a good example for the use of an example query. The next step was introduced in the section above: the object is used for a query by example, which returns a list of corresponding persistent objects from the database. The result list then displays on the user interface. Figure 2 illustrates the described process.

  • Print
  • Feedback

Resources