如何同步(锁定)JPA实体?

时间:2019-04-05 20:49:54

标签: java spring multithreading jpa

我正在编写一个应用程序,该应用程序定期从数据库表中提取新行(row.status =='NEW'),对每行作为JPA实体进行一些处理,然后以状态==将该行保存回数据库“已处理”。

数据库表:

ID | Status
1  | PROCESSED
2  | NEW
3  | NEW

Java代码:(使用Spring引导框架)

@Component
public class Processor {

   // an JPA repository for selecting Items
   @Autowired
   ItemRepository itemRepository;

   // a thread executor for submitting 
   ExecutorService executor = Executors.newSingleThreadExecutor();



   @Scheduled(fixed-rate=1000)
   void process() {
        List<Item> newItems = itemRepository.findByStatus('NEW');
        for(Item item : newItems) {
            // process each item asyncronously
            executor.submit(()-> {
                // do some processing on this item and update status.
                // THis is time consuming process, may take 4 or 5 seconds
                item.setStatus("PROCESSED");
                itemRepository.save(item);
            });
        }
   }

}

问题是,当一项item1仍在executor中处理,并且状态未更新为PROCESSED时,在下一轮处理中,它仍然将由itemRepository.findByStatus('NEW')选择。它将再次提交进行处理。

如何避免这种情况发生? (除了将fixed-rate更改为fixed-delay之外,是否存在诸如syncronize (item) { .... }之类的锁定机制,使得一旦数据库行仍在处理中,就不会在下一轮中再次选择它。 process()方法?

4 个答案:

答案 0 :(得分:0)

我认为使用Spring调度程序不容易做到这一点。另外,如果您可以在同一个JVM中找到具有某些同步功能的单实例解决方案,那么如果在具有不同JVM的集群中运行多个实例,这将失败。 您可以移至Quartz,后者可以使用(JDBC)数据库来允许一次仅执行一个作业实例。实施org.springframework.scheduling.quartz.QuartzJobBean并将其添加到Spring设置中。

搜索spring boot 2 Quartz的设置方法。这里会占用太多空间,但并不困难。一个开始可能是Spring documentation

答案 1 :(得分:0)

您是否考虑过将自己的状态设置为第三种状态?即PROCESSING-这是一种确保没有2个线程试图处理同一项目的简单方法,每个线程只能处理NEW个工作。

除了使用的STATUS对象只是一个字符串字段外,我已经做了类似的事情。要保留工作,它将变成UPDATE TOP 1 FROM table set status = status + :randomString WHERE status = 'NEW',然后重新选择开始工作。

答案 2 :(得分:0)

您需要一个簿记数据结构来跟踪已提交给执行者的任务。您可以在Item实体中引入一个新状态来进行跟踪,但是考虑到调度频率和项目数量,该方法将引入很多数据库行程,这可能会影响性能。

通过将ConcurrentHashMap的ID放入地图中,使用Items来跟踪已提交给执行者的Item。保存Item后,从地图上删除Item的ID。此映射将帮助您快速决定是否将Item提交给执行者。

如果方法findByStatus返回的项目很大,则可以考虑使用RedisMemcached来跟踪已经提交的项目。

答案 3 :(得分:0)

在我看来,可以通过使用@Transactional隔离级别的未提交读取来解决此问题。请参考以下问题:  Spring @Transactional - isolation, propagation

附加说明是在其他线程当前正在处理的项目上添加ON_PROCESS的另一状态作为标志,该状态将在执行处理之前保存。如果抛出异常,则它将自动回滚,但是成功将导致将其保存到PROCESSED。这里的关键是,只要状态不是NEW,那么只要您正在读取未提交的状态,它就不会被您的计划任务所接管。