Потенциальная проблема параллелизма при проверке и обновлении записи в таблице - PullRequest
1 голос
/ 09 апреля 2019

Вот случай,

Участник должен выкупить токен, чтобы получить доступ (разблокировать) данный предмет. Соответствующие таблицы базы данных:

Таблица 1

Table MEMBER_BALANCE: MEMBER_ID, TOKEN_BALANCE

Таблица 2

Table UNLOCKED_ITEM: MEMBER_ID, DATE_UNLOCKED, ITEM_ID

Проверки или ограничения, которые мне нужно применить, являются

  1. TOKEN_BALANCE должно быть> 0, когда пользователь пытается разблокировать элемент, и
  2. Пользователь не разблокировал тот же элемент раньше.

Я склонен писать простой метод в MemberService.java:

.
@Transactional
public void unlockItem(Member member, Item item){
    memberBalanceDAO.decrementBalance(member);
    itemDAO.unlockItem(member, item);
}

Я выполнил второе требование, добавив ограничение unique для пары MEMBER_ID / ITEM_ID в таблице UNLOCKED_ITEM.

Я думаю, единственное, о чем мне нужно позаботиться, это о том, чтобы пользователи пытались разблокировать множество элементов одновременно, при этом требование TOKEN_BALANCE не выполнено. Например, TOKEN_BALANCE равно 1, но пользователь щелкает, чтобы разблокировать два элемента практически одновременно.

Ниже мой MemberBalanceDAO.decrementBalance метод:

@Transactional
public void decrementBalance(Member member) {
    MemberBalance memberBalance = this.findMemberBalance(member);
    if (memberBalance.getTokens() >= 1) {
        memberBalance.setTokens(memberBalance.getTokens() - 1);
        this.save(memberBalance);
    } else {
        throw new SomeCustomRTException("No balance");
    }
}

Не думаю, что это защитит меня от TOKEN_BALANCE = 1 варианта использования. Меня беспокоит несколько запросов на разблокировку одновременно. Если баланс равен 1, я мог бы получить два вызова на decrementBalance() одновременно, оба зафиксировать баланс на 0, но затем также два успешных вызова на itemDAO.unlockItem(...), верно?

Как мне это реализовать? Должен ли я установить для транзакции метода уровня обслуживания значение isolation = Isolation.SERIALIZABLE? Или есть более чистый / лучший способ приблизиться к этому?

1 Ответ

0 голосов
/ 09 апреля 2019

Я бы скорее предложил вам ввести столбец version в таблицу member_balance. Обратитесь к документации, Оптимистическая блокировка .

Как вы упомянули, вы не можете изменить схему; вы можете использовать оптимистичные блокировки без версии, объяснено здесь .

Или вы можете захотеть пойти на пессимистическую блокировку, объясненную здесь . Затем вы можете изменить свой метод decrementBalance(), чтобы получить баланс участника, не используйте findMemberBalance(). Например,

@Transactional
public void decrementBalance(Member member) {
    MemberBalance memberBalance = entityManager.find(
        MemberBalance.class, member.id, LockModeType.PESSIMISTIC_WRITE,             
        Collections.singletonMap( "javax.persistence.lock.timeout", 200 ) //If not supported, the Hibernate dialect ignores this query hint.
    );
    if (memberBalance.getTokens() >= 1) {
        memberBalance.setTokens(memberBalance.getTokens() - 1);
        this.save(memberBalance);
    } else {
        throw new SomeCustomRTException("No balance");
    }
}

NB: Может не работать как есть; это просто для того, чтобы дать вам несколько советов.

...