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
.
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.
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();
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.
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:
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
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();
How do you express a join restriction using the Criteria API when there are associated objects? How do you use dynamic fetching?
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.
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.
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.
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.
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
from Book book
which fetches the complete book
object although you need only the book name.
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();
select book.publishDate, avg(book.price) from Book book group by book.publishDate
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.
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%'
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.
3.144.255.87