Пружина с замком JPA с длительным временем обработки - PullRequest
7 голосов
/ 25 апреля 2019

У меня несколько кварцевых рабочих. Каждый рабочий выбирает запись в БД (принтер), а затем выполняет с ней некоторую работу (считывание информации с принтера по сети).
Это может занять от 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 Ответ

4 голосов
/ 13 мая 2019

Независимо от того, какой тип и уровень блокировок вы собираетесь использовать, и что будет с другими работниками, долгосрочная блокировка, а также долгосрочная транзакция не являются хорошим решением.ИМХО в вашем случае лучше использовать другой подход без каких-либо блокировок, например, дополнительная таблица для записи «блокировок» принтера:

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, который позволяет контролировать зависаниезадания.

Дополнительная литература:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...