1

I want to store unique entities with their hashes. An entity consists of a generated id and an hash value which must be unique. I do this with this code:

@Stateless
@LocalBean
public class srvcEntity {

    @PersistenceContext(unitName = "mine")
    private EntityManager em;

    private Long save(byte[hash]) {
        List<Entity> list = Entity.findByHash(hash, em); // using TypedQuery.getResultList()
        if (list != null && list.size() > 0) {
            entityId = list.get(0).getId();
        } else {
            Entity e = Entity.save(hash, em); // using em.persist()
            if (e != null) {
                entityId = e.getId();
            }
        }
        return entityId;
    }
}

This works very well till I get a lot of entities to store simultaneously. Then a race condition can lead to the sitation that at the time of read no entity with the given hash can be found but at save time I get a ConstraintViolationException for a duplicate hash.

Because my code exists in a bean with container based transactions, I simply can not read again after the save has failed.

How can I resolve this race condition?

Ben
  • 366
  • 1
  • 13

2 Answers2

0

Straight-forward way is to catch the exception and then perform the read again.

You can also use a striped lock that will prevent the race condition. This is useful in situations where you don't want to perform some operation possibly twice, but it also involves locking (although the Guava Striped class allows you to configure the amount of locks and therefore throughput quite nicely).

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • As I already stated a read after the failed write is not possible, because the transaction is already marked to be rolled back. But yes that would have been the easiest way, which I already tried and failed ;-) – Ben Mar 28 '17 at 09:12
  • You could design around that, except it might be hard with your odd `Entity.save(hash, em)` style code. Is there a reason why you've created such an odd wrapper around the `EntityManager`? In any case, the second option prevents even attempting to insert a duplicate value. – Kayaman Mar 28 '17 at 09:24
  • No special reason except to "retro-fit" with the rest of the entities. How would you do it without the wrapper for em.persist(). – Ben Mar 28 '17 at 09:28
  • Depends on your existing code. For example with a service or dao class and a `REQUIRES_NEW` transaction, you could let the transaction rollback and it wouldn't affect your outer transaction. Of course that's not very compatible with your current architecture. – Kayaman Mar 28 '17 at 09:31
  • So basically I need another bean wrapping around the save to get an extra transaction which is allowed to fail but is not killing the "parent" transaction? – Ben Mar 28 '17 at 09:41
  • That would be one possibility yes. Your architecture seems to make the assumption that "entities know how to save themselves". However it fails in this case, where the saving isn't so straightforward. This approach works only in simple situations, so generally speaking you've (or someone) made a poor architectural decision. – Kayaman Mar 28 '17 at 09:46
  • That's debatable, but if you have a service class such as `SRVCEntityService`, you can have all kinds of different operations and invent new ones as you go along. Gives you a lot more flexibility than just trusting that save always means save, and nothing else. – Kayaman Mar 28 '17 at 10:16
0

EJB methods are synchronized. If you are only running one instance of your application (it isn't clusterered!) then

@Singleton

may resolve this issue as there should only ever be one entry point for this code which would remove the race condition possibility.

David
  • 589
  • 6
  • 21
  • Unfortunately I have running several application servers to receive those entities. – Ben Mar 28 '17 at 09:35