At JBoss World last week in Berlin I presented some advanced Hibernate patterns. Well, I planned to talk longer about the Swing and Hibernate demo app I wrote a few days before but it turned out that although 100% of the audience was using Hibernate, only one poor soul had to work with Hibernate in a two-tier desktop application scenario. So I spent more time on the other patterns and only showed the Swing demo for about 5 minutes at the end of the presentation.
I thought more people would be interested in this, it comes up frequently on forums and the Wiki. Developers who have to access a database from a Swing application with no middle tier always have the same questions: How do I use the Session?
and How do I get an updated object into a different view?
The answers to these questions are very much interconnected. I don't claim that what I came up with in the demo app is the best possible strategy, although it works nicely as a proof of concept. Also, I'm a terrible Swing programmer, only did two quite small projects about 4 years ago. I actually used Hibernate even back then but if I look at my code now it wasn't really that good. This is now much better.
First, here is a screenshot of the app:
On the left is a frame with a tree and a modal dialog that pops up if you create or edit one of the items in the tree. On the right hand side is a frame with a panel, in that panel is a label and a table. These views are assigned to controllers, which also handle the model for each view. I prefer the hierarchical MVC pattern for this, so you end up with MVC triads:
Now, all of this is independent of the persistence context (think Hibernate Session). I wrote a very small HMVC framework that supports arbitrary scoping of the persistence context (it's always bound to the current thread, this is also an orthogonal concern). The scope of the persistence context depends on what I want for a particular MVC subtree. Do I want to work with a single identity scope in that subtree? Do I want one in-memory representation of a particualr database row? If yes, then I use the same persistence context for all controllers in that subtree. Have a look at the controller superclasses in the download package.
If I don't want the same identity scope, I can pass detached instances between different persistence contexts and reattach them when necessary. In the demo app, I could have easily implemented the list view of items on the right side with a different persistence context and pass the selected category into the ItemListController.
I also need to pass other things between controllers, all kinds of events. So for example, if a user selects a category on the left side, I fire a CategorySelectedEvent. Whatever controller has registered a listener for that event, gets the event. The propagation is done by the controllers, and I can decide if an event is only propagated downwards to subcontrollers, or globally to every controller in the hierarchy tree (an application could have several HMVC trees). I can put a payload into an event as well, for example, to transport a detached object into a different persistence context:
Transaction handling is also an orthogonal concern. Data access might happen behind the scenes, for example, when a user expands the tree nodes. Or, a button is clicked and an action executes. You can't really wrap transaction demarcation (BEGIN and COMMIT) around on-demand data retrieval, when a Swing data model (a TreeModel here) accesses its bound entity objects. So this is a scenario where auto-commit mode for reading makes sense. The persistence context still provides repeatable read for all entity instances. On the other hand, all data access that is done explicitly when an action executes (button was clicked) is wrapped in a regular transaction block. The demo framework also has the code for this.
You can download the demo app here[1] - I will need to find some time to document this better (there is some Javadoc in the source though).
> The way I read it, it is mainly based around the session.merge to move
> between objects between sessions, where each action gets its own
> session,
> right?
No, actions are actually completely independent of the persistence
context. The persistence context is scoped to controllers, or a
subtree of controllers (remember they form a tree). You can either
a) share the persistence context between controllers, if you need the
same database identity scope
b) copy detached objects between controllers that have different
persistence contexts, usually with merge()
> One thing that I wasn't able to figure out is how you handle the
> loading of
> categories' children in the tree. I can't find any place where this
> appear.
This is happening in EntityTreeModel, which is an adapter from the
Category objects to a model that is backing the UI tree component.
The UI tree component calls isLeaf() and getChild() on its TreeModel,
which my EntityTreeModel translates. So I simply hit the
category.getChildren() collection there.
> As far as I can see, what it going on is that the session in the
> CatalogController is kept alive throughout the lifetime of the
> CategoryDialog, and the categories are being loaded in batches by
> lazy load.
Yes, the batch load is an optimization. Although you are right with
"CatalogController is kept alive", this is the fundamental pattern of
the _hierarchical_ MVC. It's a tree, the EditNewDialogController
(which controls the CategoryDialog view) is a child of
CatalogController. And I decided, through the use of constructors,
that they share the same persistence context and database identity
scope.
> IF my understanding is correct, this mean that there is a live
> connection
> held there for a long period of time. If I wanted to avoid that,
> and still
> keep the lazy-load semantics, what would you suggest doing?
No, the Session is not connected to the database at all times. It is
connected when:
a) some UI component wants to do on-demand loading of some lazy
collection or association through some model binding (tree for
example) - a very short connection in auto-commit mode is used to
execute that SQL
b) some DataAccessAction executes, which means that the database
connection is obtained in executeInController() (this is a double
dispatch pattern, btw) for the duration of the explicit transaction.
c) some controller code uses getPersistenceContext() outside of a
DataAccessAction, a very short connection in auto-commit mode is used
to execute that SQL
> Also, in DataAccessAction, it looks like you switched the pre/post
> transactions javadocs.
True :)
The problems we had with this type of use of the session can be summarized as
1.pp might suffer from " bloated session cache " phenomenon..with each your application getting slower and slower with as the session cache grows bigger and bigger as hibernate has to go through all the objects in the session cache for every query ( with huge session this might slow you down ,If the session gets big, flushing it will take more and more time, even if there are no changes). Essentially long running session advantage can become a disadvantage ( due to hibernates transactional write-behind ) if session in allowed to grow big. A workaround that might be useful in some cases might be to use query.setReadOnly(true) , session.setReadOnly() for queries/ entities which load up readonly objects into the session.
2. Manual "eviction" to keep session consistent, You might have use sql queries for various reasons in various places in the app to perform updates/deletes in such case you have do manual evicts in order to keep session from being corrupted.
3. Transactional write-behind might come in your way ( depending on how you manage long running session ) if you plan to access data from "other sessions" .
User might experience GUI freezing when hibernate is doing its lazyloading magic.
Classic lazy loading n+1 query problems ( see links below)
4.Stale data problems .if the data is updated by some background job or thread not using the main session ..your main session will not be up to date with the changes.
The point I'm trying to make is that the Session memory management is conceptually OK. It's just often not used correctly. (It's all manual, the persistence context never shrinks.)
Why is your persistence context too big? There might be stuff in Session that you didn't want. Maybe the items are fetched eagerly. Or you have stuff loaded that you are not using anymore.
The solutions for that are correct fetch plans and queries, and manual management of the Session cache with eviction of single instances (can be cascaded) and clearing of the whole cache.
And that manual management should be fairly easy, because you need to clear and evict stuff from the Session on very well defined points: mostly when an action is complete. I have the postTransaction() callback.
When the Session grows to big from UI components pulling stuff from the model (lazy loading), this is more difficult. But as I said earlier, you'd have the issue of displaying that much data anyway, or your fetch plan is completely wrong.
One thing that would help is dynamic fetch plans and fetch strategies for on-demand loading (per session, not per query or criteria) in Hibernate, this is on the road map for a next version (not in the 3.2 series though).
I am trying to build a single user swing app using native hibernate, so I thank you for your example. From what I gather, if property connection.autocommit is set to true in hibernate.cfg.xml is, then you should be able to access the db without first beginning a transaction. It looks like you do this in the demo. However, the following code gives me a .
The DAO code looks like:
public List<Course> getCoursesByPeriod(Integer period){ return findByCriteria(Restrictions.eq("period", period)); } protected List<T> findByCriteria(Criterion... criterion) { Criteria crit = getSession().createCriteria(getPersistentClass()) .setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); for (Criterion c : criterion) { crit.add(c); } return crit.list();}
(Basically straight out of caveatemptor).
I have
in hibernate.cfg.xml, also (I noticed you have it set to managed; not sure if that makes any difference)Someone else has apparently had a similar problem. Any help would be really appreciated.
I have a similar problem.
is throwing
I have only modified the persistence unit name in the persistence.xml and the same name in the HibernateUtil.java file
Best regards Jone Lura
I noticed that your cancel button doesn't actually cancel. :)
If you edit a category name, hit cancel, and cause a redraw of the tree (e.g. by collapsing and expanding the parent node of that category), you will see the changed category name.
Hi,
as far as I can see and had understood it , you ensure that you load what you need, by intercepting or modifying the actions or event listeners. E.g. DataAccessAction.java.
That approach may be a good solution for new projects, which were written from the scratch, but in an existing app, it would be nice to do the intercepting with hibernate. Like the proposed patch http://www.360works.com/articles/detail/56/. So it would be nice if hibernate would offer such an Interceptor by default.
Cause, even you would be able (which is normally not possible, cause of the vast number) to refactor all event listener and actions, you have to go very deep inside, cause the event may be only a trigger to retrieve data outside.
Also you may not be able to split up the data for each dialogue, e.g. if a series of the dialogues are spanning one huge Object tree. so you have to load all the required data during the opening of the first dialogue or it would become a Sisyphus work to define exactly the data which would be needed for each action.
This is especially hard, cause swing apps normally expect that everything is already in memory. I new apps you can dismiss this assumption.
Thanks.
Greetings Michael
PS: I'm not a native speaker, so please excuse any mistakes.
Thanks . It help me have see about Hibernate . Free Download