0

A method of a stateless EJB have to do a read-and-insert operation in transaction. First step is a complex check to test if the insert operation is allowed with the given parameters.

public boolean DoCheckThanCreateAndAddItemToGroup(Item newItem, Group group) {
     // STEP 1: checks if newItem can be created and added to group
     // This is a complex operation with database reading and some logic
     // using the current items of the group
     if (!DoesNewItemFitInGroup(newItem, group)) {
         return false;
     }

     // STEP 2: creates (persists) Item and adds it to Group
     em.persist(newItem);
     newItem.setGroup(group);
     return true;
}

It is obvious that the whole method should be locked to prevent concurrent execution in order not to add two or more items that don't fit together in the group. Default container managed JTA transaction handling does not provide this. The method can run parallel with the same group.

Currently, when running parallel, both clients can pass the check before any of them made the insert operation, so both clients insert its new item. But these items are not fit together in the group: after one is added, other should fail the check and mustn't be added.

Even if I can set transaction-isolation to SERIALIZABLE level (but I don't know how), it won't be a perfect solution because when the method is running concurrently with different groups, locking is needless and would slow down the system unnecessarily.

Other solution would be to put the method into a singleton EJB with @Lock(LockType.WRITE). But the problem is the same: for different Groups locking is unnecessary.

Something like this would solve the problem:

public boolean DoCheckThanCreateAndAddItemToGroup(Item newItem, Group group) {
    lock(group) {
        // STEP 1

        // STEP 2
    }
}

How to achieve this behaviour in a suitable way in the EJB environment? (Maybe in distributed environment, as well.)

Or any other idea, how to correctly handle this problem?

GregTom
  • 352
  • 4
  • 15
  • Do you have the possibility to add a unique constraint on DB level, so that the same newItem is not added to the same group? This is the usual solution. – V G Nov 19 '15 at 14:55
  • Yes, but this is not a usual case :-) The logic that can tell if an item can be added to a group is much more difficult. It cannot be made on the DB level. It depends on the content of the new item, on the content of the items that are in the group already and on some other parameters of the group. – GregTom Nov 19 '15 at 16:10
  • Is the `Group` entity (not the `group` property of the `newItem`) changed when you persist an `newItem`? I was thinking about adding a version property to the property, so that if Group changes, the second concurrent request will throw an exception (pessimistic lock). – V G Nov 19 '15 at 16:15
  • No, the group itself does not change. Only a new item is added. A very simple example (the actual case is much more difficult): the item is a number, and group has a maxValue. The sum of the numbers in the group mustn't exceed this maxValue. Before insertion the new sum must be checked, it it exceeds the maxValue of the group, adding new item is prohibited. – GregTom Nov 19 '15 at 16:22
  • This question and the solution gave me an idea: http://stackoverflow.com/questions/834843/java-ee-concurrency-locking?rq=1 If it is possible to synchronously call a singleton like MDB, this MDB should make the check-and-insert operation. So it can tell the callers if the addition of the item is allowed and it can make the operations in serialized way. Can it work? – GregTom Nov 19 '15 at 16:25
  • I have another idea, as I began earlier: add two fields to the Group entity: remainingCount and Version. Version will take care that the Group entity is not changed concurrently, and remainingCount will be an aggregate field. – V G Nov 19 '15 at 16:36
  • The key idea in the question you linked is to use two processes. In your case it would be to put the items in another table, and another scheduler would check which of new entries can be inserted in the main item table. Now to your adapted idea: no, I do not know anything like that (singleton has nothing to do with calling sequentially), except standard Java locking mechanisms, that PROBABLY (you should check that) are prohibited in a Java EE environment. Please check if Java locking (like `synchronize`) is allowed in Java EE: if yes, this would be the easiest solution. – V G Nov 19 '15 at 16:41
  • Did you try my idea with two new fileds in the `Group` entity? – V G Nov 20 '15 at 10:04
  • remainingCount in Group entity would be good only for the case that I constructed as a simplified example. So it was not a good example. Actual case is much more difficult. It is really a complex logic if an item can be added. And when a new item is added, the calculation must be made again for the other new item. – GregTom Nov 20 '15 at 11:39
  • I see, but the key idea is still good: just change the entity somehow (instead of remainingCount put a `lastAddedItem` field or just a random field) in order to make sure that it is not changed concurrently, this was you will get your concurrency safety. – V G Nov 20 '15 at 11:47
  • However pessimistic locking on Group entity really seems to be a good idea! I still have to check how to carry it out in JPA. – GregTom Nov 20 '15 at 11:51
  • Trying to sketch the solution: Steps to do in the method: 1. pessimistic locking of the group entity 2. STEP1: checking if item can be added to group, if not: return false 3. STEP2: persisting new item 4. STEP3: increment version value of group entity 5. COMMIT. After this I have to catch the exception raised by the pessimistic locking, and do the whole operation again (doing the check of the item again) One problem remained: the transaction boundary is the method itself (CBT). Should I use bean based transaction with manual handling to be able to catch the exception inside the method? – GregTom Nov 20 '15 at 12:03
  • My not-the-really-best solution was using java locking `synchronized` in a singleton bean (it is allowed to use when bean-based transaction is used). But locking the group entity _in the database_ really seems to be **the solution**. Especially because it must work in distributed deployment environment. My only remaining question is purely technical: how to carry it out with JPA inside a method of a stateless session bean with container-managed transactions? – GregTom Nov 20 '15 at 12:22
  • Andrei, thank you very-very much, the solution was only two additional lines in my code! – GregTom Nov 21 '15 at 10:14
  • You should use singleton ejb for achieving the needed concurrency. – donlys Dec 30 '16 at 02:08
  • @donlys: No, because I didn't need a global lock. I wanted to lock only the group, which is a parameter of the function. For another group, parallel execution is allowed. So the good solution is what I wrote as an answer. – GregTom Jan 08 '17 at 13:47

1 Answers1

0

Somehow I wanted to lock the method with the group object. The solution is based on Andrei's idea, but I did not have to modify the Group entity. I just had to add these lines at the start of the stateless session bean method:

Group lockGroup = em.find(Group.class, group.getId());
em.lock(lockGroup, LockModeType.PESSIMISTIC_WRITE);

Pessimistic lock on the entity object ensures that if a client already locked the same entity object, the second client waits at the em.lock line until the first client commits the whole transaction.

Thank you, Andrei!

Community
  • 1
  • 1
GregTom
  • 352
  • 4
  • 15