带有JPA锁的弹簧,处理时间长

时间:2019-04-25 17:21:44

标签: spring-data-jpa

我有多个石英工人 每个工作人员都选择一个数据库记录(打印机),然后对其进行一些工作(通过网络从打印机读取信息)。
完成每个作业最多可能需要30秒到1分钟的时间。

回到JDBC的日子,我会运行(伪代码)

     printer = "select from printer where status=? for update"
     do the work, (takes 1 min)
     update the printer record.

我的问题是,使用PESSIMISTIC_WRITE的这种方法是可以的:

public interface PrinterRepo extends CrudRepository<Printer,String>
 {
 @Lock(LockModeType.PESSIMISTIC_WRITE)
 @Query("SELECT r FROM Printers r where r.status = :status")
 Printer findOneAndLock(@Param("status")String status);
}

然后是工人:

@Transactional
public void execute(JobExecutionContext jobExecutionContext) {
      Printer p = printerRepo.findOneAndLock("status");
     //do the work here (30 sec to 1 min)
     printerRepo.save(p);
 }

据我了解,锁将在以@Transactional注释的函数的末尾释放吗? 我的问题是其他工人会怎样?
他们在等待findOneAndLock时会饿死吗?

谢谢

1 个答案:

答案 0 :(得分:4)

无论您要使用哪种类型和级别的锁,以及其他工作人员将如何处理,长期锁以及长期事务都不是一个好的解决方案。在您的情况下,恕我直言,最好使用没有任何锁的其他方法,例如,一个附加表来记录打印机的“锁”:

create table printer_locks (
  printer_id bigint    not null constraint printer_locks_pkey primary key,
  created_at timestamp not null,
  worker_id  bigint    not null constraint fk_printer_locks_worker_id references workers,

  constraint fk_printer_locks_printer_id foreign key (printer_id) references printers(id)
);

当工人想用某台打印机开始工作时,首先它尝试将记录插入到此表中。然后,如果成功,它将开始工作。作业完成后,工作人员将删除该记录。

因为printer_id列是唯一的-其他工作人员将无法同时开始使用同一台打印机。

实施:

@Entity
@Table(name = "workers")
public class Worker {

   @Id 
   @GeneratedValue(...)
   private Long id;
   // other stuff...
}
@Entity
@Table(name = "printers")
public class Printer {

   @Id 
   @GeneratedValue(...)
   private Long id;
   // other stuff...
}
@Entity
@Table(name = "printer_locks")
public class PrinterLock {

   @Id 
   private Long id;

   private Instant createdAt = Instant.now();

   @OneToOne(fetch = FetchType.LAZY)
   @MapsId
   private Printer printer;

   @ManyToOne(fetch = FetchType.LAZY)
   private Worker worker;

   public PrinterLock(Printer printer, Worker worker) {
       this.printer = printer;
       this.worker = worker;
   } 

   // other stuff...
}
public void execute(...) {

     Worker worker = ...;
     Long printerId = ...;

     printerRepo.findById(printerId)
         .map(printer -> {
             try {
                 printerLockRepo.save(new PrinterLock(printer, worker)); 
                 try {
                     // do the work here (30 sec to 1 min)
                 } finally {
                     printerLockRepo.deleteById(printerId); 
                 }
             } catch(Exception e) {
                log.warn("Printer {} is busy", printerId);
             }         
         })
         .orElseThrow(() -> new PrinterNotFoundException(printerId));
 }

请注意,execute方法甚至没有@Transactional注释。

此方法的另一个优点是createdAt列使您可以控制挂起的作业。

进一步阅读: