Monday, April 27, 2009

Wicket Patterns and Pitfalls #5

This is the fifth 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 these might be very specific, but others are more general. The last article was about the pitfalls related to Wickets HTTP session usage. This time a pattern for dataproviders for lists of associated entities is shown.

Data Providers for Indexed Lists



Environment: Wicket

Example Environment: Wicket, Spring, Hibernate


IDataProviders in Wicket are used to provide data to data views, such as DataTables for instance. The data provider interface has three methods:

public interface IDataProvider extends IDetachable {

Iterator iterator(int first, int count);

int size();

IModel model(Object object);

}

The iterator(first, count) method returns an iterator, that iterates over the actual data. The range is is used to get a subset of that data, e.g. for paging. The size() method returns the total number of possible result values. The objects returned by the iterator are then passed to the model() method to wrap the entity into a model, generally this should be a LoadableDetachableModel.

There are many ways to implement a data provider depending on the situation. This article sets the focus on the special case of implementing a data provider for a list of objects that are associated by another object, for example the orders of a person.

The Example
As an example, let's take an entity Person that references some Orders:


@Entity
public class Person {

@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private String lastName;
private String firstName;
private String username;

@OneToMany(mappedBy = "person")
private List<Order> orders = new ArrayList<Order>();
...
}


... and a panel that displays the orders of a person:


public class PersonOrdersPanel extends Panel {

public PersonOrdersPanel(String id,
final IModel personModel) {
super(id, personModel);

Person person = (Person) personModel.getObject();
IDataProvider dataprovider =
new PersonOrdersDataProvider(person.getId());

IColumn[] columns = ...

add(new DataTable("table", columns, dataprovider, 10));
}
}

The panel is provided a person model and it uses a Wicket DataTable to display the details of the person's orders. DataTable requires an IDataProvider that delivers the orders. Let's have a look at different implementations of such a data provider, that retrieves the orders of a person.

A DAO data provider
The first thing, that comes to my mind when thinking about how to implement the order data provider, is using a DAO that fetches the person's orders from the database.

public class PersonOrdersDataProvider implements
IDataProvider {

@SpringBean
private IOrderDao orderDao;

private final Long personId;

public PersonOrdersDataProvider(Long personId) {
this.personId = personId;
InjectorHolder.getInjector().inject(this);
}

public Iterator iterator(int first, int count) {
return orderDao.findByPerson(personId,
first, count).iterator();
}

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

public int size() {
return orderDao
.findByPersonCount(personId);
}

public void detach() {
}
}

This data provider implementation uses a @SpringBean annotated OrderDao, which is injected by the constructor. The constructor takes the person's database ID as an argument. The iterator() method uses the order DAO to find the orders of the person with this ID. size() calls the DAO to get the total number of the person's orders. The model() method returns a GenericLoadableDetachableModel for an order, which loads the order by its ID (see the previous article for a description of GenericLoadableDetachableModel).

While this implementation works well, it's quite an effort, to implement it. We need an order DAO and we have to implement the findByPerson(first, count) and findByPersonCount(personId) methods. We have to write some injection code, and we need a suitable model for the orders (this is no effort, if you have a GenericLoadableDetachableModel, though). Another issue is, that it only works for entities, not for value objects (e.g. Hibernate @Embeddables), as these don't have an ID, so it's hard to implement a LoadableDetachableModel for these. Also, this implementation results in a separate database query for the orders, although the person already might be referencing them.

A model based data provider
Another implementation could depend only on the person model, or in general, on the model for the entity that associates the list of objects we need a data provider for. Let's take a look:

public class PersonOrdersDataProvider implements
IDataProvider {

private final IModel personModel;

public PersonOrdersDataProvider(
IModel personModel) {
this.personModel = personModel;
}

public Iterator iterator(int first, int count) {
return getPerson().getOrders().subList(
first, first + count).iterator();
}

public IModel model(Object object) {
int index = getPerson().getOrders()
.indexOf(object);
return new PropertyModel(personModel,
"orders." + index);
}

public int size() {
return getPerson().getOrders().size();
}

public void detach() {
// personModel gets detached somewhere else
}

// helper
private Person getPerson() {
Person person = (Person) personModel
.getObject();
return person;
}
}

This shows a data provider that does not use a DAO, but instead completely relies on the person model. This is passed as an argument to the constructor and assigned to an instance variable. The iterator() method pulls the person out of this model and creates a sublist of the list of orders referenced by the person. Also, size() just returns the size of the orders list of the person. The model() method returns a PropertyModel based on the person model, indexing into the orders property (PropertyModel expressions support indexing). The index is the index of the order in the list of orders.

With this implementation there's no need for a DAO or a separate database query. There is no special model needed for the orders and it also works for value objects.

Alternative model based data provider
There is a variant of the previous data provider implementation, that I even like a little bit more, as it is kind of more explicit. The only difference is in the iterator() and model() methods:

public class PersonOrdersDataProvider implements
IDataProvider {

...

public Iterator iterator(int first, int count) {
return new RangeIterator(first, count);
}

public IModel model(Object object) {
Integer index = (Integer) object;
return new PropertyModel(personModel,
"orders." + index);
}

...

}

This time iterator() really just returns an iterator that iterates over the indexes of orders in the list of orders of the person. It returns a RangeIterator that iterates over the integers in the range [first, first + count] (this is not difficult to implement). In consequence this index is passed to the model method, which again returns a PropertyModel indexing into the orders property of the person model.

Conclusion
I think, this article shows a way to implement a data provider that's a bit more elegant than the conventional way. An additional benefit is, that it can also be used for lists of value objects. Also, it allows for a generic implementation, that can be reused for different entities.

A drawback is, that it performs worse than the DAO data provider when it comes to huge datasets, because it loads all elements of the collection, which might not be necessary if you use an OR-Mapper that supports lazy loading. The DAO data provider can restrict the number of results that are fetched from the database, if you want to implement some kind of paging. On the other hand if you really want to display all elements of the collections, you could fetch the person including the orders within a single query with the model based dataprovider.




5 comments:

Andy Gibson said...

The only problem with this is when your person has 2,000+ orders, all of which are loaded just to display the first 10, which aren't even considered because the user loaded the page to look at something else.

I moved away from this method of loading data towards the first method since it is constrained and you don't have to worry about how much data ends up being attached to the model.

Nick Wiedenbrueck said...

Good point, Andy.

This method works fine, when the person's orders collections is already loaded. I forgot to mention, that this method might perform worse, when it comes to uge data sets, which are lazily loaded by a OR-Mapper like Hibernate. On the other side if one really wants to display all orders (perhaps for example the person's adresses is a better example) one could load the person including the orders with a single query instead of a query for the person and a separate query for the orders.

In the end it depends on the use case. I should probably mention that in the article.

Thanks for the comment.

Ryan said...

Nick,

Great blog! I've been using Wicket with Spring+Hibernate for the last year and absolutely love it (wicket). These patterns are very common and your blog provides a great resource for getting up to speed. Thanks!

Anonymous said...

In example, you wrote:
personModel gets detached somewhere else
Where model is detached in this situation?

Nick Wiedenbrueck said...

Suppose, that the person model is attached to a component. For example, the component that instantiates the data provider. But one could also detach the person model in the dataprovider.