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 LoadableDetachableModel
s 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 LazyInitializationException
s 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 LazyInitializationException
s.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:
Comments are welcome!
Look fwd to the @springbean blog, we're using that annotation, problem free thus far.
Andrew
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
btw, I love the name of your blog :D
@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
yes, separation of responsibility is key
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.
Post a Comment