Wednesday, March 11, 2009

Wicket Patterns and Pitfalls #1

This is the first article in a series of articles about common patterns and pitfalls when using Wicket (http://wicket.apache.org). Most of these patterns apply only when using Wicket in conjunction with Spring and Hibernate and some might be very specific, but others apply for more general cases.


Hibernate IDataProvider and LoadableDetachableModel



Environment: Wicket, Hibernate, Spring, (OpenSessionInView)

Wicket and the HttpSession
When Wicket processes a request to a page, the page including the hierarchy of components and models that make up the page is constructed. Then the page including all its components and models is put into the PageMap, which in turn is put into the Wicket session, which is put into the HttpSession (it is actually put into a SessionStore, but basically it's put into the HttpSession by default). As the HttpSession is a critical resource, care has to be taken of the size of the page. Especially the size of the models, that are used by the components, can be of critical size (imagine huge amounts of table data). For this case, Wicket provides a LoadableDetachableModel - a model that holds a reference to the model object in a transient field, which is set to null after the request, so the model object is not put into the session. When the model object is needed/attached again later, LoadableDetachableModel updates the reference to its model object.

The following sections show a way to implement LoadableDetachableModel in a Spring/Hibernate environment. Along with this, an implementation of IDataProvider is presented.

The Example
Lets take, for instance, a table that displays a list of Person objects. Here's the Person class as a Hibernate entity. DomainObject is an interface for all the domain classes, which provides a method getId(), which returns the Hibernate id of the entity.

@Entity
public class Person implements DomainObject {
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String lastName;
private String firstName;
...
}

The page that displays the persons uses a DataTable:

public PersonListPage() {
IDataProvider personDataProvider = ...
List<IColumn> columns = new ArrayList<IColumn>();
list.add(new PersonNameColumn());
... // more columns
add(new DataTable("table", columns.toArray(
new IColumn[0]), personDataProvider, 20));
}

DataTable requires an IDataProvider, which delivers the data. Lets first have a look at how to implement this in a reusable way.

A generic IDataProvider implementation
The following implementation of an IDataProvider assumes the existence of a generic DAO IGenericDao, which provides methods for retrieving lists of different classes of entities from the database (the implementation of this generic DAO is out of the scope of this article).

public class GenericDataProvider<T extends DomainObject>
implements IDataProvider {

@SpringBean
private IGenericDao dao;
private Class<T> entityClass;

public GenericDataProvider(Class<T> entityClass) {
this.entityClass = entityClass;
InjectorHolder.getInjector().inject(this);
}

public Iterator iterator(int first, int count) {
return dao.findAll(entityClass, first, count).iterator();
}

public IModel model(Object object) {
return new GenericLoadableDetachableModel<T>((T) object);
}

public int size() {
return dao.count(entityClass);
}

public void detach() {}
}

The type T of GenericDataProvider specifies the type of domain objects this data provider retrieves. In the example, this would be Person. GenericDataProvider gets access to the generic DAO by dependency injection (@SpringBean). By default, Spring Beans can only be injected into Wicket components (IDataProvider is not a component), but we can inject Spring Beans exactly the same way that Wicket uses for components by calling InjectorHolder.getInjector().inject(this) from the constructor.

There's not much more to say about this IDataProvider implementation, but that it uses a generic LoadableDetachableModel, which is described in the next section. This basic version of a generic LoadableDetachableModel works for all of the domain objects. A more elaborate version could provide ways to restrict the result set by applying additional criteria, but I'll leave this up to you.

A generic LoadableDetachableModel implementation
For the implementation of a Hibernate LoadableDetachableModel we again make use of the generic DAO as well as of the interface DomainObject mentioned above.

public class GenericLoadableDetachableModel<T extends
DomainObject> extends LoadableDetachableModel {

@SpringBean
private IGenericDao dao;
private Class<T> entityClass;
private Long entityId;

public GenericLoadableDetachableModel(T entity) {
super(entity);
entityId = entity.getId();
entityClass = (Class<T>) entity.getClass();
InjectorHolder.getInjector().inject(this);
}

public GenericLoadableDetachableModel(Class<T> entityClass,
Long entityId) {
super();
this.entityClass = entityClass;
this.entityId = entityId;
InjectorHolder.getInjector().inject(this);
}

protected T load() {
return dao.get(clazz, entityId);
}

}

There's nothing special about this code, but this is a LoadableDetachableModel which is applicable to any domain object. It uses the same way to inject Spring Beans as the GenericDataProvider above.

There's one thing, though, to be aware of when the first constructor is used. The entity passed as an argument might be an instance of a Hibernate proxy in some cases. For example, suppose you've retrieved the entity by calling session.load(id) before. In this case Hibernate really returns a proxy instead of the entity itself. When this proxy is passed to the constructor, entity.getClass() will return the proxy class instead of the entity class. But there's a way around this. Lets change the constructor to make it aware of Hibernate proxies:

public GenericLoadableDetachableModel(T entity) {
super(entity);
entityId = entity.getId();

if (entity instanceof HibernateProxy) {
HibernateProxy proxy = (HibernateProxy) entity;
clazz = (Class<T>) proxy.getHibernateLazyInitializer()
.getPersistentClass();
} else {
clazz = (Class<T>) entity.getClass();
}

InjectorHolder.getInjector().inject(this);
}

This code checks for a HibernateProxy and eventually determines the persistentClass, which is what we want. This way the GenericLoadableDetachableModel can even be used with Hibernate proxies.

OpenSessionInView and GenericLoadableDetachableModel
The nice thing about the GenericLoadableDetachableModel is that it is applicable to all of our domain objects in a generic way, and in general LoadableDetachableModels keep the session size small. But in addition, there's another nice side effect when using GenericLoadableDetachableModel in combination with the OpenSessionInView pattern.

To show this, suppose we use GenericLoadableDetachableModel without an OpenSessionInViewFilter/Interceptor. When a component that uses a GenericLoadableDetachableModel is attached along with its model, the load() method of the GenericLoadableDetachableModel is called, which calls the generic DAO. The DAO opens a Hibernate session, retrieves the entity from the database and then closes the session. After that, the entity is detached (in a Hibernate sense) because the session is closed and LazyInitializationExceptions might eventually be thrown when we try to navigate an uninitialized association. Additionally, if we did not use a LoadableDetachableModel (with or without OSIV), but a non-detachable model, the model object would also be detached (Hibernate detached) on postbacks, because the model was put into the session including its model object (the entity).

This changes when we use an OpenSessionInViewFilter, which opens a session at the beginning of a request and closes it at the end of a request (details about the OSIV pattern is out of the scope of this article). This in combination with a LoadableDetachableModel leads to model objects, which are actually never detached (in a Hibernate sense). On the one hand, this is a consequence of the fact that the session is kept open after the call to load(), so that the entity is not detached. On the other hand, this is a consequence of the fact, that model objects are detached in a Wicket sense by the LoadableDetachableModel after the request, so that they will not be put into the Wicket session. So on postbacks the model object is not pulled from the session, but it is retrieved freshly from the database instead, because model.getObject() calls load() on LoadableDetachableModel. So, as a client you will actually never see any detached model objects and, as a result, no more LazyInitializationExceptions.

Note: The OpenSessionInView pattern needs special attention regarding transactions. This will be topic of a subsequent article in this series.


That's it for now. The next article will be about a pitfall using the @SpringBean annotation, so stay tuned.

7 comments:

Nick Wiedenbrück said...

Comments are welcome!

andrew humphries said...

Look fwd to the @springbean blog, we're using that annotation, problem free thus far.

Andrew

Stefan Fußenegger said...

We've been using this approach a lot as well. Additionally, we wanted to make all our persistent objects non-serializable, to be sure that they won't go into the session. However, this isn't very handy when modifying objects through forms etc. We then tried to implement this load-detach-mechanism as part of the serialization itself. And it works awesomely well, even with regular Models.

It might be a good idea to explain it in detail in a blog post myself. Check techblog.molindo.at for updates if you're interested.

I'm also looking forward to your @SpringBean article as it is one of the few things that never caused problems so far :)

Cheers

Stefan Fußenegger said...

btw, I love the name of your blog :D

Taha Hafeez said...

@Nick: It assumes direct DAO usage without a service layer.

I too am using a similar approach but with a service layer. I find your approach more tempting but have read in a lot of articles that DAO and Service layers must be separated.

Taha

andrew humphries said...

yes, separation of responsibility is key

Anonymous said...

In a world of transparent persistence frameworks, JDO, Hibernate etc., I can't see why DAO's are at all relevent anymore? I think they are a hangover from the pre transparent persistence days that people should let go of to take advantage of reduced coding and maintenance efforts.
I think there is defintely a roll for Services and Repositories as detailed in the 'exposed domain model pattern' but DAOs have gone the way of the dinosaurs as far as any project I've worked on in the last 4 years.