2015-05-09 13 views
17

Gram trochę ze Springem i JPA/Hibernate i jestem nieco zdezorientowany na właściwej drodze do zwiększenia licznika w tabeli.Spring, JPA i Hibernate - jak zwiększyć licznik bez problemów z współbieżnością

Moja REST API musi zwiększać i zmniejszać jakąś wartość w bazie danych w zależności od działania użytkownika (na przykład mieszka, lubić lub nie lubić tag uczyni przyrost licznika lub zmniejszenia o jeden w tabeli Tag)

tagRepository jest JpaRepository (wiosna-data) i mam skonfigurowane transakcję jak ten

<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); 
    } 
} 

jak widać mam umieścić celowo spać o 20 sekundy na przyrost tuż przed .save(), aby być w stanie przetestować scenariusz współbieżności.

początkowy licznik znaczników = 10;

1) Użytkownik wzywa increaseTag a kod uderza sen więc wartość podmiotu = 11 i wartość w dB jest nadal 10

2) użytkownik wywołuje decreaseTag i przechodzi cały kod. wartość jest baza danych jest teraz = 9

3) Wykończenia zasypia i uderza .save z podmiotem mającym liczbę 11 i następnie uderza .save()

Kiedy sprawdzić baza danych, wartość tego znacznika jest teraz równa 11 .. kiedy w rzeczywistości (przynajmniej to, co chciałbym osiągnąć) byłaby równa 10

Czy to zachowanie jest normalne? Czy adnotacja @Transactional nie działa?

Odpowiedz

34

Najprostszym rozwiązaniem jest delegate the concurrency to your database i po prostu polegać na zamek database isolation level na aktualnie zmodyfikowanych wierszy:

przyrost jest tak proste, jak to:

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

a zapytanie o dekrementację to:

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

Zapytanie UPDATE przyjmuje blokadę zmodyfikowano wiersze, uniemożliwiając innym transakcjom modyfikowanie tego samego wiersza, pod warunkiem, że nie używasz READ_UNCOMMITTED).

+0

geniusz .. to jest 100 razy lepsze niż zamki pesymistów! – Johny19

+1

(btw po zamieszczeniu mojego pytania szukałem jakiegoś dokumentu na temat poziomu izolacji i spadł na Właśnie zdałem sobie sprawę, że to twój blog) było bardzo jasne i dobrze wyjaśnione. więc +1 za to! – Johny19

+0

Dziękuję za docenienie mojej pracy. –

1

Na przykład użyj Optymistycznego blokowania. To powinno być najprostsze rozwiązanie, aby rozwiązać problem. Aby uzyskać więcej informacji, patrz ->https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html

+0

Dziękuję za odpowiedź. Jeśli zastosuję optymistyczne blokowanie, po wykryciu stanu wyścigu zostanie zgłoszony wyjątek StaleObjectStateException. Chodzi o to, że nie chcę, aby "tag wzrostowy" zawiedził ... i nie chcę (i nie sądzę, że jest bardzo zalecany.), Aby wychwycić wyjątek StaleObjectStateException i spróbować ponownie, dopóki nie zrobi to .save " t wyrzucił wyjątek? Czy istnieje sposób automatycznego ponowienia z najnowszą wartością DB? – Johny19

+0

To nie powinno zawieść, po prostu obsłuż wyjątek. Każde inne rozwiązanie byłby hacky (ale mam niektóre, jeśli chcesz je;)) –

+0

Czy masz na myśli obsługiwać wyjątek w pętli while?ponieważ będę musiał ponowić incrementTag, dopóki nie otrzymam wyjątku. Nie mogę sobie z tym poradzić, na przykład: try (przyrostowy) catch (StaleObjectStateException e) increment()} – Johny19