3

Consider the following situation: We receive a request from a web service which updates our entity. Sometimes we might get two requests at (almost) the same time. We had situations in which our entity looked completely wrong, because of concurrent updates. The idea is to lock the entity pessimistic so that whenever the first request comes it instantly locks the entity and the second request can't touch it (Optimistic locking is no alternative for us). I wrote an integration test to check this behaviour.

I got an integration test which looks like the following:

protected static TestRemoteFacade testFacade;

@BeforeClass
public static void setup() {
    testFacade = BeanLocator.lookupRemote(TestRemoteFacade.class, TestRemoteFacade.REMOTE_JNDI_NAME, TestRemoteFacade.NAMESPACE);
}

@Test
public void testPessimisticLock() throws Exception {
    testFacade.readPessimisticTwice();
}

which calls the bean

@Stateless
@Clustered
@SecurityDomain("myDomain")
@RolesAllowed({ Roles.ACCESS })
public class TestFacadeBean extends FacadeBean implements TestRemoteFacade {

    @EJB
    private FiolaProduktLocalFacade produkt;

    @Override
    public void readPessimisticTwice() {
        produkt.readPessimisticTwice();
    }
}

with produkt being a bean itself

@Stateless
@Clustered
@SecurityDomain("myDomain")
@RolesAllowed({ Roles.ACCESS })
public class ProduktFacadeBean implements ProduktLocalFacade {

    @Override
    public void readPessimisticTwice() {
        EntityManager entityManager = MyService.getCrudService().getEntityManager();
        System.out.println("Before first try.");
        entityManager.find(MyEntity.class, 1, LockModeType.PESSIMISTIC_WRITE);
        System.out.println("Before second try.");
        entityManager.find(MyEntity.class, 1, LockModeType.PESSIMISTIC_WRITE);
        System.out.println("After second try.");
    }
}

with

public class MyService {

    public static CrudServiceLocalFacade getCrudService() {
        return CrudServiceLookup.getCrudService();
    }

}

public final class CrudServiceLookup {

    private static CrudServiceLocalFacade crudService;

    private CrudServiceLookup(){
    }

    public static CrudServiceLocalFacade getCrudService() {
        if (crudService == null)
            crudService = BeanLocator.lookup(CrudServiceLocalFacade.class, CrudServiceLocalFacade.LOCAL_JNDI_NAME);
        return crudService;
    }

    public static void setCrudService(CrudServiceLocalFacade crudService) {
        CrudServiceLookup.crudService = crudService;
    }

}

@Stateless
@Local(CrudServiceLocalFacade.class)
@TransactionAttribute(TransactionAttributeType.MANDATORY)
@Interceptors(OracleDataBaseInterceptor.class)
public class CrudServiceFacadeBean implements CrudServiceLocalFacade {

    private EntityManager em;

    @Override
    @PersistenceContext(unitName = "persistence_unit")
    public void setEntityManager(EntityManager entityManager) {
        em = entityManager;
    }

    @Override
    public EntityManager getEntityManager() {
        return em;
    }

}

The problem that arises now is: If I start the integration test once with a breakpoint at System.out.println("Before second try."); and then start the integration test a second time, the latter one can still read MyEntity. Remarkable is that they were different instances (I made this observation on the instanceId in debug mode). This suggests that the entityManager didn't share his hibernate context.

I made the following observations:

  • Whenever I call a setter on entity and save it to the db, the lock is aquired. But this is not what I need. I need the lock without having modified the entity.
  • I tried the method entityManager.lock(entity, LockModeType.PESSIMISTIC_WRITE) as well, but the behaviour was the same.
  • I found Transaction settings in DBVisualizer. At the moment it is set to TRANSACTION_NONE. I tried all the others (TRANSACTION_READ_UNCOMMITTED, TRANSACTION_READ_COMMITTED, TRANSACTION_REPEATABLE_READ, TRANSACTION_SERIALIZABLE) as well, without any success.
  • Let the first thread read the entity, then the second thread read the same entity. Let the first tread modify the entity and then the second modify it. Then let both save the entity and whoever saves the entity last wins and no exceptions will be thrown.

How can I read an object pessimistic, that means: Whenever I load an entity from the db I want it to be locked immediately (even if there was no modification).

