Reputation: 29290
I have the following code, that is run in multiple threads:
@Component
public class CreateInstrumentTask {
@Autowired
InstrumentRepository repository; // Spring-data JPA repo interface
@Transactional
public void createInstrument(String instrumentId) {
synchronized(instrumentId.intern()) {
QInstrument $instrument = QInstrument.instrument;
Instrument instrument = repository.findOne($instrument.instrumentId.eq(instrumentId));
if (instrument == null) {
log.info("Thread {} creating instrument {}", Thread.currentThread().getName(), message.getInstrumentId());
instrument = createInstrument(instrumentId); // impl. ommitted
repository.saveAndFlush(instrument);
}
}
}
This logs out:
INFO [] Thread taskExecutor-1 creating instrument ABC
INFO [] Thread taskExecutor-17 creating instrument ABC
org.springframework.integration.MessageHandlingException:
org.springframework.dao.DataIntegrityViolationException: Duplicate entry 'ABC' for key 'instrumentId';
I expected that, given the code is synchronized
against the instrumentId
, that duplicates should be prevented.
However, I guess that because the code is transactional, and the boundaries of the transaction are at the method, (not the synchronized block), that the lock is released before the transaction has persisted, allowing the duplicates.
This must be a fairly common pattern ("create-if-not-exists"). What is the correct recipe for performing this in a concurrent fashion?
Upvotes: 1
Views: 2873
Reputation: 29290
I ended up refactoring the method to shift the transactional boundaries:
public void createInstrument(String instrumentId) {
synchronized(instrumentId.intern()) {
persistInstrument(instrumentId);
}
}
@Transactional
protected void persistInstrument(String instrumentId) {
QInstrument $instrument = QInstrument.instrument;
Instrument instrument = repository.findOne($instrument.instrumentId.eq(instrumentId));
if (instrument == null) {
log.info("Thread {} creating instrument {}", Thread.currentThread().getName(), message.getInstrumentId());
instrument = createInstrument(instrumentId); // impl. ommitted
repository.saveAndFlush(instrument);
}
}
Upvotes: 1