Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring data PESSIMISTIC_WRITE returns old DB value

I have this method in my interface which extends CrudRepository:

@Lock(LockModeType.PESSIMISTIC_WRITE)
Voucher findTop1ByUsedFalseAndProductOrderByIdAsc(Product product);

And this component:

@Component
@Transactional
public class VoucherFetchComponent {
    @Autowired
    private VoucherRepository voucherRepository;
public Voucher fetch(Product product) {
    Voucher voucher=voucherRepository.findTop1ByUsedFalseAndProductOrderByIdAsc(product);
    if(voucher==null)
       return null;
    voucher.setUsed(true);
    voucherRepository.save(voucher);
    return voucher;
}
}

The problem is concurrently executing fetch() method. (assume that I only have 1 voucher left in my DB)

What I expect:

1- Services A and B call the fetch() method of VoucherFetchComponent

2- Service A (or B) locks the row, updates one of it's fields and returns

3- The other service can now access the row which was locked. But now the query doesn't match the given criteria (used=false), so it returns null.

What I get

1 and 2 just like above

3- The other service returns the old object which has the parameter (used=false) but it had been updated before!

like image 927
MoeKav Avatar asked Nov 19 '22 02:11

MoeKav


1 Answers

If you look carefully at the javadoc of @Lock it states that:

Annotation used to specify the LockModeType to be used when executing the query.

So the lock is active only during the query execution.. after the query is done.. the lock is released. It is not active for the duration of the entire transaction as you expect it to be.

I would suggest using standard locking mechanism:

VoucherRepository

public void lock(Voucher voucher, LockModeType lockType){

entityManager.lock(voucher, lockType);

VoucherFetchComponent

public Voucher fetch(Product product) {
    Voucher voucher=voucherRepository.findTop1ByUsedFalseAndProductOrderByIdAsc(product);
    if(voucher==null)
       return null;
    voucherRepository.lock(voucher, LockModeType.PESSIMISTIC_WRITE);
    voucher.setUsed(true);
    voucherRepository.save(voucher);
    return voucher;
}

Once the transaction finishes, the lock will be released.

like image 117
Maciej Kowalski Avatar answered Nov 23 '22 21:11

Maciej Kowalski