wake-up-neo.net

Spring, JPA und Hibernate - So erhöhen Sie einen Zähler ohne Parallelitätsprobleme

Ich spiele ein bisschen mit Spring und JPA/Hibernate und bin etwas verwirrt, wie man einen Zähler in einer Tabelle erhöht.

Meine REST - API muss abhängig von der Benutzeraktion einige Werte in der Datenbank erhöhen und dekrementieren.

tagRepository ist eine JpaRepository (Spring-data) und ich habe die Transaktion so konfiguriert

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

Wie Sie sehen, habe ich vor dem .save() absichtlich einen Ruhezustand von 20 Sekunden mit Inkrement JUST gesetzt, um ein Parallelitätsszenario testen zu können.

anfangsmarkierungszähler = 10;

1) Ein Benutzer ruft die Methode "erhöhteTag" an, und der Code schlägt im Ruhezustand ein, so dass der Wert der Entität = 11 und der Wert im DB ist immer noch 10

2) Ein Benutzer ruft den descendTag an und durchläuft den gesamten Code. das Wert ist die Datenbank ist jetzt = 9

3) Der Schlaf endet und trifft die .save mit der Entität, die eine .__ hat. zählt 11 und trifft dann .save ()

Wenn ich die Datenbank überprüfe, ist der Wert für dieses Tag jetzt gleich 11. Wenn es in der Realität (zumindest was ich erreichen möchte) wäre, würde es 10 betragen

Ist dieses Verhalten normal? Oder die @Transactional-Annotation funktioniert nicht?

22
Johny19

Die einfachste Lösung besteht darin, die Parallelität an Ihre Datenbank zu delegieren und sich einfach auf die Isolationsstufe Datenbank zu verlassen lock für die aktuell geänderten Zeilen:

Das Inkrement ist so einfach wie folgt:

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

und die Dekrement-Abfrage lautet:

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

Die UPDATE-Abfrage sperrt die geänderten Zeilen und verhindert, dass andere Transaktionen dieselbe Zeile ändern, , bevor die aktuelle Transaktion festgeschrieben wird (solange Sie nicht READ_UNCOMMITTED verwenden).

45
Vlad Mihalcea

Verwenden Sie beispielsweise Optimistic Locking . Dies sollte die einfachste Lösung sein, um Ihr Problem zu lösen. Weitere Informationen finden Sie unter -> https://docs.jboss.org/hibernate/orm/4.0/devguide/en-US/html/ch05.html

1
mh-dev

Eine weitere eventuell konsistente Lösung zum Hinzufügen:

  • erstellen Sie eine separate counters_increment-Tabelle, um jedes Zählerinkrement einzufügen
  • planer hinzufügen, um die counters-Haupttabelle aus dem counters_increment zu aktualisieren

Mehr:

  • eine andere Datenbank für schreibgeschützte counters_increment-Speicherung (z. B. Cassandra, Redis)
  • counters_increment_{period} Tabelle pro Periode (z. B. Tag) und vollständige Tabelle löschen/neu erstellen, nachdem Daten verarbeitet und nicht mehr benötigt werden
0
aux