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
).
Ответ 2
Например, используйте Оптимистическую блокировку.
Это должно быть самым простым решением для решения вашей проблемы.
Подробнее см. → https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html