UPM 1.4 Released

Version 1.4 of UPM has just been released.

The most important feature in this release is the support for multiple languages. It ships with English and French language bundles but I’d really appreciate help from anyone who can fix up the French bundle (I used Google to translate but I don’t think it worked out too well) or provide other language translations. Here’s the resource bundle that needs to be translated.

The full change log is…

  • Added support for internationalisation. English and French language bundles are included.
  • Added support for editing the account name
  • Double clicking or hitting enter on an account opens the account in read-only view mode. Edit it still available on the toolbar, menu bar and as a keyboard shortcut.

Problem with Hibernate many-to-many association

This post is really a record for both myself and anyone else who ever comes across the same problem.

The Problem: Given two entities with a many-to-many association between them, the join table isn’t being populated when an association is made.

The Problem Set: Two persistent entities, “Group” and “ActivityType” with a many-to-many association between them. A few snippets from the relevant files…

Group.hbm.xml

<set name="activityTypes" table="group_activity_type" cascade="save-update" lazy="true">
    <key column="group_id"/>
    <many-to-many column="activity_type_id" class="ActivityType"/>
</set>

ActivityType.hbm.xml

<set name="groups" table="group_activity_type" inverse="true" cascade="save-update" lazy="true">
    <key column="activity_type_id"/>
    <many-to-many column="group_id" class="Group"/>
</set>

Group.java

private Set activityTypes = new HashSet();

public Set getActivityTypes() {
	return activityTypes;
}

public void setActivityTypes(Set activityTypes) {
	this.activityTypes = activityTypes;
}

ActivityType.java

private Set groups = new HashSet();

public Set getGroups() {
	return groups;
}

public void setGroups(Set groups) {
	this.groups = groups;
}

Some test code

org.hibernate.classic.Session session = HibernateUtil.getSessionFactory().openSession();
session.setFlushMode(FlushMode.MANUAL);
ManagedSessionContext.bind(session);
session.beginTransaction();

Group group = new Group();
group.setName("Test Group");
ActivityType activityType = new ActivityType();
activityType.setName("Activity Type");
activityType.getGroups().add(group);
group.getActivityTypes().add(activityType);
activityTypeDAO.saveActivityType(activityType);
groupDAO.saveGroup(group);

ManagedSessionContext.unbind(HibernateUtil.getSessionFactory());
session.getTransaction().commit();
session.close();

The Solution: This took me ages to figure out but I eventually stumbled across the problem on this thread. The problem is that I’m not calling session.flush() before I commit the session. Here’s the last paragraph of test code with the new flush statement.

ManagedSessionContext.unbind(HibernateUtil.getSessionFactory());
session.flush();
session.getTransaction().commit();
session.close();

Using Managed Sessions in Hibernate to Ease Unit Testing

If you’ve ever tried to reuse a session in Hibernate you may have come across this exception…

org.hibernate.SessionException: Session is closed!
   at org.hibernate.impl.AbstractSessionImpl.errorIfClosed(AbstractSessionImpl.java:49)
   at org.hibernate.impl.SessionImpl.beginTransaction(SessionImpl.java:1319)
   at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
   at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
   at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
   at java.lang.reflect.Method.invoke(Unknown Source)
   at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:301)
   at $Proxy0.beginTransaction(Unknown Source)
   ....

The reason for this is that Hibernate is using “thread” managed sessions. With this type of session management Hibernate manages the session for you. When you first attempt to use a session Hibernate will create one and attach it to your local thread. When you commit the transaction in the session Hibernate will automatically close the session meaning it can’t be reused.

To get around this problem the best option is to use “managed” sessions. With managed sessions you’re in full control of creating, flushing, commiting, and closing sessions. Here’s how.

In your hibernate.cfg.xml change the property “current_session_context_class” to “managed”.

To create a session and start a transaction in that session do this…

   org.hibernate.classic.Session session = HibernateUtil.getSessionFactory().openSession();
   session.setFlushMode(FlushMode.MANUAL);
   ManagedSessionContext.bind(session);
   session.beginTransaction();

To commit a transaction in the session do this…

   ManagedSessionContext.unbind(HibernateUtil.getSessionFactory());
   session.flush();
   session.getTransaction().commit();
   session.close();

To use this code in unit tests I created this base unit test class that all my unit tests extend. Whenever I want to create a new session/transaction I call the method createNewSessionAndTransaction(). To commit the session’s transaction I call the method commitTransaction().

import org.hibernate.FlushMode;
import org.hibernate.Session;
import org.hibernate.context.ManagedSessionContext;
import util.HibernateUtil;
import junit.framework.TestCase;

/**
 * Abstract unit test with helper methods for managed session control
 */
public abstract class ManagedSessionUnitTest extends TestCase {

   /**
    * Create a new Session.
    *
    * For this method to work, the application managed session strategy has to
    * be enabled. This basically means that the life of a session is controlled
    * by you and and not by Hibernate.
    *
    * To enable the application managed session strategy set the property
    * hibernate.current_session_context_class to "managed".
    *
    * Within this method we create a new session and set the flush mode to
    * MANUAL. This ensures that we have full control over when the session is
    * flushed to the database.
    */
   protected org.hibernate.Session createNewSession() {
      org.hibernate.classic.Session session = HibernateUtil.getSessionFactory().openSession();
      session.setFlushMode(FlushMode.MANUAL);
      ManagedSessionContext.bind(session);
      return (org.hibernate.Session) session;
   }

   /**
    * Start a new Transaction in the given session
    * @param session The session to create the transaction in
    */
   protected void startNewTransaction(Session session) {
      session.beginTransaction();
   }

   /**
    * Shortcut method that creates a new session and begins a transaction in it
    * @return A new session with a transaction started
    */
   protected org.hibernate.Session createNewSessionAndTransaction() {
      Session session = createNewSession();
      startNewTransaction(session);
      return session;
   }

   /**
    * Commit the transaction within the given session. This method unbinds
    * the session from the session context (ManagedSessionContext), flushes
    * the session, commmits the session and then closes the session
    * @param session The session with the transaction to commit
    */
   protected void commitTransaction(Session session) {
      ManagedSessionContext.unbind(HibernateUtil.getSessionFactory());
      session.flush();
      session.getTransaction().commit();
      session.close();
   }

}