Chapter 9. Querying with Criteria and Example

In the previous chapter, you saw how to use Hibernate Query Language (HQL) and Java Persistence Query Language (JPA QL). HQL forces you to interact with the database using SQL-like syntax. You must have some knowledge of SQL to create complex queries, which makes it difficult to develop applications that use multicriteria queries.

With HQL and JPA QL, it's easy to make mistakes with string concatenation and SQL syntax. Sometimes, errors occur due to the simple fact that there is no space between two clauses in SQL. Because these errors are uncovered only at runtime, if you have no unit test, you must go through the long process of build and deployment on an application server to catch them.

Unlike HQL, Hibernate Criteria is completely object oriented. It uses objects and their interfaces to load data from Hibernate-supported databases. It presents an elegant and cleaner way to build dynamic queries. For example, the following code invokes the Criteria API to fetch the Book details from the database given a book's name and publisher:

Criteria criteria = session.createCriteria(Book.class);
if (startDate != null) {
                criteria.add(Expression.ge("bookName",bookName);
}
if (endDate != null) {
        criteria.add(Expression.le("publisher",publisher);
}
List results = criteria.list();

The same code when used with HQL must deal with string concatenation and setting parameters:

String queryString = "from Book where bookName=? and publisher=?";

        Session session = SessionManager.getSessionFactory().getCurrentSession();
        Query query = session.createQuery(arg0);

        query.setParameter(0, "Recipes");
        query.setParameter(1, "APress");
        List<Book7_1> list = query.getResultList();

As you can see, accessing the data using Criteria is simpler and easy.

In this chapter, you learn how to use Criteria and the Example API. The Java Persistence API version 2.0 (JSR-317 JPA 2.0) has introduced a similar Criteria API to support dynamic queries. This is still evolving; you can find the documentation at http://openjpa.apache.org/builds/latest/docs/manual/jpa_overview_criteria.html.

The chapter shows how to apply restrictions to criterion objects to fetch data. These restrictions act like the where clause in HQL or SQL. You learn how to use a criteria object that isn't in a session: this is called a DetachedCriteria.

Using Criteria

Problem

How do you create a basic criteria object and use it to load data from a database?

Solution

As with HQL, you get the criteria object from the session. If you don't have a session to create a criteria object, then you can use DetachedCriteria. This DetachedCriteria object is later used with the session to get the results. This recipe demonstrates the basic implementation of the Criteria API.

How It Works

A basic Criteria query looks like this:

Criteria criteria = session.createCriteria(Book.class)
List books = criteria.list();

This query corresponds to the following HQL query:

from Book book

Most methods in the Criteria class return an instance of themselves, so you can build up your criteria as follows. Here you add a restriction to your criteria, which says that the name of the book should be equal to "Hibernate Quickly":

Criteria criteria = session.createCriteria(Book.class)
.add(Restrictions.eq("name", "Hibernate Quickly"));
List books = criteria.list();

If you don't have an open session, you can instantiate a detached criteria by using the forClass() method and later attach it to a session for execution. When you have a session to run the query, you call getExecutableCriteria() and pass the session as a method argument. The getExecutableCriteria() method returns an executable instance of criteria:

DetachedCriteria criteria = DetachedCriteria.forClass(Book.class);
List books = criteria.getExecutableCriteria(session).list();

Using Restrictions

Problem

How do you filter data when using criteria? How do you apply restriction to criteria?

Solution

You can add a series of restrictions to your Criteria query to filter the results, just as you build up the where clause in HQL. The Restrictions class provides a variety of methods to build restrictions.

How It Works

Each restriction you add is treated as a logical conjunction. The Criterion interface is an object-oriented representation of a query criterion that may be used as a restriction in a Criteria query. The Restrictions class provides static factory methods that return built-in criterion types. The org.hibernate.criterion package provides an API for creating complex queries:

Criteria criteria = session.createCriteria(Book.class);
Criterion nameRest = Restrictions.eq("name", "Hibernate Recipes");
criteria.add(nameRest);
List books = criteria.list();

This is equivalent to the following HQL:

from Book book where name='Hibernate Recipes'

eq is an equals implementation that takes two input method arguments: the name of the property of the Book class and the value to be compared with. To get a unique result, you can use the uniqueResult() method:

Criteria criteria = session.createCriteria(Book.class);
Criterion nameRest = Restrictions.eq("name", "Hibernate Recipes");
criteria.add(nameRest);
Book book = (Book)criteria.uniqueResult();

You can use the ignoreCase() method to add case insensitivity to the query:

Criterion nameRest = Restrictions.eq("name", "Hibernate Recipes").ignoreCase();

The Restrictions class has many other methods, including the following:

  • gt (greater than)

  • lt (less than)

  • ge (greater than or equal to)

  • idEq (ID is equal to)

  • ilike (a case-insensitive like, similar to PostgreSQL's ilike operator)

  • isNull

  • isNotNull

  • isEmpty

  • isNotEmpty

  • between

  • in (applies an "in" constraint to the named property)

  • le (less than or equal to)

Here's an example:

Criteria criteria = session.createCriteria(Book.class)
        .add(Restrictions.like("name", "%Hibernate%"))
        .add(Restrictions.between("price", new Integer(100), new Integer(200)));
List books = criteria.list();

This Criteria query corresponds to the following HQL query:

from Book book
where (book.name like '%Hibernate%') and (book.price between 100 and 200)

The % character is a wildcard. Because it's added before and after the word Hibernate, it enables you to search for all books that have the word Hibernate in a book name. If you want to look for all books whose name starts with Hibernate, the query is as follows:

Criteria criteria = session.createCriteria(Book.class)
        .add(Restrictions.like("name", "Hibernate%"))
.add(Restrictions.between("price", new Integer(100), new Integer(200)));
List books = criteria.list();

Notice that there is no % character before the word Hibernate in the search string. You can also group some of the restrictions with logical disjunction. How do you achieve the following query using the Criteria API?

from Book book
where (book.name like '%Hibernate%' or book.name like '%Java%') and (book.price between 100
and 200)

You can directly translate it into something like this:

Criteria criteria = session.createCriteria(Book.class)
        .add(Restrictions.or(
                        Restrictions.like("name", "%Hibernate%"),
                        Restrictions.like("name", "%Java%")
                )
)
.add(Restrictions.between("price", new Integer(100), new Integer(200)));
List books = criteria.list();

This query can also be written using Restrictions' disjunction() method:

Criteria criteria = session.createCriteria(Book.class)
                .add(Restrictions.disjunction()
                                .add(Restrictions.like("name", "%Hibernate%"))
                                .add(Restrictions.like("name", "%Java%"))
                )
                .add(Restrictions.between("price", new Integer(100), new Integer(200)));
List books = criteria.list();

If you want to search using wildcards, you can use the MatchMode class. It provides a convenient way to express a substring match without using string manipulation:

Criteria criteria = session.createCriteria(Book.class);
Criterion nameRest = Restrictions.like("name", "Hibernate",MatchMode.START);
criteria.add(nameRest);
Book books = (Book)criteria.uniqueResult();

This Criteria statement is the same as

Criterion nameRest = Restrictions.like("name", "Hibernate%");

The following MatchMode values are available:

  • START: Matches the start of the string with the pattern

  • END: Matches the end of the string with the pattern

  • ANYWHERE: Matches the pattern anywhere in the string

  • EXACT: Matches the complete pattern in the string

Writing Subqueries

Using Criteria, you can also execute subqueries. A subquery is a query that is nested inside another query. It's also called as inner query or inner select.

For example, here's a subquery in SQL:

select publisher_name, address from Publisher pub where pub.code=(select publisher_code from Book book where book.publish_date=current_timestamp)

This query fetches the name and address of any publisher whose book was published today. The second select query is nested in the main SQL query.

You can write this using Criteria as follows:

DetachedCriteria todaysBook = DetachedCriteria.forClass(Book.class)
.setProjection( (Projection) Property.forName("publishDate").eq("current_timestamp") );
List =manager.createCriteria(Publisher.class)
            .add( Property.forName("publisherCode").eq(todaysBook) )
            .list();

Using Criteria in Associations

Problem

How do you express a join restriction using the Criteria API when there are associated objects? How do you use dynamic fetching?

Solution

When you have associated objects, you need to join them in order to retrieve data from them. In HQL, you use an alias and an equijoin on the foreign key in the where clause:

from Book b, Publisher p where b.publisherId = p.publisherId

This query joins the Publisher object and Book object with publisherId, which is the foreign key in Book.

Let's see how this is achieved using the Criteria API. Hibernate provides two methods to enable joining of associated entities: createCriteria() and createAlias(). The difference between the two is that createCriteria() returns a new instance of Criteria. It means you can nest calls to createCriteria(). The createAlias() method doesn't create a new instance of Criteria and is very similar to the alias you declare in the HQL from clause.

How It Works

You can reference an association property by its name to trigger an implicit join. For example, suppose you have an Order class that contains a Price object, which holds details as unitPrice and currency:

public class Order {

        Price price;
}

public class Price {

                String currency;
                float unitPrice;
}

If you need to fetch the unitPrice of an Order, you can use Order.price.unitPrice in HQL:

from Order order where order.price.unitPrice > 50

Can the same implicit join work using Criteria?

Criteria criteria = session.createCriteria(Book.class,"book")
.add(Restrictions.like("name", "%Hibernate%"))
.add(Restrictions.eq("book.publisher.name", "Manning"));
List books = criteria.list();

If you execute this code, the following exception is thrown:

Exception in thread "main" org.hibernate.QueryException: could not resolve property:

publisher.name of: com.hibernaterecipes.chapter5.Book
    at
org.hibernate.persister.entity.AbstractPropertyMapping.propertyException(AbstractPropertyMapping.java:67)

Hibernate can't resolve the property publisher.name. That means an implicit join isn't supported for Criteria queries. To join an association explicitly, you need to create a new criteria object for it:

Criteria criteria = session.createCriteria(Book.class)
        .add(Restrictions.like("name", "%Hibernate%"))
        .createCriteria("publisher")
        .add(Restrictions.eq("name", "Manning"));
List books = criteria.list();

In this example, the createCriteria() method has a string attribute, where you pass the name of the associated property. Because you're creating a criteria on the publisher property of the Book object, you need to pass "publisher" in this case. You can also use createAlias() to generate the same query:

Criteria criteria = session.createCriteria(Book.class)
                .createAlias("publisher", "publisherAlias")
                .add(Restrictions.like("name", "%Hibernate%"))
                .add(Restrictions.eq("publisherAlias.name", "Manning"));
                List books = criteria.list();

In HQL, the query is as follows:

from Book book
where book.name like '%Hibernate%' and book.publisher.name = 'Manning'

As in HQL, you can specify the fetching strategy that Hibernate should follow when it loads data in case of associations. This strategy can also be set in the metadata configuration file. If the configuration needs to be overridden, then you can use the FetchMode API when you retrieve data using Criteria.

Following are the FetchMode options are available:

  • DEFAULT: The default setting that is used from the configuration file.

  • EAGER: Deprecated. Use FetchMode.JOIN instead.

  • JOIN: Retrieves the associated object's data by using an outer join.

  • LAZY: Deprecated. Use FetchMode.SELECT instead.

  • SELECT: Fetches eagerly using an additional SELECT query. Unless the lazy option is disabled explicitly in the configuration file, the second SELECT query is executed when the association is accessed.

The following code uses the FetchMode.JOIN and FetchMode.SELECT options:

Criteria criteria = session.createCriteria(Book.class)
.add(Restrictions.like("name", "%Hibernate%"))
.setFetchMode("publisher", FetchMode.JOIN)
.setFetchMode("chapters", FetchMode.SELECT);
List books = criteria.list();

In this case, the associated Publisher object is fetched using an outer join, where Chapters is retrieved with a second SELECT query.

Using Projections

Problem

What are projections, and how do you use them in the Criteria API?

Solution

Until now, when you retrieved objects using Criteria, you fetched the complete object in the result set. What if you want to fetch only certain fields or columns in a table, and you don't want the complete object?

Projections help you filter data and return only those fields you need. You can use projections when you don't want the entire fetched entity and its associations. You can also use them when there is a requirement to get the results in a flat manner (not in an object graph). This approach improves performance, especially when you're dealing with lot of columns and the object graph is very deep.

How It Works

To customize the fields of the result, you can use projections to specify the fields to be returned:

Criteria criteria = session.createCriteria(Book.class)
.setProjection(Projections.property("name"));
List books = criteria.list();

This is similar to the following SQL statement:

select book.name from Book book

This is better than

from Book book

which fetches the complete book object although you need only the book name.

Aggregate Functions and Groupings with Projections

Aggregate functions in SQL summarize data and return a single value by calculating it from values in a column. Here are some examples:

  • SUM: Returns the summation of the column values on which it's applied

  • AVG: Returns the average of the column values

  • MIN: Returns the minimum of the specified column values

  • MAX: Returns the maximum of the specified column values

In HQL, you can use these directly just as in SQL:

select min(book.price) from Book book

This query returns the minimum price of all the books in the BOOK table.

When you're querying with the Criteria API, you can use projections to apply the aggregated functions. These functions are encapsulated in the Projections class as static methods:

Criteria criteria = session.createCriteria(Book.class)
.setProjection(Projections.avg("price"));
List books = criteria.list();

This is similar to the following:

select avg(book.price) from Book book

The results can be sorted in ascending or descending order:

Criteria criteria = session.createCriteria(Book.class)
.addOrder(Order.asc("name"))
.addOrder(Order.desc("publishDate"));
List books = criteria.list();

This is something like

from Book book
order by book.name asc, book.publishDate desc

Grouping is also supported for Criteria queries. You can assign any properties as group properties, and they appear in the group by clause:

Criteria criteria = session.createCriteria(Book.class)
.setProjection(Projections.projectionList()
.add(Projections.groupProperty("publishDate"))
.add(Projections.avg("price")));
List books = criteria.list();

Thisresults in

select book.publishDate, avg(book.price)
from Book book
group by book.publishDate

Querying by Example

Problem

What is Query by Example (QBE), and how do you use it to retrieve data?

Solution

Suppose you need to retrieve data based on different input. The search criteria can be complex filtering data on various columns. Using Criteria, it might look like this:

Criteria criteria = getSession().createCriteria(Book.class);
        criteria.add(Restrictions.eq("name", "Hibernate")).
                add(Restrictions.eq("price",100))

If you have more columns to be filtered, the list increases. QBE makes this easy for you.

QBE returns a result set depending on the properties that were set on an instance of the queried class. You can also specify the properties that need to be excluded. This approach greatly reduces the amount of code you have to write.

How It Works

Criteria queries can be constructed through an Example object. The object should be an instance of the persistent class you want to query. All null properties are ignored by default:

Book book = new Book();
book.setName("Hibernate");
book.setPrice(new Integer(100));
Example exampleBook = Example.create(book);
Criteria criteria = session.createCriteria(Book.class)
.add(exampleBook);
List books = criteria.list();

The HQL is similar to the following:

from Book book
where book.name = 'Hibernate' and book.price = 100

In this example, the instance of Book is created, and the search criteria are set: they include name and price. This Book instance is then passed while creating an Example object. Finally, the Criteria interface accepts the Example instance to retrieve the data based on the values set on the Book instance.

You can specify creation rules for the construction of the Example object. For example, you can exclude some of the properties or enable like for a string comparison:

Book book = new Book();
book.setName("%Hibernate%");
book.setPrice(new Integer(100));
Example exampleBook = Example.create(book)
.excludeProperty("price")
.enableLike();
Criteria criteria = session.createCriteria(Book.class)
.add(exampleBook);
List books = criteria.list();

The corresponding HQL is as follows:

from Book book
where book.name like '%Hibernate%'

Summary

This chapter has introduced the Criteria API and how to use it to retrieve data. You can use restrictions to filter data. The Restrictions interface has several methods that let you filter data: equal, like, greater than, and less than. It also provides disjunctions. The MatchMode interface provides variables that you can use for string comparisons. Subqueries also can be implemented using the DetachedCriteria object. You can use the createCriteria() and createAlias() methods can be used to fetch data when there are associated objects. And you can use projections for aggregated functions and groupings. Finally, Query by Example makes it easy to fetch data when the data has to be filtered on various search criteria.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.255.87