JPA/Hibernate Conversational States Caveats

This article follow on from JPA/Hibernate Global Conversation and JPA/Hibernate Tempoorary Conversations.

We discuss here about the commons issues encountered when using the conversational states design.

I Thread Safety

It is well known that the Entity Manager is not thread-safe by nature. Only the Entity Manager Factory is thread-safe. If more than one thread is using the same Entity Manager, you’ll run into big trouble. There is no point trying to synchronize the Entity Manager operations since it will be a arduous task and very error-prone.

Situations where many threads try to access the same Entity Manager are quite common if you use a JPA conversational state (same Entity Manager for one user unit of work).

If you have a rich client interface for your application with many AJAX interactions, concurrent AJAX requests from the client to the server are likely to occur. In this case, each new request will be served by a distinct HTTP thread (from a thread pool) on the server side and each of them will access the same Entity Manager in the current user session, thus the issue.

To solve such issues, many solutions exist:

  1. Queue your AJAX calls so only one AJAX request is being processed by the server at a time. The new AJAX request will be sent to the server only upon reception of the result of the previous one.
  2.  

  3. Display a waiting popup upon each AJAX request to the server and remove it once the response is received. This waiting popup will prevent the user to perform any other action in between.
  4.  

  5. Set up a token system with the Entity Manager being the token itself. In the code section responsible for retrieving the Entity Manager from the user HTTP Session, put a synchronized block to remove it completely from the session. Any subsequent request trying to access the conversational Entity Manager will get a null value if the current request has not finished processing and has not put the Entity Manager back into the session. In such case, re-direct the request to an error page
  6. 
    		// Try to get the Entity Manager from the HttpSession
    		HttpSession httpSession = ((HttpServletRequest) request).getSession();
    		synchronized(httpSession)
    		{
    			currentEmHolder = (EntityManagerHolder) httpSession.getAttribute(this.entityManagerFactoryName);
    			httpSession.setAttribute(this.entityManagerFactoryName,null);
    			
    			if(currentEmHolder == null)
    			{
    				//Redirect to error page
    			}
    		}
    

II Exception Handling

According to the litterature, any exception occuring in the Entity Manager should be considered unrecoverable be cause the latter is in an unknown state and should be closed down. Continuing working with a stale Entity Manager will lead to data corruption and inconsistencies.

In the context of JPA conversational state, the same Entity Manager is used for an entire unit of work. This design makes the conversational Entity Manager very sensitive to runtime exceptions. In case of exception the conversational Entity Manager should be closed down as soon as possible but all the managed entities will become “detached” (the evil detached state we wanted to avoid with conversational states).

If a new Entity Manager is created, the task of merging these entities fall to the applicative code. Needless to say that determining which entities need to be merged is a daunting task.

A smarter solution is to detect any SQL/JPA related exception, re-direct the request to an error page and re-initialize all bean references. In this case all “detached” entities will be de-referenced by the reinit process. The user will start a new conversation with a fresh new Entity Manager.

  1. For the global conversation design, the Servlet filter is the right place to set the exception detecting and fordwarding mecanism.
  2. 		try
    		{
    			chain.doFilter(request, response);
    		}
    		catch (PersistenceException jpaException)
    		{
    			// Re-direct to an error page
    			// Re-init all view beans to de-reference detached entities
    		}
    		catch (HibernateException hibException)
    		{
    			// Re-direct to an error page
    			// Re-init all view beans to de-reference detached entities
    		}		
    		catch (DataAccessException daException) // Spring wrapped exceptions
    		{
    			// Re-direct to an error page
    			// Re-init all view beans to de-reference detached entities
    		}
    
  3. For the temporary conversation design, since there is no central place to capture JPA exceptions, we should rely on Spring AOP to create an around aspect and advise all classes participating in temporary conversations.
  4. 	@Pointcut("execution(public * *(..)) && @annotation(test.springaop.TemporaryConversationBegin)")
    	public void temporaryConversationBegin()
    	{}
    
    	@Pointcut("execution(public * *(..)) && @annotation(test.springaop.TemporaryConversationEnd)")
    	public void temporaryConversationEnd()
    	{}
    
    	@Pointcut("execution(public * *(..)) && args(test.springaop.ConversationEnum,..)")
    	public void temporaryConversationUse()
    	{}
    
    	@Around("temporaryConversationBegin() || temporaryConversationEnd() || temporaryConversationUse()")
    	public Object catchJPAException(ProceedingJoinPoint jp)
    	{
    		Object retValue = null;
    		try
    		{
    			retValue = jp.proceed();
    		}
    		catch (PersistenceException jpaException)
    		{
    			// Re-direct to an error page
    			// Re-init all view beans to de-reference detached entities
    		}
    		catch (HibernateException hibException)
    		{
    			// Re-direct to an error page
    			// Re-init all view beans to de-reference detached entities
    		}		
    		catch (DataAccessException daException) // Spring wrapped exceptions
    		{
    			// Re-direct to an error page
    			// Re-init all view beans to de-reference detached entities
    		}
    		return retValue;
    	}
    

The exception handling is identical to the one for global conversation

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.