Tuesday, March 17, 2009

Wicket Patterns and Pitfalls #2

This is the second episode in a series of articles (see
here for the first article) 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. This article illustrates how Wicket's SpringBean-Annotation can lead to subtle problems.

Be Aware of Spring Bean Proxies


This article shows a Hibernate example where Wicket's SpringBean-Dependency-Injection can have subtle side effects, but it can also have side effects in many other scenarios.

Environment: Wicket, Spring
Example Environment: Wicket, Spring, Hibernate, OpenSessionInView


The Example
When using Wicket in a Spring container the most common way to use Spring's dependency injection is to annotate fields to be injected with the @SpringBean annotation. As a simple example let's take a Hibernate entity Person ...

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

... and a page that displays the details of a person:

public class PersonPage extends WebPage {
public PersonPage(Long personId) {
Person person = getPerson(personId);
add(new Label("firstName",
new PropertyModel(person, "firstName")));
add(new Label("lastName",
new PropertyModel(person, "lastName")));
}
...
}

For the moment let's don't mind using an inappropriate Model for the person entity (we could use a LoadableDetachableModel instead). Now assume that we have configured an OpenSessionInViewFilter that cares about opening a Hibernate session at the beginning of a request and closing it after rendering the response on our behalf. In this case, one way to implement getPerson(personId) would be to use the recommended way of calling SessionFactoryUtils.getSession(sessionFactory, allowCreate) to get a Hibernate session and call get(personId) on it. We'll let Wicket inject a SessionFactory by @SpringBean for that purpose:

@SpringBean
private SessionFactory sessionFactory;

private Person getPerson(Long personId) {
return (Person) SessionFactoryUtils
.getSession(sessionFactory, true)
.get(Person.class, personId);
}

The Problem
As we know that the OpenSessionInViewFilter opened a session on our behalf, we can be sure that the call to SessionFactoryUtils.getSession() returns that session. But this is not the case and we won't recognize it immediately! The code works well at first look and the person will be displayed. So what's wrong with this code then?

In order to open a Hibernate session the OpenSessionInViewFilter pulls the session factory out of the Spring application context, then opens a session on it and basically puts it into a Map of registered open sessions which is held in a ThreadLocal. The session factory instance is used as the key for that map. The other way round, SessionFactoryUtils.getSession(sessionFactory, allowCreate) looks up a session in the same map with the sessionFactory provided to getSession(sessionFactory, allowCreate) as the key. So in this map the session, which was opened by the OpenSessionInView before, should obviously be found, because the OSIF has put it there under the same key, see the following figure.



What breaks this in the case above is that the sessionFactory object, that was injected into the page, is really a proxy of the sessionFactory.* The OSIF on the other hand pulled the sessionFactory directly from the application context, which is not a proxy. So consequently we tried to lookup the session opened by the OSIF in the map with an instance of a proxy of the sessionFactory as the key instead of the real sessionFactory. As these objects are not equal, the session opened by the OSIF is not found, see figure.


* Wicket injects proxies of Spring Beans instead of the beans themselves, because these would get serialized as parts of Wicket components and along with these beans the whole application context might get serialized.


The Consequences
Primarily this article is just aimed at showing that one should be aware of Wicket's @SpringBean injection. But just out of curiosity, let's have a deeper look at the consequences in this particular Hibernate scenario.

At first look the code just works fine. But what really happens in this example is that a new Hibernate session is created, because we called getSession(sessionFactory, allowCreate) with true as second argument. We could just don't care that a new session is opened, but actually this session is not closed after the request. The OSIF only closes sessions it opened itself, so our manually opened session just stays open (forever). We could turn on Hibernate statistics to see that each request adds one more open session, which is something you really wouldn't want.

What happens if we call SessionFactoryUtils.getSession(sessionFactory, false) (false as second argument) telling SessionFactoryUtils not to open a new session if none exists already? In this case an exception is thrown, telling us that no open session is available, which is quite healthier than silently opening more and more new sessions, which aren't closed afterwards. And after reading this article you even won't have to scratch your head why the session opened by the OSIF is not found and an exception is thrown.

The Solution
Actually, I think the example above is not quite common, as in most cases one would probably not call SessionFactoryUtils from a Wicket component directly. Instead I'd suggest to call a DAO method or another Spring Bean to load the person. These objects could be injected using @SpringBean. The fact, that these are really proxies of the Spring Beans as well does not matter in this case. It's just important, that these will have access to the session factory directly instead of through a proxy.

To close this article, I'd like to mention a kind of funny side effect of the example above. The nice (not really) thing about it is, that one wouldn't even get LazyInitializationExceptions accessing the person on post backs, because the session that loaded the person is not closed, so the person instance is not detached. And that even without using a LoadableDetachableModel ;-).


Stay tuned. More posts are coming soon. Leave a comment!

4 comments:

Anonymous said...

gone thru your article but didnt absorb much may be cos i am not that tech savvy. can you be bit simpler in further posts

Nick Wiedenbrück said...

Please don't mind, this is a very special example.

The thing is, to be aware that @SpringBean-injected beans are proxies instead of the real beans.

victori said...

You should not be using PropertyModels on the entity beans directly but on models instead. You will be serializing more than you have to if you pass the bean instead of the model that fetches the bean.

I learned this lesson the hard way on a high traffic wicket site.

Nick Wiedenbrück said...

@victori: Thanks for the hint. As mentioned, one should really depend on a LoadableDetachable Model for the person. But as this is not the topic of this post (but of a future article) I chose to go the simple way.