As per ACID properties, @Transactional should handle Isolation by default. For Isolation.REPEATABLE_READ, it should prevent dirty read, non repeatable read problems. I'm using MySQL database, Spring boot 3.2.3, Java 17
I'm trying to handle concurrency since a 3 days. Nothing is working.
I have a Product Entity, I'm trying to do addQuantity(id){q+10} and mulQuantity(id){q*2} which updates quantity field.
To demonstrate concurrency I'm trying to run two threads one adds other multiplies
My Expectation is that:
Either it should add first and multiply next or vice versa, but in any case the updates should not be overriden
But with my approaches it is getting overriden
Controller
@PostMapping("/run")
ResponseEntity<?> run() {
ExecutorService executorService = Executors.newFixedThreadPool(4);
executorService.submit(() -> productService.addQuantity(1L));
executorService.submit(() -> productService.mulQuantity(1L));
executorService.submit(() -> productService.addQuantity(2L));
executorService.submit(() -> productService.mulQuantity(2L));
executorService.shutdown();
return ResponseEntity.ok("ok");
}
Service
@Transactional
public void addQuantity(Long id) {
Product p = productRepository.findById(id).get();
p.setQuantity(p.getQuantity() + 10);
log.info("ADD id {}, q {}", id, p.getQuantity());
productRepository.save(p);
}
@Transactional
public void mulQuantity(Long id) {
Product p = productRepository.findById(id).get();
p.setQuantity(p.getQuantity() * 2);
log.info("MUL id {}, q {}", id, p.getQuantity());
productRepository.save(p);
}
Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private int price;
private int quantity;
}
My Approaches:
- I think this is Optimistic locking,
@Vesrion, Got thisObjectOptimisticLockingFailureExceptionwhich is ok, but not concurrent..
@Version
private int version;
- Adding Isolation level at add, multiply method, which is by default the same, overrides it
@Transactional(isolation = Isolation.REPEATABLE_READ)
- Only
workingway was, pessimistic lock on repository
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<Product> findById(Long id);
- Did not try
Isolation.SERIALIZATION, but I think it will work but not efficient
My questions:
1)I want to know, is adding @Transactional not sufficient to handle concurrency? (when I run two different transactions in mysql command line, it either waits or give dead lock, it works very well) But why there is an issue in Spring boot.
2)Do we really need to handle locking separately, no way out?
3) Ways to handle concurrency with transaction, with lock
4) Except for rollback what is use of @Transactional?
Open to provide more details, please help me out.