0

My Spring component gets a request from a client, asks a web-service about some data and saves received objects to a database. I identify all objects and save only new ones.

The issue occurs when the client makes two or more same requests in the same time (or due to even different user requests I receive same objects from web-service).

To describe the issue with persistence here some details. For each client request my component starts execution in a separate thread, I get a new entityManager, begin a transaction, receive a data from web-service, then I identify objects and persist new ones using given entityManager in a current transaction.

If in separate transactions I receive the same objects from web-service and if they are new ones that are not yet in database I am not able to identify them in not-commited transactions and so they are persisted in all transactions. Then all duplicate objects will be commited and saved to database.

What could be good solutions in this case? Is there any way to identify new objects properly even in different transactions? Or what approaches can be applied?

May be Spring provides some approaches to manage transactions or entityManagers so that it can help with this issue...

Note. Of course I can use database instruments to avoid saving duplicate objects but in this case it is not a very good solution.

Kirill Ch
  • 5,496
  • 4
  • 44
  • 65

2 Answers2

0
Justinas Jakavonis
  • 8,220
  • 10
  • 69
  • 114
  • Please see my question more carefully. I don't need the instruments on database level to prevent saving of duplicate objects. One of the reason is that in described situation with database constraints during commiting two difference transactions with some number of duplicate objects then we will receive an error from database and one of the transaction will be rolled back - and this is not the best solution. – Kirill Ch Jun 16 '17 at 08:52
  • 1
    I have noticed that so I have mentioned synchronization. Why do you avoid rollbacks? What do you imagine as better solution? – Justinas Jakavonis Jun 16 '17 at 09:51
  • Rollbacks is the solution that we came to, better then nothing. However we don't want to waste user's time to potencially error requests only because we don't know how to manage duplicate objects in different transaction. I don't think it is a unique situation in developing systems so the better solution might be. – Kirill Ch Jun 16 '17 at 10:28
  • 1
    User will wait either way. Processing same concurrent request in REST: https://stackoverflow.com/questions/31777442/processing-same-concurrent-request-in-rest – Justinas Jakavonis Jun 16 '17 at 11:07
0

So the solution in my case is the following:

  1. Make transactions pretty small and commit every object separately.
  2. Make unique constraints in database to prevent duplicating of objects. This point will not help us a lot but needed for point 3.
  3. Every commit() method we insert in try-catch block. If we try to commit duplicate object in parallel transactions then we will receive an exception and in catch block we can check the database, select the object that is already there and work with it futher.

The example:

boolean reidentifyNeed = false;

try {

    DofinService.getEntityManagerThreadLocal().getTransaction().begin();

    DofinService.getEntityManagerThreadLocal().persist(entity);

    try {

        DofinService.getEntityManagerThreadLocal().getTransaction().commit();

        //if commit is successfull
        entityIdInDB = (long) entity.getId();

        DofinService.getEntityManagerThreadLocal().clear();

    } catch (Exception ex) {

        logger.error("Error committing " + entity.getClass().getSimpleName() + " in DB. Possibly duplicate object. Will try to re-identify object. Error: " + ex.toString());

        reidentifyNeed = true;

    }

    if(reidentifyNeed){
        //need clear entityManager, because if duplicated object was persisted then during *select* an object flush() method will be executed and it will thrown ConstrainViolationException 
        DofinService.getEntityManagerThreadLocal().clear();

        CheckSimilarObject checkSimilarObject = new CheckSimilarObject();

        long objectId = checkSimilarObject.checkObject(dofinObject);

        logger.warn("Re-identifying was done. EntityId = " + objectId);

        entityIdInDB = objectId;
    }

} catch (Exception ex) {
    logger.error("Error persisting and commiting object: " + ex.toString());
}
Kirill Ch
  • 5,496
  • 4
  • 44
  • 65