I have two concurrent transactions which check if appropriate PostgreSQL table record exists and if no - try to insert a new one.
I have the following Spring Data repository method:
@Lock(LockModeType.PESSIMISTIC_WRITE)
TaskApplication findByUserAndTask(User user, Task task);
As you may see I have added @Lock(LockModeType.PESSIMISTIC_WRITE)
there. Inside of my service method I check if entity exists and if no, create a new one:
@Transactional
public TaskApplication createIfNotExists(User user, Task task) {
TaskApplication taskApplication = taskApplicationRepository.findByUserAndTask(user, task);
if (taskApplication == null) {
taskApplication = create(user, task);
}
}
I also added unique constraint on tasks_applications (user_id, task_id)
fields.
ALTER TABLE public.task_applications
ADD CONSTRAINT "task_applications-user_id_task_id_unique"
UNIQUE (user_id, task_id)
automatically created corresponding unique index:
CREATE UNIQUE INDEX "task_applications-user_id_task_id_unique"
ON public.task_applications
USING btree (user_id, task_id)
Unfortunately in case of two concurrent transactions with same user_id
and task_id
, the second one always fail with the following exception:
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "task_applications-user_id_task_id_unique"
Key (user_id, task_id)=(1, 1) already exists.
What am I doing wrong and how to fix it in order to be able to handle such situation in my service method?
UPDATED
I don't understand why the following method doesn't block the execution of second transaction until first transaction will be committed or rolled back:
TaskApplication taskApplication = taskApplicationRepository.findByUserAndTask(user, task);
I'm not sure about PostgreSQL, but typically it should block the execution based on unique index in case of no record.
How to achieve it?
UPDATED 2
The sequence of SQL commands generated during execution:
select * from task_applications taskapplic0_ where taskapplic0_.user_id=? and taskapplic0_.task_id=? for update of taskapplic0_
insert into task_applications values (?,...)
INSERT ... ON CONFLICT
.Lock(LockModeType.PESSIMISTIC_WRITE)
overfindByUserAndTask method
. In my understanding it should block execution of transaction #2 until transaction #1 will be committed or rolled back. And in order to archive it, it should usetask_applications-user_id_task_id_unique
index as a semaphore in case of attempt to create a new entity. But it doesn't work.