Result transformers

One of very popular pitfalls of database object mappers is the number of executed queries before constructing final entity. In Hibernate with FetchMode enum we can control better the quantity of queries executed against the database. However, in some cases we can need some supplementary help in the form of result transformers.

In the first part of the article we'll discover the differences between two FetchModes - SELECT and JOIN. After that we'll explain the role of result transformers. At the end we'll put these two concepts together and show how they are working through some simple test cases.

FetchMode in Hibernate

FetchMode enumeration in Hibernate contains 5 entries. But two of them are marked as deprecated (EAGER, LAZY) and it's preferable to use DEFAULT, JOIN or SELECT. The first one doesn't need a lot of explanation. This mode uses the mode defined in mapping file.

In the other side, SELECT, will query related entities through separated select queries. Imagine that you have one entity class which contains some of one-to-many relationships with another entity. Now, if you use FetchMode.SELECT against it, Hibernate will execute following queries:
- the first one to get matching rows for parent entity
- number of queries corresponding to the number of returned rows in previous query will be executed further; for example for 10 queries returned previously, 10 supplementary queries will be made to get related elements

As you can see, if we get more important number of parent entities, SELECT mode can become a real overhead. It's why Hibernate allows to get parent and child entities in single query, by using JOIN fetch mode. As its name indicates, JOIN mode will construct SQL query containing SQL's JOIN clauses, as for example:

select this_.id as id1_4_2_, this_.name as name2_4_2_, this_.seo_desc as seo_desc3_4_2_, this_.seo_keywords as seo_keyw4_4_2_, this_.seo_title as seo_titl5_4_2_, products2_.category_id as category7_4_4_, products2_.id as id1_10_4_, products2_.id as id1_10_0_, products2_.category_id as category7_10_0_, products2_.name as name2_10_0_, products2_.price as price3_10_0_, products2_.provider_id as provider8_10_0_, products2_.meta_description as meta_des4_10_0_, products2_.meta_keywords as meta_key5_10_0_, products2_.meta_title as meta_tit6_10_0_, provider3_.id as id1_13_1_, provider3_.name as name2_13_1_ from category this_ left outer join product products2_ on this_.id=products2_.category_id left outer join provider provider3_ on products2_.provider_id=provider3_.id

Result transformers in Hibernate

However, FetchMode.JOIN also has one pitfall - it returns a Cartesian product for matching rows. It means that parent entity can appear more than once if this entity contains relations to multiple children rows. For example if each parent entity has 3 children rows, Hibernate will return 6 rows - each children with associated parent. To avoid this kind of verbose output, Hibernate provides result transformers.

Basic interface for result transformers is org.hibernate.transform.ResultTransformer. It defines two methods: transformTuple and transformList. The first one is used to transform rows returned by the query to the final object. The second one allows us to transform collections into appropriated results.

To create distinct results, we need to specify Criteria.DISTINCT_ROOT_ENTITY transformer. In fact, it's an instance of org.hibernate.transform.DistinctResultTransformer. which uses under-the-hood DistinctResultTransformer. Transformation method looks like and we can see how do they work by turning on debugging logs:

@Override
public List transformList(List list) {
  List result = new ArrayList( list.size() );
  Set distinct = new HashSet();
  for ( int i = 0; i < list.size(); i++ ) {
    Object entity = list.get( i );
    if ( distinct.add( new Identity( entity ) ) ) {
            result.add( entity );
    }
  }
  LOG.debugf( "Transformed: %s rows to: %s distinct results", list.size(), result.size() );
  return result;
}

After activating logs, we could observe an output similar to: (2 parents rows, 1st parent with 3 children rows, 2nd parent with 2)

DEBUG: org.hibernate.transform.DistinctResultTransformer - Transformed: 5 rows to: 2 distinct results

Example of result transformers in Hibernate

To see transformers in action, let's make some test cases. Test cases look like below and are commented with inline comments to better understanding:

/**
 * Test cases to show how {@lnik ResultTransformer} works. It requires an unwrapping of
 * {@link javax.persistence.EntityManager} to Hibernate's {@link Session}
 * object to be able to use transformers after.
 */
public class ResultTransformersTest extends AbstractJpaTester {

