1

我有以下代码,它在多个线程中运行:

@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);
         }
      }
   }

这会注销:

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';

我预计,鉴于代码是synchronized针对 的instrumentId,应该防止重复。

但是,我猜是因为代码是事务性的,并且事务的边界在方法上(不是同步块),所以锁在事务持续之前被释放,允许重复。

这必须是一个相当普遍的模式(“如果不存在则创建”)。以并发方式执行此操作的正确方法是什么?

4

1 回答 1

1

我最终重构了转移事务边界的方法:

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);
   }   
}
于 2013-04-04T22:51:05.387 回答