it-swarm.com.ru

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

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

Мой 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 {


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

    public 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(), чтобы иметь возможность проверить сценарий параллелизма.

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

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

2) пользователь вызываетужалую метку и просматривает весь код. метод значение базы данных сейчас = 9

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

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

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

22
Johny19

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

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

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 блокирует измененные строки, не позволяя другим транзакциям изменять эту же строку, перед тем, как текущая транзакция фиксирует (если вы не используете READ_UNCOMMITTED).

45
Vlad Mihalcea

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

1
mh-dev

Еще одно непротиворечивое решение для добавления:

  • создать отдельную таблицу counters_increment для вставки каждого приращения счетчика
  • добавьте планировщик для обновления основной таблицы counters из кода counters_increment

Больше:

  • другая БД для хранения counters_increment с высокой интенсивностью записи (например, Cassandra, Redis)
  • counters_increment_{period} таблицы за период (например, день) и удалить/воссоздать всю таблицу после обработки данных, которые больше не нужны
0
aux