Reputation: 7084
I have a table products
. In this table I need is_active
with constraint - only one row with the same type can be true.
I have service for saving new Product
with checking:
@Service
public class ProductServiceImpl implements ProductService {
private final ProductRepository productRepository;
public ProductServiceImpl(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Override
public void save(Product product) {
Product productInDb = productRepository.findOneByTypeAndIsActive(product.getType());
if (productInDb != null)
throw new AlreadyActiveException();
product.setActive(true);
productRepository.saveAndFlush(product);
}
}
When I call save
method in a few threads and try to check active product - in both threads findOneByTypeAndIsActive
methods return productInDb is null
because I haven't active products in the table.
In each thread I set product.setActive(true);
and try to save in DB.
If I don't have the constraint in DB - I save both products in is_active = true
state and this checking not performed:
if (productInDb != null)
throw new AlreadyActiveException();
My question - Can I fix this without adding of constraint in DB? And checking above is useless?
Upvotes: 0
Views: 1391
Reputation: 1832
Your operation consists of 2 actions:
Get an entity from DB
Save a new entity if it doesn't exist
Your problem is that few threads can start this operation at the same time and don't see changes of each other. That's definitely not what you need. Your operation which consists of a few actions must be atomic.
If I understand correct you have a rule to keep only 1 active
product of the same type
in datastore. It sounds like a data consistency requirement, which should be solved on application level.
The most naive option to solve your problem is to acquire a lock before performing your operation. It may be solved either with synchronised
or explicit lock:
@Override
public synchronised void save(Product product) {
Product productInDb = productRepository.findOneByTypeAndIsActive(product.getType());
if (productInDb != null)
throw new AlreadyActiveException();
product.setActive(true);
productRepository.saveAndFlush(product);
}
Upvotes: 1
Reputation: 734
From my point of view this is not the best db tables design to have is_active
flag in record structure in pair with restriction, that only one record in table can be is_active
at same time.
You have to use database schema constraints or you have to lock whole table with all records. How to lock whole table for modification is database specific. I don't think JPA natively supports such locks.
But you wrote:
Can I fix this without adding of constraint in DB?
No, it is not possible with strict guarantee for all clients.
But if you have only one application that uses this table - you can use local, application specific locks, for example you can create Read/Write java locks on @Service level.
Upvotes: 1