Chris311
  • 3,794
  • 9
  • 46
  • 80
  • I don't know how it applies in Java, Hibernate, JPA, but — if your software opens the cursor that fetches the row(s) (aka entity) with either CURSOR STABILITY or REPEATABLE READ isolation (SET ISOLATION statement) and FOR UPDATE mode (cursor attribute), then the row will be locked against update by others as it is fetched. You won't stop others reading the row until you make changes, but you can make sure it won't change behind your back. However, that's working at a relatively low level (I think ESQL/C; you think JDBC or ODBC). I don't know how it translates up the calling chain. – Jonathan Leffler Feb 06 '18 at 09:46
  • @JonathanLeffler: I changed the transaction level to TRANSACTION_READ_UNCOMMITED, TRANSACTION_READ_COMMITED, TRANSACTION_REPEATABLE_READ and TRANSACTION_SERIALIZABLE. All of them didn't work. I haven't gotten an exception at the second read (without modification). – Chris311 Feb 06 '18 at 14:04
  • @Chris311 you should post more complete code you're using to test this. Are the entities being read using two different entity managers? Otherwise whenever you try to load the same entity a second time the entity manager will just return it's locally managed instance, it doesn't need to go to the database anymore. – Gimby Feb 14 '18 at 08:25
  • The following may help https://stackoverflow.com/questions/32336481/table-exclusive-lock-with-jpa – Abderrazak BOUADMA Feb 14 '18 at 09:24
  • Lock will never prevent from reading but only from concurrent updates – Gab Feb 14 '18 at 16:17
  • 1
    EntityManager is not thread safe, having an EM with singleton scope really looks like a design smell (except if your app is mono-threaded) – Gab Feb 14 '18 at 16:19
  • @Gimby You said "the entity manager will just return it's locally managed instance". My observations revealed that this is not the truth. I had different entity instances in the debug while using the same entityManager instance (see my updated question above). – Chris311 Feb 14 '18 at 17:09
  • If your app is a web one, I hope for you that you have at least a different EM (e.g. a Session in hibernate words) instance per http request, ie. per thread. If you rely on the EJB container to inject you the EM, then it will handle it for you, I bet that the essence of your problem is a misunderstanding of those concepts. You maye have a look to https://stackoverflow.com/questions/22772980/struggling-to-understand-entitymanager-proper-use/22773758#22773758 – Gab Feb 15 '18 at 08:22
  • @Gab: Ok, i was wrong telling you that our entityManager is singleton. Please have a look at my updated question. – Chris311 Feb 15 '18 at 09:39
  • Well I don't get what you're trying to achieve, as I said above lock won't prevent from reading but only from concurrent updates. Update your entity after reading it and your second test run will raise an error (if the 1st is still running). – Gab Feb 15 '18 at 09:43
  • @Gab: I updated my question to show you our goal. Btw: Without any locking mechanism the following could occur: The first thread reads the entity, then the second thread reads the same entity. The first tread modifies the entity and then the second modifies it. Then both save the entity and whoever saves the entity last wins and no exceptions will be thrown. – Chris311 Feb 15 '18 at 10:04
  • Doesn't change anything. locking only prevent from concurrent update not concurrent loading. You just don't get what's an EntityManager. (see it as a partial in memory copy of the database state, 2 EM instance loading same entity=> 2 different in memory copy of the same row in database). The lock is in fact handled at the DATABASE level, not at the EM one and will only protect from UPDATES to the row) And the update will only occur when EM is flushed or closed (at the end of the transaction) – Gab Feb 15 '18 at 10:12
  • So if the `EntityManager` has nothing to do with this, why is it possible to read with a `LockTypeMode`? `public T find(Class entityClass, Object primaryKey, LockModeType lockMode);` with comment "Find by primary key and lock. Search for an entity of the specified class and primary key and lock it with respect to the specified lock type."? – Chris311 Feb 15 '18 at 12:58
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/165228/discussion-between-chris311-and-gab). – Chris311 Feb 15 '18 at 13:44

3 Answers3

1

Both ways you describe ie.

  • em.find(MyEntity.class, 1, LockModeType.PESSIMISTIC_WRITE)
  • em.lock(entity, LockModeType.PESSIMISTIC_WRITE)

hold a lock on the related row in database but only for the the entityManager lifespan, ie. for the time of the enclosing transaction, the lock will be so automatically released once you've reached the end of the transaction

@Transactional()
public void doSomething() {
  em.lock(entity, LockModeType.PESSIMISTIC_WRITE); // entity is locked
  // any other thread trying to update the entity until this method finishes will raise an error
}
...
object.doSomething();
object.doSomethingElse(); // lock is already released here
Gab
  • 7,869
  • 4
  • 37
  • 68
  • This is exactly the problem I am facing. I got the same entity manager instance and I am in the same transaction. I updated my question to make this more clear. – Chris311 Feb 14 '18 at 15:54
0

Lock fails only if another thread is already holding the lock. You can take two FOR UPDATE locks on single row in DB, so it's not JPA-specific thing.

asm0dey
  • 2,841
  • 2
  • 20
  • 33
0

Have you tried to set the isolation level in your application server?

To get a lock on a row no matter what you are trying to do afterwards (read/write), you need to set the isolation level to TRANSACTION_SERIALIZABLE.

Sad Ikar
  • 76
  • 2