I have an issue when two threads try to create an entity in my database. The first thread checks if the object exists, sees that it is not there, and then creates it. However, at the same time, the second thread also checks if the object exists and, not finding it, tries to create it as well.
I want to solve this issue using @Retryabl
e on my getOrCreateEntity
method because it seems to be the simplest solution (at least, I think so). This method is called from another method annotated with @Transactional
.
So, I have two questions:
- Do I need to add
@Transactional(propagation = Propagation.REQUIRES_NEW)
to my method with@Retryable
? - Do I need to catch the potential exception (
DataIntegrityViolationException
) and, in the catch block, retrieve the entity that has already been created?
Here is the code :
@Retryable(
retryFor = {DataIntegrityViolationException.class},
maxAttempts = 2,
backoff = @Backoff(delay = 100)
)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public T getOrCreateEntity(UUID entityUuid) {
T entity = findEntityByUuid(entityUuid);
if (entity != null) {
return entity;
}
try {
log.info("Creating entity with UUID {}", entityUuid);
T newEntity = createNewEntity(entityUuid);
return repository.save(newEntity);
} catch (DataIntegrityViolationException e) {
log.warn("Concurrent insert detected, returning existing entity");
return findEntityByUuid(entityUuid);
}
}
So, as I mentioned, getOrCreateEntity
is called by a method that already holds a transactional context.
Thanks in advance,
I have an issue when two threads try to create an entity in my database. The first thread checks if the object exists, sees that it is not there, and then creates it. However, at the same time, the second thread also checks if the object exists and, not finding it, tries to create it as well.
I want to solve this issue using @Retryabl
e on my getOrCreateEntity
method because it seems to be the simplest solution (at least, I think so). This method is called from another method annotated with @Transactional
.
So, I have two questions:
- Do I need to add
@Transactional(propagation = Propagation.REQUIRES_NEW)
to my method with@Retryable
? - Do I need to catch the potential exception (
DataIntegrityViolationException
) and, in the catch block, retrieve the entity that has already been created?
Here is the code :
@Retryable(
retryFor = {DataIntegrityViolationException.class},
maxAttempts = 2,
backoff = @Backoff(delay = 100)
)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public T getOrCreateEntity(UUID entityUuid) {
T entity = findEntityByUuid(entityUuid);
if (entity != null) {
return entity;
}
try {
log.info("Creating entity with UUID {}", entityUuid);
T newEntity = createNewEntity(entityUuid);
return repository.save(newEntity);
} catch (DataIntegrityViolationException e) {
log.warn("Concurrent insert detected, returning existing entity");
return findEntityByUuid(entityUuid);
}
}
So, as I mentioned, getOrCreateEntity
is called by a method that already holds a transactional context.
Thanks in advance,
Share Improve this question edited Feb 6 at 6:23 M. Deinum 125k22 gold badges230 silver badges246 bronze badges asked Feb 5 at 21:25 magnetiktankmagnetiktank 1252 silver badges10 bronze badges1 Answer
Reset to default 0If you do } catch (DataIntegrityViolationException e) {
, then you don't need that @Retryable
, since in the catch
block you do that really manually.
The Propagation.REQUIRES_NEW
depends on your business logic. If it really requires that entity has to be there independently of the current transaction, then yes, you would need a new one just for this operation. Otherwise it might be OK to have such a retrieval as part of existing transaction. Yes, it might fail in the end because entity is already there by another transaction, but that might be OK as well according to the whole business logic of this call chain.