Marty Pitt
Marty Pitt

Reputation: 29290

Spring data - Concurrent create-if-not-exists leads to duplicate entires

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

Answers (1)

Marty Pitt
Marty Pitt

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

Related Questions