  @Test
  public void testTransformingOneToManyRelationship() {
    // Category => Products
    Session session = entityManager.unwrap(Session.class);

    Criteria criteria = session.createCriteria(Category.class);
    criteria.setFetchMode("products", FetchMode.JOIN);

    // FetchMode.JOIN will produce following query
    // select this_.id as id1_4_2_, this_.name as name2_4_2_, this_.seo_desc as seo_desc3_4_2_,
    // this_.seo_keywords as seo_keyw4_4_2_, this_.seo_title as seo_titl5_4_2_, products2_.category_id as category7_4_4_,
    // products2_.id as id1_10_4_, products2_.id as id1_10_0_, products2_.category_id as category7_10_0_,
    // products2_.name as name2_10_0_, products2_.price as price3_10_0_, products2_.provider_id as provider8_10_0_,
    // products2_.meta_description as meta_des4_10_0_, products2_.meta_keywords as meta_key5_10_0_,
    // products2_.meta_title as meta_tit6_10_0_, provider3_.id as id1_13_1_,
    // provider3_.name as name2_13_1_ from category this_
    // left outer join product products2_ on this_.id=products2_.category_id
    // left outer join provider provider3_ on products2_.provider_id=provider3_.id

    List<Category> categories = criteria.list();

    // It's supposed to find 5 results because of FetchMode.JOIN
    assertEquals("5 categories should be returned", 5, categories.size());

    // However, with the use of DISTINCT_ROOT_ENTITY result transformer, only 2 categories will be returned
    criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);

    categories = criteria.list();
    assertEquals("Two categories should be returned", 2, categories.size());

    // after defining Criteria.DISTINCT_ROOT_ENTITY, we should find following fragment in the logs:
    // DEBUG: org.hibernate.transform.DistinctResultTransformer - Transformed: 5 rows to: 2 distinct results
  }

  @Test
  public void testWithSelectFetchMode() {
    // Category => Products
    Session session = entityManager.unwrap(Session.class);

    Criteria criteria = session.createCriteria(Category.class);
    criteria.setFetchMode("products", FetchMode.SELECT);

    List<Category> categories = criteria.list();

    // FetchMode.SELECT will execute 3 queries : the first one to get all categories and two next queries to
    // get rows associated with two previously get categories. This is a fragment of MySQL logs proving that:
    // 40 Query     select this_.id as id1_4_0_, this_.name as name2_4_0_, this_.seo_desc as seo_desc3_4_0_,
    // this_.seo_keywords as seo_keyw4_4_0_, this_.seo_title as seo_titl5_4_0_ from category this_
    // 40 Query     select products0_.category_id as category7_4_0_, products0_.id as id1_10_0_,
    // products0_.id as id1_10_1_, products0_.category_id as category7_10_1_,
    // products0_.name as name2_10_1_, products0_.price as price3_10_1_,
    // products0_.provider_id as provider8_10_1_, products0_.meta_description as meta_des4_10_1_,
    // products0_.meta_keywords as meta_key5_10_1_, products0_.meta_title as meta_tit6_10_1_,
    // provider1_.id as id1_13_2_, provider1_.name as name2_13_2_ from product products0_
    // left outer join provider provider1_ on products0_.provider_id=provider1_.id where products0_.category_id=2
    // 40 Query     select products0_.category_id as category7_4_0_, products0_.id as id1_10_0_,
    // products0_.id as id1_10_1_, products0_.category_id as category7_10_1_,
    // products0_.name as name2_10_1_, products0_.price as price3_10_1_, products0_.provider_id as
    // provider8_10_1_, products0_.meta_description as meta_des4_10_1_, products0_.meta_keywords as meta_key5_10_1_,
    // products0_.meta_title as meta_tit6_10_1_, provider1_.id as id1_13_2_, provider1_.name as
    // name2_13_2_ from product products0_
    // left outer join provider provider1_ on products0_.provider_id=provider1_.id where products0_.category_id=1

    assertEquals("Two categories should be found directly with SELECT mode", 2, categories.size());
  }

}

In this article we can discover the way of transforming results according to access methods: select queries for children rows for every parent or join producing Cartesian product. We saw that the first one doesn't need supplementary transformation but in the other hand, it can generate a lot of work to database server. The second method sends only one request containing JOIN clauses, but it can return duplicated parent rows. To avoid this situation, we saw that the use of Criteria.DISTINCT_ROOT_ENTITY is a valid solution.


If you liked it, you should read:

📚 Newsletter Get new posts, recommended reading and other exclusive information every week. SPAM free - no 3rd party ads, only the information about waitingforcode!