Spring, JPA и Hibernate - как увеличить счетчик без concurrency проблем

Я немного поиграю с Spring и JPA/Hibernate, и я немного смущен правильно, чтобы увеличить счетчик в таблице.

API-интерфейс REST API должен увеличивать и уменьшать некоторое значение в базе данных в зависимости от действия пользователя (в приведенном ниже примере, симпатии или неприятии тега будет увеличиваться или уменьшаться счетчик на единицу в таблице тегов)

tagRepository - это JpaRepository (Spring -data) и я сконфигурировал транзакцию, подобную этой

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"/>

@Controller
public class TestController {

    @Autowired
    TagService tagService

    public void increaseTag() {
        tagService.increaseTagcount();
    }
    public void decreaseTag() {
        tagService.decreaseTagcount();

    }
}

@Transactional
@Service
public class TagServiceImpl implements TagService {


    pubic void decreaseTagcount() {
        Tag tag = tagRepository.findOne(tagId);
        decrement(tag)
    }

    pubic void increaseTagcount() {
        Tag tag = tagRepository.findOne(tagId);
        increment(tag)
    }

    private void increment(Tag tag) {
        tag.setCount(tag.getCount() + 1); 
        Thread.sleep(20000);
        tagRepository.save(tag);
    }

    private void decrement(Tag tag) {
        tag.setCount(tag.getCount() - 1); 
        tagRepository.save(tag);
    }
}

Как вы можете видеть, я поставил цель спящий на 20 секунд с увеличением JUST до того, как .save() сможет протестировать сценарий concurrency.

счетчик начальных тегов = 10;

1) Пользователь вызывает увеличениеTag, и код попадает в сон, поэтому значение объекта = 11, а значение в БД все еще 10

2) пользователь вызывает сокращениеTag и проходит через весь код. значение - это база данных = 9

3) Спит заканчивается и ударяет .save с сущностью, имеющей счет 11, а затем удаляет .save()

Когда я проверяю базу данных, значение для этого тега теперь равно 11.. когда на самом деле (по крайней мере, чего я хотел бы достичь) он был бы равен 10

Это нормальное поведение? Или аннотация @Transactional не делает работу?

Ответы

Ответ 1

Простейшим решением является делегировать concurrency в вашу базу данных и просто полагаться на уровень изоляции базы данных заблокировать измененные строки:

Приращение так же просто, как это:

UPDATE Tag t set t.count = t.count + 1 WHERE t.id = :id;

а запрос декремента:

UPDATE Tag t set t.count = t.count - 1 WHERE t.id = :id;

Запрос UPDATE принимает блокировку измененных строк, не позволяя другим транзакциям изменять одну и ту же строку, до совершения текущей транзакции (as если вы не используете READ_UNCOMMITTED).