4 Replies Latest reply on Oct 25, 2006 7:55 PM by Alex Levine

    EntityManager.lock() issues

    Manuel Teira Paz Newbie

      Hello. I'm trying to build an scenario with the following behaviour:

      Two kind of messages can be feeded into a JMS queue:
      -NEW. Messages that triggers the creation of a new entity (ejb3.test.AEntity).
      -INCR. Messages that triggers the modification of a persistent property
      of a given entity(ejb3.test.AEntity).

      ejb3.test.AEntity is a simple versioned entity with just a persistent property (other than version and id) called counter. It exposes a bussiness method to increment that counter as:

      public void incr() {
      logger.info("incr called for " + this);
      setCounter( getCounter() + 1);
      }

      I have a MDB to handle the messages. NEW messages are handled in the following fashion:
      Creates a new AEntity instance.
      Persist it in the injected EntityManager.
      Flushes the Entity Manager.
      Send 10 INCR messages.

      INCR messages are handled as:
      Finds the AEntity that the message is referenced to (using a property into the message).
      Locks the AEntity.
      Calls the AEntity bussiness method: incr().


      This scenario does not work using Oracle as DataSource: The AEntity is created, but the MDBs handling the 'INCR' messages are not able to find that Entity using:

      AEntity a = manager.find(AEntity.class, aId);

      I suppose that the problem is that the transaction for the MDB handling the 'NEW' message has not finished. But, shouldn't the EntityManager.flush() commit the changes to the DataBase? I tried to change the MDB to BMT and it worked in that way.

      On the other hand, this worked under Hypersonic, supposedly because it only implements UNCOMMITED_READ as isolation transaction level. Also, I got warning with Hypersonic trying to use:

      manager.lock(a, LockModeType.READ);

      in my attempts to lock the AEntity instance before calling its business method:

      AEntity a = manager.find(AEntity.class, aId);
      if ( a != null ) {
      manager.lock(a, LockModeType.READ);
      a.incr();
      } else {
      logger.warn("No entity with id " + aId);
      }

      Unfortunately, using Oracle 9i as DataSource, the INCR handlers are never able to see the Entity (manager.find returns null). With Hypersonic, the entity becomes available after flushing the EntityManager (READ_UNCOMMITED).

      Furthermore, when using an UserTransaction with BMT, the Entity is visible by the 'INCR' MDB handlers but they seems to see the entity in a concurrent fashion, hence, the final result of the counter field is not valid.

      Also, trying to use manager.lock(a, LockModeType.WRITE) with a previously created Entity, using CMT, finishes in a NullPointerException:

      2006-07-28 13:37:03,801 ERROR [ejb3.test.MsgHandler] Errors found:
      java.lang.NullPointerException at org.hibernate.persister.entity.AbstractEntityPersister.lock(AbstractEntityPersister.java:1282)
      at org.hibernate.event.def.AbstractLockUpgradeEventListener.upgradeLock(AbstractLockUpgradeEventListener.java:88)
      at org.hibernate.event.def.DefaultLockEventListener.onLock(DefaultLockEventListener.java:64)
      at org.hibernate.impl.SessionImpl.fireLock(SessionImpl.java:586)
      at org.hibernate.impl.SessionImpl.lock(SessionImpl.java:578)
      at org.hibernate.ejb.AbstractEntityManagerImpl.lock(AbstractEntityManagerImpl.java:337)
      at org.jboss.ejb3.entity.TransactionScopedEntityManager.lock(TransactionScopedEntityManager.java:101)


      After reading the EJB3 Persistence specification, I think that using EntityMager.lock() should avoid the concurrent MDBs trying to get the Entity to access it, and the transactions implied should be serialized to avoid inconsistencies (physically locking the DB row related with the entity). I'm a newbie in this ejb3 world (even in j2ee world) so, I would like to know if I have make any (or a lot of ) mistake in my assumptions or code.

      Best regards.

        • 1. Re: EntityManager.lock() issues
          Emmanuel Bernard Master

          flush() does not commit a transaction, it just execute statements. That's the reason for you not seeing the entity.
          Yes HSQLDB does not implements the required isolation level.
          About the NPE, this is bad, I'll fix that
          http://opensource.atlassian.com/projects/hibernate/browse/HHH-1958

          • 2. Re: EntityManager.lock() issues
            Manuel Teira Paz Newbie

            Thanks for the answer and excuses for the delay into replaying again.

            So, when is the transaction supposed to be commited? On exiting the MDB onMessage() embedding the entity creation?

            What I want to get using the EntityManager.lock() method is to avoid other MDBs handling the INCR messages to modify concurrently the entities. Also, I want the modifications made by any of the MDB to be seen by the other ones. Something similar to the behaviour we got using 'SELECT FOR UPDATE' SQL clauses. Isn't that what EntityManager.lock() is used for?

            Any other suggestion to get this pattern working on EJB3? I want a pool of MDBs handling requests for activity. These requests, when targetting the same entity, must get exclusive access to that entity.

            Regards.

            • 3. Re: EntityManager.lock() issues
              Manuel Teira Paz Newbie

              In different words, what I'm looking for is the way to apply a pessimistic locking approach here.
              I was googling and found some not clear enough signs that pessimistic locking is not supported in JPA entities. Is this true?

              The approach I was trying to use:

              AEntity a = manager.find(AEntity.class, aId);
              manager.lock(a, LockModeType.READ);

              I think now it's not the way to go, because it's not atomic: Different competing threads could adquire an entity representing that database row before one of then reaches the lock.

              So, is there any way to force the EntityManager.find to use a 'select for update' clause to make its job?

              Regards.

              • 4. Re: EntityManager.lock() issues
                Alex Levine Newbie

                 


                The approach I was trying to use:

                AEntity a = manager.find(AEntity.class, aId);
                manager.lock(a, LockModeType.READ);

                I think now it's not the way to go, because it's not atomic: Different competing threads could adquire an entity representing that database row before one of then reaches the lock.


                I had a simlar problem and came up with this solution: Since multiple threads can get then entity before locking it, once you get the lock, refresh the entitiy, then you will have the latest copy and you can then update it without overwriting previous updates:
                AEntity a = manager.find(AEntity.class, aId);
                manager.lock(a, LockModeType.READ);
                manager.refresh(a);
                // now do stuff