Josef Bodnar
Josef Bodnar

Reputation: 21

Wicket LoadableDetachableModel - unnecessary detaching during ajax requests

I noticed that Wicket's LoadableDetachableModel (LDM) detaches on each request by design (via RequestCycle.processRequestAndDetach()). This may cause performance issues in particular cases and I would like to keep the cached data while still using benefits of LDM.

Say you have a detail page for an entity with id. The page is divided into tabs (AjaxTabbedPanel). If you open the page, entity is read from DB for given id (into the LDM). If you click on second tab, the model is already detached and will be reloaded again. This is not necessary in my case, since I do not want to refresh data on each request.

I want the LDM on the page so that page history will work (only the entity ID will be serialized and data reloaded on demand).

So how to fix the unnecessary reloads?

I came up with two solutions:

  1. wrap the LDM into static Model so it does not get detached automatically and detach (=reload) it manually only when needed.
  2. implement a child implementation of LDM which would keep the transient data and reload only if null (like when revived a serialized page). This geenric model can be then reused.

I would think there would be a solution already implmented for this in wicket, but I failed to find any. Do you know any other (standard) way to achieve this?

Many thanks for answers.

PS: sample implementation for 2)

    private class MyEntityModel extends LoadableDetachableModel<MyEntity> {
    private String entityId;
    private transient MyEntity modelObject;

    public MyEntityModel(String entityId) {
        this.entityId = entityId;
    }

    //call this for explicit reload
    public void forceDetach() {
        modelObject = null;
        detach();
    }

    @Override
    protected MyEntity load() {
        if (modelObject != null) {
            return modelObject;
        }
        MyEntity entity = getData(entityId);
        modelObject = message;
        return message;
    }
}

UPDATE: It seems my original question is not clear enough. Sorry for that. Current behaviour:

  1. A wicket page is loaded with entityID passed as parameter
  2. A new instance of LDM is created, entityID passed to it
  3. When (during response rendering) LDM.getObject() is called, the entity is loaded from the database
  4. After response is rendered, LDM.detach() is called
  5. A page with ajax controls is loaded
  6. When user clicks on any control (like tab switch), a new request is generated to the server
  7. The server swaps the tabs, attempts to fill the new tab with data, so it calls LDM.getObject(), which has been detached in 4., and it loads the data again
  8. Response is send to the client and LDM.detach() is called

Desired behaviour: Ad 4 - when LDM.detach() is called, the transient model object is preserved Ad 7 - in case the transient model object is still in memory, use it. Load the data otherwise

Above I described two ways, how to achieve this behaviour. Is there any standard or better way?

Upvotes: 1

Views: 1028

Answers (3)

Volksman
Volksman

Reputation: 2068

Most people would be using an ORM, either an implementation of JDO or JPA and the most useful use case is "open persistence manager/session in view". Trust me that it's the most useful - I tried for a long time to avoid it by trying all other incarnations but they just end in tears and a lot of extra, buggy code.

"Open persistence manager/session in view" means that when your app receives a HTTP request it gets a new persistence manager/session (which has its own L1 cache) and then your code uses it to load objects, as required, to supply content to Wicket components via IModel and then the Wicket components detach the model references they have when their onDetach method is called during the http request cycle. At the end of the request the persistence manager/session is closed. Sure, we lose the L1 cache, but most of its goodies are still cached in the L2 cache.

You do have an L2 cache don't you?

Any good ORM will have a Level 2 (L2) cache which will cache all the objects (limited by memory capacity of course) loaded by persistence managers during previous HTTP requests.

Given these are already in memory it is very quick to copy these into the L1 cache of a new persistence manager created to service a new HTTP request. i.e. the ORM doesn't have to reload the objects from scratch from the database if they are in the L2 cache.

Anyone running an ORM without an L2 cache configured needs to go back to day one of ORM School, lesson 1.0.1.

When a persistent object is loaded it is "attached" to the persistence manager that it was loaded by. It is essential not to use such an object with a new persistence manager unless it has first been detached from the original one and then reattached to the new one.

This is where Wicket's onDetach saves you - and (answering the question finally ;) ) this is why detach needs to happen at the end of EVERY HTTP request: that's where you do all your ORM detaching and it has to happen at the end of each request otherwise you'll be cross pollinating your objects across different persistent managers over subsequent HTTP requests - and that always turns out badly!

Upvotes: 0

Martijn Dashorst
Martijn Dashorst

Reputation: 3692

There are several options available to achieve what you want:

  1. long running hibernate sessions
  2. detach the entity from the session or entity manager
  3. use CDI and store the entity in a conversation scoped object

I haven't used 1 and 2, but 2 is probably the closest alternative to what you currently use (and quicker to implement). 3 is probably the closest alternative to what you actually want to achieve, but requires you to add a whole new infrastructure to your application–depending on your container easier (JavaEE7) or harder (a plain old servlet container like Tomcat/Jetty).

Upvotes: 2

Don Roby
Don Roby

Reputation: 41135

I noticed that Wicket's LoadableDetachableModel (LDM) detaches on each request by design (via RequestCycle.processRequestAndDetach()). This may cause performance issues in particular cases and I would like to keep the cached data while still using benefits of LDM.

Do you know that it is in fact causing you performance issues?

It sounds like you are trying to optimize performance by building in a caching mechanism. If in fact you actually have a problem and need this, there is likely a better solution than building your own caching.

If you're using hibernate, you probably want to look at configuring hibernate to use a Second Level Cache.

If you're not using hibernate, whatever mechanism you are using for persistence may have a similar cache. Or if you in fact need to add your own, you should likely base it on an existing caching mechanism such as ehcache.

Upvotes: 1

Related Questions