Data Providers for Indexed Lists
Environment: Wicket
Example Environment: Wicket, Spring, Hibernate
IDataProvider
s in Wicket are used to provide data to data views, such as DataTable
s 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 @Embeddable
s), 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.