Различные потоки получают одну и ту же сущность и не видят изменений друг друга - PullRequest
0 голосов
/ 04 июля 2019

У меня есть стол products. В этой таблице мне нужно is_active с ограничением - only one row with the same type can be true.

У меня есть сервис для сохранения нового Product с проверкой:

@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);
    }
}

Когда я вызываю save метод в нескольких потоках и пытаюсь проверить активный продукт - в обоих потоках findOneByTypeAndIsActive методы возвращают productInDb is null, потому что у меня нет активных продуктов в таблице. В каждом потоке я устанавливаю product.setActive(true); и пытаюсь сохранить в БД. Если у меня нет ограничений в БД - я сохраняю оба продукта в состоянии is_active = true, и эта проверка не выполняется:

     if (productInDb != null)
        throw new AlreadyActiveException();

Мой вопрос - можно ли это исправить без добавления ограничения в БД? А проверка выше бесполезна?

Ответы [ 2 ]

1 голос
/ 04 июля 2019

С моей точки зрения, это не лучший дизайн таблиц БД, в котором в структуре записей есть флаг is_active в паре с ограничением, что только одна запись в таблице может быть is_active одновременно.

Вы должны использовать ограничения схемы базы данных или блокировать всю таблицу со всеми записями.Как заблокировать всю таблицу для модификации зависит от базы данных.Я не думаю, что JPA изначально поддерживает такие блокировки.

Но вы написали:

Можно ли это исправить без добавления ограничения в БД?

Нет, это невозможно со строгой гарантией для всех клиентов.

Но если у вас есть только одно приложение, которое использует эту таблицу - вы можете использовать локальные, специфичные для приложения блокировки, например, вы можете создать Read /Напишите Java-блокировки на уровне @Service.

1 голос
/ 04 июля 2019

Ваша операция состоит из 2 действий:

  1. Получить объект из БД

  2. Сохранить новый объект, если он не существует

Ваша проблема в том, что несколько потоков могут запустить эту операцию одновременно и не видят изменений друг друга.Это определенно не то, что вам нужно.Ваша операция, состоящая из нескольких действий, должна быть атомарной.

Если я правильно понимаю, у вас есть правило хранить только 1 active продукт того же type в хранилище данных.Это звучит как требование согласованности данных, которое должно решаться на уровне приложения.

Самый наивный способ решить вашу проблему - это получить блокировку перед выполнением вашей операции.Это может быть решено либо с synchronised, либо с явной блокировкой:

@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);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...