我有多个石英工人
每个工作人员都选择一个数据库记录(打印机),然后对其进行一些工作(通过网络从打印机读取信息)。
完成每个作业最多可能需要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时会饿死吗?
谢谢
答案 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
列使您可以控制挂起的作业。
进一步阅读: