ip696
ip696

Reputation: 7084

Different threads get same entity and don't see changes each other

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

Answers (2)

star67
star67

Reputation: 1832

Your operation consists of 2 actions:

  1. Get an entity from DB

  2. 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

Dzmitry  Prakapenka
Dzmitry Prakapenka

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

Related Questions