Hibernate Query by Example (QBE)

What is It

Query by example is an alternative querying technique supported by the main JPA vendors but not by the JPA specification itself.

QBE returns a result set depending on the properties that were set on an instance of the queried class.

So if I create an Address entity and fill in the city field then the query will select all the Address entities having the same city field as the given Address entity.

The typical use case of QBE is evaluating a search form where the user can fill in any search fields and gets the results based on the given search fields. In this case QBE can reduce code size significantly.

When to Use

·  Using many fields of an entity in a query

·  User selects which fields of an Entity to use in a query

·  We are refactoring the entities frequently and don’t want to worry about breaking the queries that rely on them

Limitations

·  QBE is not available in JPA 1.0 or 2.0

·  Version properties, identifiers and associations are ignored

·  The query object should be annotated with @Entity

Test Data

I used the following entities to test the QBE feature of Hibernate:

·  Address (long id, String city, String street, String countryISO2Code, AddressType addressType)

·  AddressType (Integer type, String description)

Imports

The examples will refer to the following classes:

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Restrictions;
import org.junit.Test;
import java.util.List;

Utility Methods

I also made two utility methods to present a list of the two entity types:

private void listAddresses(List<Address> addresses) {
  for (Address address : addresses) {
    System.out.println(address.getId() + ", " +
      address.getCountryISO2Code() + ", " +
      address.getCity() + ", " + address.getStreet() + ", " +
      address.getAddressType().getType() + ", " +
      address.getAddressType().getDescription());
  }
}

private void listAddressTypes(List<AddressType> addressTypes) {
  for (AddressType addressType : addressTypes) {
    System.out.println(addressType.getType() + ", " +
      addressType.getDescription());
  }
}

Example 1: Equals

This example code returns the Address entities matching the given CountryISO2Code and City.

Method:

@Test
public void testEquals() throws Exception {
  Session session = (Session) entityManager.getDelegate();
  Address address = new Address();
  address.setCountryISO2Code("US");
  address.setCity("CHICAGO");
  Example addressExample = Example.create(address);
  Criteria criteria = session.createCriteria(Address.class).add(addressExample);
  listAddresses(criteria.list());
}

Result:

  75, US, CHICAGO, Los Angeles Way2, 6, Customer

  170, US, CHICAGO, Jackson Blvd 33a, 4, Delivery

  63, US, CHICAGO, Main Avenue 1, 5, Bill to

  37, US, CHICAGO, Jackson Blvd 33a, 4, Delivery

  36, US, CHICAGO, Jackson Blvd 33a, 4, Delivery

Example 2: Id Limitation

This example presents that id fields in the query object are ignored.

Method:

@Test
public void testIdLimitation() throws Exception {
  Session session = (Session) entityManager.getDelegate();
  Address address = new Address();
  address.setCountryISO2Code("US");
  address.setCity("CHICAGO");
  address.setId(100);  // setting id is ignored
  Example addressExample = Example.create(address);
  Criteria criteria = session.createCriteria(Address.class).add(addressExample);
  listAddresses(criteria.list());
} 

Result:

  75, US, CHICAGO, Los Angeles Way2, 6, Customer

  170, US, CHICAGO, Jackson Blvd 33a, 4, Delivery

  63, US, CHICAGO, Main Avenue 1, 5, Bill to

  37, US, CHICAGO, Jackson Blvd 33a, 4, Delivery

  36, US, CHICAGO, Jackson Blvd 33a, 4, Delivery

Example 3: Association Limitation

Associations of the query object are ignored, too.

Method:

@Test
public void testAssociationLimitation() throws Exception {
  Session session = (Session) entityManager.getDelegate();
  Address address = new Address();
  address.setCountryISO2Code("US");
  address.setCity("CHICAGO");
  AddressType addressType = new AddressType();
  addressType.setType(5);
  address.setAddressType(addressType);  // setting an association is ignored
  Example addressExample = Example.create(address);
  Criteria criteria = session.createCriteria(Address.class).add(addressExample);
  listAddresses(criteria.list());
}


Result:

  75, US, CHICAGO, Los Angeles Way2, 6, Customer

  170, US, CHICAGO, Jackson Blvd 33a, 4, Delivery

  63, US, CHICAGO, Main Avenue 1, 5, Bill to

  37, US, CHICAGO, Jackson Blvd 33a, 4, Delivery

  36, US, CHICAGO, Jackson Blvd 33a, 4, Delivery

Example 4: Like

QBE supports like in the query object if we enable it with Example.enableLike().

Method:

@Test
public void testLike() throws Exception {
  Session session = (Session) entityManager.getDelegate();
  Address address = new Address();
  address.setCountryISO2Code("US");
  address.setCity("AT%");
  Example addressExample = Example.create(address).enableLike();
  Criteria criteria = session.createCriteria(Address.class).add(addressExample);
  listAddresses(criteria.list());
}

 Result:

  83, US, ATLANTA, null, 6, Customer

  184, US, ATLANTA, null, 1, Shipper

  25, US, ATLANTA, null, 1, Shipper

Example 5: ExcludeProperty

We can exclude a property with Example.excludeProperty(String propertyName).

