2016-07-13 4 views
2

Я занимаюсь разработкой веб-приложение с использованием Spring MVC и имеют такие методы в моем приложении:гонки состояние между транзакциями

@Transactional 
public void methodA(Long id, String color) { 
    Fruit fruit = entityManager.createNamedQuery("Fruit.findById", Fruit.class).setParameter(1, id).getSingleResult(); 
    fruit.setColor("color"); 
    entityManager.merge(fruit); 
} 

@Transactional 
public void methodB(Long id, int price) { 
    Fruit fruit = entityManager.createNamedQuery("Fruit.findById", Fruit.class).setParameter(1, id).getSingleResult(); 
    fruit.setPrice(price); 
    entityManager.merge(fruit); 
} 

Эти два метода часто называют почти в то же самое время, и из-за того, что происходит состояние гонки , Есть ли способ решить эту проблему? Я думаю, что поместить оба из них в один синхронизированный метод не будет хорошей идеей, потому что я ожидаю много вызовов этих методов (тысяч) разными пользователями одновременно, поэтому это может вызвать задержки. Исправьте меня, если я ошибаюсь.

+0

Для чего ваш сложный и дорогой запрос - это просто 'entityManager.find (Fruit.class, id)'. – chrylis

+0

Эти методы находятся в Сервисе или в DAO (или репозитории)? –

+0

@KimAragonEscobar, он находится внутри класса репозитория. Класс репозитория находится внутри класса обслуживания –

ответ

0

EntityManager.merge(T entity) объединяет состояние данного объекта в текущий контекст сохранения. В зависимости от базового хранилища данных, к тому времени, когда он объединяет объект, одна и та же запись сущности из репозитория может уже быть изменена с помощью различной информации, поэтому любая из этой измененной информации может быть потеряна и перезаписана последующим слиянием.

Вместо того, чтобы использовать EntityManager.merge(T entity), используйте EntityManager.createQuery(CriteriaUpdate updateQuery).executeUpdate(). Это должно только обновлять значения указанных вами атрибутов.

@Transactional 
public void methodA(Long id, String color) { 
    final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
    final CriteriaUpdate<Fruit> updateColor = cb.createCriteriaUpdate(Fruit.class); 
    final Root<Fruit> updateRoot = updateColor.from(Fruit.class); 
    updateColor.where(cb.equal(updateRoot.get(Fruit_.id), id)); 
    updateColor.set(updateRoot.get(Fruit_.id), id); 
    entityManager.createQuery(updateColor).executeUpdate(); 
} 

@Transactional 
public void methodB(Long id, int price) { 
    final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); 
    final CriteriaUpdate<Fruit> updatePrice = cb.createCriteriaUpdate(Fruit.class); 
    final Root<Fruit> updateRoot = updatePrice.from(Fruit.class); 
    updatePrice.where(cb.equal(updateRoot.get(Fruit_.id), id)); 
    updatePrice.set(updateRoot.get(Fruit_.price), price); 
    entityManager.createQuery(updatePrice).executeUpdate(); 
} 

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

0

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

Другой вариант: optimistic locking. Перед записью ресурса его состояние сравнивается с тем, когда оно было первоначально прочитано. Если они отличаются, другой процесс изменил этот ресурс, который обычно заканчивается на OptimisticLockException. Хорошо, вы можете поймать его и снова попытаться обновить этот ресурс. Точно так же вы могли бы рассказать пользователю о конфликте. Это твой выбор.

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

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

0

На основании ответа на этот вопрос Would transactions/spring Transaction propagation solve this concurrency issue? вы можете попытаться поместить @transactional в @service, а не каждый метод из репозитория. Вы бы что-то вроде этого:

@Service 
@Transactional 
class MyService { 

    @Autowired 
    MyRepo repository; 
    public void methodA(Data data){ 
     repository.methodA(data); 
    } 
    public void methodB(Data data){ 
     repository.methodB(data); 
    } 
} 

Я знаю, что проблема с этого поста не то же самое, что у вас есть, но это может решить вашу проблему.