Announcement Announcement Module
Collapse
No announcement yet.
entityManager not suspended when @Transactioanl( REQUIRES_NEW ) Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • entityManager not suspended when @Transactioanl( REQUIRES_NEW )

    I'm looking for a way to have Spring bind a new entityManager in the EntityManagerHolder for methods marked @Transactional( propagation = Propagation.REQUIRES_NEW ) suspending the existing one if it exists. I'm also surprised that this isn't the default behavior. Let me explain why:

    First my general setup: I'm using the OpenEntityManagerInViewFilter to make an entityManager available throughout a request. Controller methods that save changes are marked @Transactional( REQUIRED ). These methods cause the entityManager opened by the filter to by associated to the transaction, so that when the transaction commits, the entityManager is flushed, but stays around for the rest of the request. Everything is as expected.

    Now comes the problem. I have an exception handler setup to log unexpected errors that occur in controller methods to the database. Here's it is:

    Code:
    @Transactional( propagation = Propagation.REQUIRES_NEW )
    public void write( Throwable t, String externalId ) {
        SystemLog sl = new SystemLog();
        sl.setExternalId( externalId );
        sl.setSummary( t.getMessage() );
        sl.setDetail( ExceptionUtils.getFullStackTrace( t ) );
        sl.setTimestamp( new Date() );
        systemLogDao.persist( sl );
    }
    The problem is that the @Transactional( REQUIRES_NEW ) doesn't cause a new entityManager to be created, but rather uses the entityManager opened by the filter just like @Transactional( REQUIRED ). Here's a use case when this fails to work properly:

    1 - the controller method (REQUIRED) persists an invalid entity
    Code:
    @Transactional
    public String save() {		
        Company co = companyModel.getCompany();
        co.setCompanyName( " " ); // this value causes entity validation to fail on commit
        companyDao.merge( co );
        return "saved";
    }
    2 - the commit causes a validation exception to be thrown, the transaction is rolled back.
    3 - the exception handler is called because of this (REQUIRES_NEW), and persists an entity about the error.
    4 - the exception handler's transaction commits, but since it's using the same entityManager, which still contains the invalid entity, it also causes a validation exception to be thrown, and is also rolled back!

    It seems I really should be getting a new entityManager when propagation = REQUIRES_NEW! Is there some way to tell Spring to behave like this or some other way (preferably not a hack, I got that covered already!) to accomplish what I want?

  • #2
    No you shouldn't be getting e new entitymanager with the propagation level is REQUIRES_NEW, this would introduce a whole lot of issues...

    If you need a new/fresh not related EntityManager create one yourself, which is what I would always do in an exceptionhandler, because in case of an exception the state of the entitymanager isn't reliable anymore , so you always want a fresh one. (This is at least the case for hibernate).

    Comment


    • #3
      Originally posted by Marten Deinum View Post
      No you shouldn't be getting e new entitymanager with the propagation level is REQUIRES_NEW, this would introduce a whole lot of issues...

      If you need a new/fresh not related EntityManager create one yourself, which is what I would always do in an exceptionhandler, because in case of an exception the state of the entitymanager isn't reliable anymore , so you always want a fresh one. (This is at least the case for hibernate).
      So, when Ive got a exception, trying to persist an entity, I cant use the same entitymanger anymore? Why? This can be the cause of this problem: http://forum.springsource.org/showthread.php?t=107148

      Many Thanks!

      Comment


      • #4
        Hijacking a thread isn't considered nice (nor is your post helpful!).

        Comment


        • #5
          @Marten - Thank you for your reply. There's something I don't understand though. From my point of view, not getting a new entityManager introduces issues:

          REQUIRES_NEW states the intention to get a clean slate. The callee doesn't want to inherit the caller's transaction, nor does it want to inherit its entityManager. The actual behavior however is sometimes like this, but not always, it depends on the caller's context:
          1. If the caller's context is transactional: the existing transaction and entityManager are suspended, new ones are created for the callee
          2. If the caller's context is not transactional: a new transaction is created, but the entityManager of the caller is inhertied by the callee.
          In effect, the callee receives a new entityManager only if the caller is transactional. This is not a clean slate! The use case of the exceptionHandler shows how this can be a problem.

          Your suggestion of creating a new entityManager to address the problem is what I'm looking for. I'd expect Spring to do this by default however, but I will explore doing this myself if all else fails. But if this was the default behavior, what is it you're thinking of that would be problematic? Can you describe a sample use case that would break?

          Comment


          • #6
            Spring follows the behavior as specified by the EJB spec (this also uses the current entitymanager, changing this behavior for spring would break this contract!).

            The new entitymanager wouldn't see changed data in the suspended transaction if it would be a new fresh entitymanager, which can be quite problematic.

            Also REQUIRES_NEW is about the transaction NOT about the underlying data access technology... REQUIRES_NEW also doesn't get you a fresh database connection if you would use JDBC...

            Comment


            • #7
              Thank you once again. I don't doubt you're right that this would break the contract. I'm not trying to gets the specs revised, just trying to understand and find a clean way to get what I want. So here's what I have in mind:

              My DAOs all inherit from a base DAO class that injects the persistence context like this:

              Code:
              @Repository
              public abstract class GenericDao<T, ID extends Serializable> {
              
              	@PersistenceContext
              	private EntityManager entityManager;
              
                      public void persist( T entity ) {
              		entityManager.persist( entity );
              	}
              }
              So to setup a "clean slate" context and continue to use my DAOs, I have to change the context's entityManager (and JDBC connection to be complete, thanks for pointing that out). I don't think I can simply create a new entityManager in the callee: this won't change the fact that the caller's entityManager will already have been bound to the transaction, causing it to roll back.

              So I'm left with one other option: push the requirement on the caller to cleanup their context before the call. This works:

              Code:
              @Component
              public class CleanSlateContextHelper {
              	
              	public static interface Delegate {
              		void doWork();
              	}	
              	
                      // sets up a new transactional context to absorb the caller's context, but not commit it.
              	@Transactional( propagation = Propagation.REQUIRES_NEW, readOnly = true )
              	public void doWork( Delegate delegate ) {
              		delegate.doWork();
              	}
              }
              ... and the caller

              Code:
              @Resource
              private SystemLogService systemLogService;
              
              @Resource
              private CleanSlateContextHelper cleanSlateContextHelper;
              
              private void log( final Throwable throwable, final String errorId ) {
              	cleanSlateContextHelper.doWork( new CleanSlateContextHelper.WorkDelegate() {			
              		@Override
              		public void doWork() {
                                      // this is the write method from my first post, marked propagation=REQUIRES_NEW
              			systemLogService.write( throwable, errorId );				
              		}
              	} );	
              }
              So there's a read-only transaction setup to absorb the caller's context, which gets suspended when calling the desired method, finally setting up a clean context. This works but it's rather hacky, and imposes a constraint on the caller which I'd like to avoid. Do you know a better way?

              Comment


              • #8
                I like this pattern better. It removes the burden from the caller:

                On the SystemLogService:

                Code:
                @Resource
                private SystemLogServiceHelper systemLogServiceHelper;
                
                // readonly transaction to absorb caller's entityManager/jdbc connection
                @Transactional( propagation = Propagation.REQUIRES_NEW, readOnly = true )
                public void write( Throwable t, String externalId ) {		
                	systemLogServiceHelper.writeImpl( t, externalId );
                }
                and in the same package a little helper component:
                Code:
                @Component
                class SystemLogServiceHelper {
                
                	final static Logger log = Logger.getLogger( SystemLogService.class );
                	@Resource
                	private SystemLogDao systemLogDao;
                	
                	@Transactional( propagation = Propagation.REQUIRES_NEW )
                	public void writeImpl( Throwable t, String externalId ) {		
                		SystemLog sl = new SystemLog();
                		sl.setExternalId( externalId );
                		sl.setSummary( t.getMessage() );
                		sl.setDetail( ExceptionUtils.getFullStackTrace( t ) );
                		sl.setTimestamp( new Date() );
                		systemLogDao.persist( sl );		
                	}
                }
                Maybe I'll find a more generic way of implementing this pattern eventually, but for now it works, and it's isolated in the implementation.

                Thanks for your guidance Marten.

                Comment

                Working...
                X