Wednesday, 26 November 2008

Hibernate's Criteria API

Hibernate has a Criteria API which allows you to dynamic build up filter conditions and set various join options. It is perfectly suited for flexible searches on entities, however there are areas in falls down in and the documentation is scarce.
Criteria instances can be either created from the hibernate Session or a DetachedCriteria can be created for later execution or for passing into Spring's HibernateTemplate.

The first thing that is explicitly mentioned is that references to properties can only be one deep, properties on child entities must be mapped via createAlias / createCriteria call. For example, the following will give a "org.hibernate.QueryException: could not resolve property: child.property of: com.example.model.Parent".
DetachedCriteria criteria = DetachedCriteria.forClass(Parent.class,"parent");
criteria.add(Restrictions.eq("parent.child.property", "somevalue");

Instead the child entity must be explicitly join by calling the following:
DetachedCriteria criteria = DetachedCriteria.forClass(Parent.class,"parent");
criteria.createAlias("parent.child","thechild");
criteria.add(Restrictions.eq("thechild.property", "somevalue");

Creating a join is not nesecary, if just the identifier is being referenced:
DetachedCriteria criteria = DetachedCriteria.forClass(Parent.class,"parent");
criteria.add(Restrictions.eq("parent.child.id", 1);

Add an IN condition.
To add a "where field in subquery", two separate criteria are required. One for the main (root) entity, and the other to retrieve the values for the IN list.
The following retrives the orders for which it has an order item which the product has a stock level of ZERO. The result transformer is used to only retrieve one row per entity, instead of having multiple rows as a result of the join on a one-to-many relationship.
DetachedCriteria ids = DetachedCriteria.forClass(ProductStock.class, "stock");
ids.add(Restrictions.eq("stock.stockLevel", 0));
ids.setProjection(Property.forName("productId"));

DetachedCriteria criteria = DetachedCriteria.forClass(Order.class, "order");
criteria.createAlias("order.orderItems", "items", CriteriaSpecification.LEFT_JOIN);
criteria.add(Subqueries.propertyIn("items.productId", ids));
criteria.setResultTransformer(Criteria.ROOT_ENTITY);

Performing a WHERE EXISTS
An exists is a variation on the above query, sometimes useful when the join could be large, or when it simply results in a faster execution.
DetachedCriteria ids = DetachedCriteria.forClass(ProductStock.class, "stock");
ids.add(Restrictions.eqProperty("stock.productId", "items.productId"));
ids.add(Restrictions.eq("stock.stockLevel", 0));

DetachedCriteria criteria = DetachedCriteria.forClass(Order.class, "order");
criteria.createAlias("order.orderItems", "items", CriteriaSpecification.LEFT_JOIN);
criteria.add(Subqueries.exists(ids));

However you cannot do arbitrary joins between 2 unassociated tables. All associations have to be defined in the mapping files before a criteria can be joined (i.e. a createAlias), the IN and EXISTS are the only other alternative.
There are other limitations which I have had to circumvent by either doing extra set mappings or extending Hibernate's classes.

Wednesday, 19 November 2008

Hibernate's Lazy Loading Quirks

I have come across two problems which have caused me no end of grief which turned out to be pretty simple.

LazyInitializationException
When using the Spring OpenSessionInViewFilter, I refer to lazily initialised properties (mapped as many-to-one or one-to-one) in the JSP pages. However I was getting a scenario where if a user performed a certain action I would get the following error:
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
After a lot of debugging and sifting through hibernate's source I traced it to a bit of code I had used that someone had posted for having a paged interval collection for hibernate (as oracle doesn't support server side paging). The culprit line of code was:
hibernateTemplate.clear();

//which is defined in Spring's wrapper as
public void clear() throws DataAccessException {
executeWithNativeSession(new HibernateCallback() {
public Object doInHibernate(Session session) {
session.clear();
return null;
}
});
}
This causes all objects that had been loaded previously by that session to be DISCONNECTED from the session. Hence the slightly misleading message. The session is still open, its just longer associated with it.

many-to-one relationships not being lazy

I had a problem with relationships which I had defined as being lazy not being lazily loaded which I only discovered after I turned of the second level cache off. With the cache on, I never saw the requests it made internally to load an entire child-parent-etc hierarchy (which is one bonus of using a cache).
After spending what seemed like hours, sifting through the documentation on lazy loading, fiddling with settings, checking the bytecode libraries, I finally discovered the cause. To get around cases where the 'static' data that these entities were referring to were missing due to some muppet bodging the data, I had set attribute not-found="ignore". This prevents the JSP page blowing up when you try and access the property when all you really want is to display a blank field or maybe a message. However, in order for a relational property to be lazy loaded it must be set to not-found="exception" (which is the default). Its a trade off between dealing with the exceptions where you don't expect them and the performance hit.

Monday, 17 November 2008

EasyMock and Spring Autowiring

Spring Framework provides an easy way to unit test components in isolation and EasyMock provides a quick way to create mock objects with very little effort. Combining them together, Spring can be used to create the mock objects so that beans that use auto wiring to inject dependencies can be tested without having to modify the classes or create complex context configuration files.

Creating mock objects using spring is simple, here is a sample beans.xml:
<bean class="org.easymock.EasyMock" factory-method="createNiceMock" primary="true" id="someServiceMock">
   <constructor-arg value="com.example.service.SomeService" />
</bean>
This creates a mock object from the given interface. The primary is used to indicate that it should be used for autowiring which is useful when component scanning is used together with @Component tags. When you want to refer to the actual implementation you would use @Qualifier("someService").

If interfaces aren't used then the following beans.xml is used.
<bean class="org.easymock.classextension.EasyMock" factory-method="createNiceMock" primary="true" id="someDAO" >
   <constructor-arg value="com.example.dao.SomeDAO" />
   <property name="sessionFactory">
      <null />
   </property>
</bean>
Performing the test is relatively simple and with autowiring configuration is minimal. Below I have used the classextension static imports as it can be used for both types of mocks. However the standard expect/replay/etc cannot be used with classextension created mocks.
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import org.junit.Before;
import org.junit.Test;
import org.springframework.ui.ModelMap;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/controller-beans.xml" })
public class TestSampleController
{
   /** UNIT UNDER TEST */
   @Autowired
   private SampleController controller;
   /** mock object */
   @Autowired
   private SampleService service;

   /**
   * Called before each test.
   */
   @Before
   public void setUp() throws Exception
   {
      reset(service);
   }

   @Test
   public void testPage() throws Exception
   {
      //data
      List data = new LinkedList();

      //prepare
      expect(service.getData()).andReturn(data);
      replay(service);

      //perform
      ModelMap map = new ModelMap();
      String result = controller.page(map);

      //verify
      assertThat("result", result, equalTo("expected");
      assertThat((Map<String,Object>) map, Matchers.hasKey("someobj"));
      assertThat("someobj", map.get("someobj"), notNullValue());
   }
}
Finally there are cases where you need the mock object ready to give a response during load up, such as in an InitializingBean. In those cases the following util class can be adapted:
public class MockUtil
{
   public static <T> T createNiceMock(Class<T> toMock, boolean replay)
   {
      IMocksControl control = EasyMock.createNiceControl();
      T mock = control.createMock(toMock);
      if (replay)
      {
         control.replay();
      }
      return mock;
   }
}
<bean id="dataServiceMock" class="com.example.test.MockUtil"  factory-method="createNiceMock" primary="true">
   <constructor-arg value="com.example.service.DataService" />
   <constructor-arg value="true"/>
</bean>