Method:

@Test
public void testExcludeProperty() throws Exception {
  Session session = (Session) entityManager.getDelegate();
  Address address = new Address();
  address.setCountryISO2Code("US");
  address.setCity("AT%");
  Example addressExample = Example.create(address).enableLike()
  .excludeProperty("countryISO2Code");
  // countryISO2Code is a property of Address
  Criteria criteria = session.createCriteria(Address.class).add(addressExample);
  listAddresses(criteria.list());
}

Result:

  154, GR, ATHENS, BETA ALPHA Street 5, 2, Consignee

  83, US, ATLANTA, null, 6, Customer

  25, US, ATLANTA, null, 1, Shipper

  184, US, ATLANTA, null, 1, Shipper

Example 6: IgnoreCase

Case-insensitive search is supported by Example.ignoreCase().

Method:

@Test
public void testIgnoreCase() throws Exception {
  Session session = (Session) entityManager.getDelegate();
  AddressType addressType = new AddressType();
  addressType.setDescription("customer");
  Example addressTypeExample = Example.create(addressType).ignoreCase();
  Criteria criteria = session.createCriteria(AddressType.class)
    .add(addressTypeExample);
  listAddressTypes(criteria.list());
}


Result:

  6, Customer

Example 7: ExcludeZeroes

We can ignore 0 values of the query object by Example.excludeZeroes().

Method:

@Test
public void testExcludeZeroes() throws Exception {
  Session session = (Session) entityManager.getDelegate();
  AddressType addressType = new AddressType();
  addressType.setType(0);
  addressType.setDescription("Customer");
  Example addressTypeExample = Example.create(addressType)
    .excludeZeroes();
  Criteria criteria = session.createCriteria(AddressType.class)
    .add(addressTypeExample);
  listAddressTypes(criteria.list());
}

Result:

  6, Customer

Example 8: Combining with Criteria

QBE can be combined with criteria query. In this example we add further restriction to the query object using criteria query.

Method:

@Test
public void testCombiningWithCriteria() throws Exception {
  Session session = (Session) entityManager.getDelegate();
  AddressType addressType = new AddressType();
  addressType.setDescription("Customer");
  Example addressTypeExample = Example.create(addressType);
  Criteria criteria = session
    .createCriteria(AddressType.class).add(addressTypeExample)
    .add(Restrictions.eq("type", 6));
  listAddressTypes(criteria.list());
}

Result:

  6, Customer

Example 9: Association

With criteria query we can filter both sides of an association, using two query objects.

Method:

@Test
public void testAssociation() throws Exception {
  Session session = (Session) entityManager.getDelegate();
  Address address = new Address();
  address.setCountryISO2Code("US");
  AddressType addressType = new AddressType();
  addressType.setType(6);
  Example addressExample = Example.create(address);
  Example addressTypeExample = Example.create(addressType);
  Criteria criteria = session.createCriteria(Address.class).add(addressExample)
    .createCriteria("addressType").add(addressTypeExample);
  // addressType is a property of Address
  listAddresses(criteria.list());
}

Result:

  84, US, BOSTON, null, 6, Customer

  83, US, ATLANTA, null, 6, Customer

  82, US, SAN FRANCISCO, null, 6, Customer

  75, US, CHICAGO, Los Angeles Way2, 6, Customer

EclipseLink

EclipseLink QBE uses QueryByExamplePolicy, ReadObjectQuery and JpaHelper:

QueryByExamplePolicy qbePolicy =newQueryByExamplePolicy();
qbePolicy.excludeDefaultPrimitiveValues();
Address address =newAddress();
address.setCity("CHICAGO");
ReadObjectQuery roq =newReadObjectQuery(address, qbePolicy);
Query query =JpaHelper.createQuery(roq, entityManager);



OpenJPA

OpenJPA uses OpenJPAQueryBuilder:

CriteriaQuery<Address> cq = openJPAQueryBuilder.createQuery(Address.class);
Address address =newAddress();
address.setCity("CHICAGO");
cq.where(openJPAQueryBuilder.qbe(cq.from(Address.class), address);


References

Hibernate:

·  Srinivas Guruzu and Gary Mak: Hibernate Recipes: A Problem-Solution Approach (Apress)

·  http://docs.jboss.org/hibernate/core/3.3/reference/en/html/querycriteria.html#querycriteria-examples

·  http://www.java2s.com/Code/Java/Hibernate/CriteriaQBEQueryByExampleCriteria.htm

·  http://www.dzone.com/snippets/hibernate-query-example

·  http://gal-levinsky.blogspot.de/2012/01/qbe-pattern.html

Hibernate associations:

·  http://stackoverflow.com/questions/9309884/query-by-example-on-associations

·  http://stackoverflow.com/questions/8236596/hibernate-query-by-example-equivalent-of-association-criteria-query

JPA:

·  http://stackoverflow.com/questions/2880209/jpa-findbyexample

EclipseLink:

·  http://www.coderanch.com/t/486528/ORM/databases/findByExample-JPA-book

OpenJPA:

·  http://www.ibm.com/developerworks/java/library/j-typesafejpa/#N10C18

 

 

 

 

Top