Ruhezustand - Die Stapelaktualisierung hat eine unerwartete Zeilenanzahl von der Aktualisierung zurückgegeben: 0 tatsächliche Zeilenanzahl: 0 erwartet: 1


140

Ich erhalte folgenden Fehler im Ruhezustand. Ich kann die Funktion identifizieren, die das Problem verursacht. Leider gibt es mehrere DB-Aufrufe in der Funktion. Ich kann die Zeile, die das Problem verursacht, nicht finden, da die Sitzung am Ende der Transaktion im Ruhezustand beendet wurde. Der unten erwähnte Fehler im Ruhezustand scheint ein allgemeiner Fehler zu sein. Es wird nicht einmal erwähnt, welche Bean das Problem verursacht. Kennt jemand diesen Fehler im Ruhezustand?

org.hibernate.StaleStateException: Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1
        at org.hibernate.jdbc.BatchingBatcher.checkRowCount(BatchingBatcher.java:93)
        at org.hibernate.jdbc.BatchingBatcher.checkRowCounts(BatchingBatcher.java:79)
        at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:58)
        at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:195)
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:235)
        at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:142)
        at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:297)
        at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:27)
        at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:985)
        at org.hibernate.impl.SessionImpl.managedFlush(SessionImpl.java:333)
        at org.hibernate.transaction.JDBCTransaction.commit(JDBCTransaction.java:106)
        at org.springframework.orm.hibernate3.HibernateTransactionManager.doCommit(HibernateTransactionManager.java:584)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransacti
onManager.java:500)
        at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManag
er.java:473)
        at org.springframework.transaction.interceptor.TransactionAspectSupport.doCommitTransactionAfterReturning(Transaction
AspectSupport.java:267)
        at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:170)
        at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:176)

Vielen Dank an Peter Mortensen. Ich habe meine E-Mail aktualisiert.
Sujee

Ich habe das gleiche Problem. Dies ist kein großes Problem, da es sehr selten vorkommt. Die Verwendung von show_sql ist nicht praktikabel, da für die Reproduktion dieses Verhaltens Millionen von Transaktionen erforderlich sind. Da dies jedoch während eines von mir durchgeführten Systemtests (der Millionen von Transaktionen umfasst) wiederholt auftritt, vermute ich, dass es einen bestimmten Grund gibt.
daniel_or_else

Ich bin auf dieses Problem gestoßen, als ich versucht habe, Zeilen zu aktualisieren, die KEINE Änderungen aufweisen. Aktualisieren Sie nichts, wenn es keinen Unterschied gibt.
Fifman

Antworten:


62

Ohne Code und Zuordnungen für Ihre Transaktionen ist es nahezu unmöglich, das Problem zu untersuchen.

Versuchen Sie jedoch Folgendes, um einen besseren Überblick über die Ursachen des Problems zu erhalten:

  • Setzen Sie in Ihrer Konfiguration für den Ruhezustand hibernate.show_sql auf true. Dies sollte Ihnen die ausgeführte SQL anzeigen und das Problem verursachen.
  • Stellen Sie die Protokollebenen für Spring und Hibernate auf DEBUG ein. Auch hier erhalten Sie eine bessere Vorstellung davon, welche Zeile das Problem verursacht.
  • Erstellen Sie einen Komponententest, der das Problem repliziert, ohne im Frühjahr einen Transaktionsmanager zu konfigurieren. Dies sollte Ihnen eine bessere Vorstellung von der fehlerhaften Codezeile geben.

Hoffentlich hilft das.


21
hibernate.show_sql> Ich würde eher empfehlen, die Protokollkategorie org.hibernate.SQL auf DEBUG zu setzen. Auf diese Weise müssen Sie die Ruhezustandskonfiguration nicht nur für die Protokollierung ändern.
Thierry

Die Verwendung von "show_sql" kann sehr ausführlich sein und in der Produktion nicht praktikabel sein. Zu diesem Zweck habe ich die catch-Klausel in Zeile 74 der BatchingBatcher-Klasse so geändert, dass die Anweisung mit einem ps.toString () gedruckt wird, um nur die Anweisung zu haben, bei der das Problem auftritt.
Orden

71

Ich habe die gleiche Ausnahme beim Löschen eines Datensatzes nach ID erhalten, der überhaupt nicht vorhanden ist. Überprüfen Sie also, ob der Datensatz, den Sie aktualisieren / löschen, tatsächlich in der Datenbank vorhanden ist


12
Ich hatte dieses Problem, als ich ein Kind aus einer Eltern-Kind-Beziehung entfernte, das Elternteil speicherte (wodurch das Kind gelöscht wird) und dann versuchte, das Kind auch manuell zu löschen.
Dave Thieben

Dies löste mein Problem. Der Datensatz war nicht vorhanden und mein Dienst rief die updateAll () -Methode auf, während er tatsächlich die createOrUpdateAll () -Methode aufrufen musste. Vielen Dank.
Mital Pritmani

3
Wie kann man das Problem lösen? Wenn ich einen Datensatz erhalte und ihn dann lösche; Wenn das System es jedoch bereits löscht, bevor ich ein anderes lösche, löst meine Anwendung die Ausnahme aus.
Stony

@davethieben, das war genau die Information, die ich brauchte. Ich wusste nicht, dass die Eltern-Kind-Beziehung beim Löschen bestehen blieb.
Schnee

Die Lösung besteht darin, beim Abrufen des Datensatzes select for update zum Sperren der Zeile zu verwenden, damit nichts anderes sie löschen kann, bevor Sie dies tun.
Matthew Read

54

Lösung: Wenn Sie in der Zuordnungsdatei für den Ruhezustand für die Eigenschaft id eine Generatorklasse verwenden, sollten Sie für diese Eigenschaft den Wert nicht explizit mithilfe einer Setter-Methode festlegen.

Wenn Sie den Wert der Id-Eigenschaft explizit festlegen, wird der obige Fehler angezeigt. Überprüfen Sie dies, um diesen Fehler zu vermeiden. oder Es wird ein Fehler angezeigt, wenn Sie in der Zuordnungsdatei den Feldgenerator = "native" oder "inkrementell" erwähnen und in Ihrer DATABASE die zugeordnete Tabelle nicht auto_incremented ist. Lösung: Gehen Sie zu Ihrer DATABASE und aktualisieren Sie Ihre Tabelle, um auto_increment festzulegen


2
Absolut richtig. Dies muss die richtige Antwort sein. Danke @ Rēda Biramanē
Kuldeep Verma

1
Allerdings können Sie den ID - Wert auf ‚0‘ gesetzt , und dann wird Hibernate fühlen sich frei , es zu überschreiben
forresthopkinsa

1
Vielen Dank! Ich bin mir nicht sicher, warum dies nicht die Antwort mit dem höchsten Rang ist.
Stuart McIntyre

18

Dies ist mir einmal zufällig passiert, als ich einigen Objekten bestimmte IDs zugewiesen habe (Testen) und dann versucht habe, sie in der Datenbank zu speichern. Das Problem war, dass es in der Datenbank eine spezielle Richtlinie zum Einrichten der IDs der Objekte gab. Weisen Sie nur keine ID zu, wenn Sie eine Richtlinie im Ruhezustand haben.


15

In meinem Fall bin ich in zwei ähnlichen Fällen zu dieser Ausnahme gekommen:

  • In einer mit annotierten Methode hatte @Transactionalich einen Anruf bei einem anderen Dienst (mit langen Antwortzeiten). Die Methode aktualisiert einige Eigenschaften der Entität (nach der Methode ist die Entität noch in der Datenbank vorhanden). Wenn der Benutzer beim zweiten Verlassen der Transaktionsmethode zweimal die Methode anfordert (da er glaubt, dass sie beim ersten Mal nicht funktioniert), versucht Hibernate, eine Entität zu aktualisieren, die ihren Status bereits zu Beginn der Transaktion geändert hat. Wenn der Ruhezustand nach einer Entität in einem Status sucht und dieselbe Entität gefunden hat, die jedoch bereits durch die erste Anforderung geändert wurde, wird eine Ausnahme ausgelöst, da die Entität nicht aktualisiert werden kann. Es ist wie ein Konflikt in der GIT.
  • Ich hatte automatische Anfragen (zur Überwachung der Plattform), die eine Entität aktualisieren (und das manuelle Rollback einige Sekunden später). Diese Plattform wird jedoch bereits von einem Testteam genutzt. Wenn ein Tester einen Test in derselben Entität wie die automatischen Anforderungen durchführt (innerhalb derselben Hundertstel Millisekunde), erhalte ich die Ausnahme. Wie im vorherigen Fall hat sich beim Beenden der zweiten Transaktion die zuvor abgerufene Entität bereits geändert.

Fazit: In meinem Fall war es kein Problem, das im Code zu finden ist. Diese Ausnahme wird ausgelöst, wenn Hibernate feststellt, dass sich die zum ersten Mal aus der Datenbank abgerufene Entität während der aktuellen Transaktion geändert hat , sodass sie nicht in die Datenbank gelöscht werden kann, da Hibernate nicht weiß, welche Version der Entität korrekt ist: die aktuelle Transaktionsabruf am Anfang; oder die bereits in der Datenbank gespeicherte.

Lösung: Um das Problem zu lösen, müssen Sie mit dem Hibernate LockMode spielen , um den für Ihre Anforderungen am besten geeigneten zu finden.


Hallo, danke für deine hilfreiche Antwort. Könnten Sie bitte mitteilen, für welchen LockMode Sie sich letztendlich entschieden haben, um den Fehler zu beseitigen? Ich habe ein ähnliches Problem, wenn eine API in einer anderen Millisekunde nacheinander aufgerufen wird, weil der Benutzer versehentlich zweimal geklickt hat. Pessimistisches Sperren beeinträchtigt die Leistung. Möchten Sie wissen, wofür Sie sich letztendlich entschieden haben?
Madhur Bhaiya

1
Es ist lange her, dass ich das Problem hatte und ich erinnere mich nicht gut an die genaue Lösung, die ich eingeführt habe, aber es war LockMode.READoder LockMode.OPTIMISTIC.
Sergio Lema

13

Ich bin gerade auf dieses Problem gestoßen und habe festgestellt, dass ich einen Datensatz lösche und danach versuche, ihn in einer Hibernate-Transaktion zu aktualisieren.


9

Dies kann passieren, wenn Trigger zusätzliche DML-Abfragen (Datenmodifikation) ausführen, die sich auf die Zeilenanzahl auswirken. Meine Lösung bestand darin, oben am Auslöser Folgendes hinzuzufügen:

SET NOCOUNT ON;

1
Diese Antwort hat mich in die richtige Richtung geschickt. Ich habe mit einer Legacy-Datenbank gearbeitet, auf der sich ein Auslöser befand. Zum Glück habe ich die Funktion des Auslösers durch Code ersetzt, sodass ich sie einfach löschen konnte.
S. Baggy

2
+1 Das war auch mein Problem. Ich habe postgresql verwendet und musste daher die Annotation @SQLInsert verwenden, um die Überprüfung der Zeilenanzahl auszuschalten: technology-ebay.de/the-teams/mobile-de/blog/…
s1mm0t

NOCOUNT AUSSETZEN *
G. Ciardini

7

Ich stand vor dem gleichen Problem. Der Code funktionierte in der Testumgebung. Aber es funktionierte nicht in einer Staging-Umgebung.

org.hibernate.jdbc.BatchedTooManyRowsAffectedException: Batch update returned unexpected row count from update [0]; actual row count: 3; expected: 1

Das Problem war, dass die Tabelle einen einzelnen Eintrag für jeden Primärschlüssel beim Testen der DB-Tabelle hatte. In der Staging-Datenbank gab es jedoch mehrere Einträge für denselben Primärschlüssel. (Das Problem ist, dass in der Staging-Datenbank für die Tabelle keine Primärschlüsseleinschränkungen vorhanden waren und auch mehrere Einträge vorhanden waren.)

Jedes Mal, wenn ein Update durchgeführt wird, schlägt dies fehl. Es wird versucht, einen einzelnen Datensatz zu aktualisieren, und es wird erwartet, dass die Aktualisierungsanzahl 1 beträgt. Da jedoch 3 Datensätze in der Tabelle für denselben Primärschlüssel vorhanden sind, wird die Anzahl der Ergebnisaktualisierungen auf 3 festgelegt. Da die erwartete Aktualisierungsanzahl und die tatsächliche Ergebnisaktualisierungsanzahl nicht übereinstimmen Es löst eine Ausnahme aus und rollt zurück.

Nach dem habe ich alle Datensätze entfernt, die einen doppelten Primärschlüssel haben, und Primärschlüsseleinschränkungen hinzugefügt. Es funktioniert gut.

Hibernate - Batch update returned unexpected row count from update: 0 actual row count: 0 expected: 1

tatsächliche Zeilenanzahl: 0 // bedeutet, dass kein Datensatz zum Aktualisieren der
Aktualisierung gefunden wurde: 0 // bedeutet, dass kein Datensatz gefunden wurde, sodass keine Aktualisierung
erwartet wird: 1 // bedeutet, dass mindestens 1 Datensatz mit Schlüssel in der DB-Tabelle erwartet wird.

Hier besteht das Problem darin, dass die Abfrage versucht, einen Datensatz für einen Schlüssel zu aktualisieren, der Ruhezustand jedoch keinen Datensatz mit dem Schlüssel gefunden hat.


Hallo @ParagFlume, ich habe den gleichen Fehler "Stapelaktualisierung hat unerwartete Zeilenanzahl von Aktualisierung zurückgegeben: 0 tatsächliche Zeilenanzahl: 0 erwartet: 1" Wenn ich versuche, ein Objekt aus meiner Datenbank auszuwählen, besteht das Problem nur in einer Produktion. Haben Sie eine? Idee, wie man dieses Problem löst, ich bin in einer kritischen Situation. Grüße !
James

Ich denke, die Abfrage hat keinen Datensatz in der Datenbank gefunden. Sie erwartet, dass ein Datensatz in der Datenbank gefunden wird, den sie zu aktualisieren versucht. Sie können manuell / Abfrage-Browser überprüfen, ob tatsächlich ein Datensatz in der Datenbank vorhanden ist.
ParagFlume

Ja, der Datensatz ist vorhanden, aber mein Problem ist, warum der Ruhezustand versucht, ein Update durchzuführen. Ich verwende nur einen Auswahldienst, um das Objekt aus der Datenbank abzurufen.
James

Möglicherweise versuchen Sie, den Status der Objekte zu ändern. und Sitzung ist noch offen.
ParagFlume

wie ich dieses Verhalten überprüfen kann, weil ich nicht die Person bin, die den Dienst entwickelt ...
James

5

Wie Julius sagt, geschieht dies, wenn ein Update für ein Objekt erfolgt, dessen untergeordnete Elemente gelöscht werden. (Wahrscheinlich, weil ein Update für das gesamte Vaterobjekt erforderlich war und wir es manchmal vorziehen, die Kinder zu löschen und sie erneut in den Vater einzufügen (neu, alt spielt keine Rolle), zusammen mit anderen Updates, die der Vater für eines der Objekte haben könnte seine anderen einfachen Felder) Also ... damit dies funktioniert, löschen Sie die Kinder (innerhalb einer Transaktion) durch Aufrufen childrenList.clear()(Schleifen Sie nicht durch die Kinder und löschen Sie jedes mit einigen childDAO.delete(childrenList.get(i).delete()))und Einstellungen @OneToMany(cascade = CascadeType.XXX ,orphanRemoval=true)auf der Seite des Vaterobjekts. Aktualisieren Sie dann den Vater (dadDAO.update (Vater)). (Wiederholen Sie dies für jedes Vaterobjekt.) Das Ergebnis ist, dass Kindern die Verbindung zu ihrem Vater entzogen wird und sie dann vom Framework als Waisenkinder entfernt werden.


5

Ich bin auf dieses Problem gestoßen, bei dem wir eine Eins-Viele-Beziehung hatten.

In der Ruhezustand-Hbm-Zuordnungsdatei für Master, für Objekt mit festgelegter Typanordnung, hinzugefügt cascade="save-update"und es hat gut funktioniert.

Ohne dies versucht der Ruhezustand standardmäßig, ein Update für einen nicht vorhandenen Datensatz durchzuführen, und fügt stattdessen ein.


4

Dies kann auch passieren, wenn Sie versuchen, UPDATEeinen PRIMARY KEY zu verwenden .


3

Ich habe das gleiche Problem und ich habe überprüft, dass dies aufgrund des Auto-Inkrement-Primärschlüssels auftreten kann. Um dieses Problem zu lösen, fügen Sie keinen automatischen Inkrementwert in den Datensatz ein. Daten ohne Primärschlüssel einfügen.


3

Eine andere Möglichkeit, diesen Fehler zu erhalten, besteht darin, ein Nullelement in einer Sammlung zu haben.


2

Dies passiert, wenn Sie versuchen, dasselbe Objekt zu löschen und dann dasselbe Objekt erneut zu aktualisieren. Verwenden Sie dies nach dem Löschen

session.clear ();


2

Dies ist mir auch passiert, weil ich meine ID als Long hatte und aus der Sicht den Wert 0 erhielt. Als ich versuchte, in der Datenbank zu speichern, bekam ich diesen Fehler, dann habe ich ihn behoben, indem ich die ID auf null gesetzt habe.


1

Ich bin auf dieses Problem gestoßen, als ich Transaktionen innerhalb der als "annotierten Methode" manuell gestartet und festgeschrieben habe @Transactional. Ich habe das Problem behoben, indem festgestellt wurde, ob bereits eine aktive Transaktion vorhanden war.

//Detect underlying transaction
if (session.getTransaction() != null && session.getTransaction().isActive()) {
    myTransaction = session.getTransaction();
    preExistingTransaction = true;
} else {
    myTransaction = session.beginTransaction();
}

Dann erlaubte ich Spring, die Transaktion festzuschreiben.

private void finishTransaction() {
    if (!preExistingTransaction) {
        try {
            tx.commit();
        } catch (HibernateException he) {
            if (tx != null) {
                tx.rollback();
            }
            log.error(he);
        } finally {
            if (newSessionOpened) {
                SessionFactoryUtils.closeSession(session);
                newSessionOpened = false;
                maxResults = 0;
            }
        }
    }
}

1

Dies geschieht, wenn Sie die JSF Managed Bean als deklariert haben

@RequestScoped;

wann sollten Sie als deklarieren

@SessionScoped;

Grüße;


1

Ich habe diesen Fehler erhalten, als ich versucht habe, ein Objekt mit einer ID zu aktualisieren, die in der Datenbank nicht vorhanden war. Der Grund für meinen Fehler war, dass ich der clientseitigen JSON-Darstellung des Objekts manuell eine Eigenschaft mit dem Namen 'id' zugewiesen hatte. Beim Deserialisieren des Objekts auf der Serverseite überschrieb diese 'id'-Eigenschaft dann die Instanzvariable ( auch 'id' genannt), die Hibernate generieren sollte. Achten Sie daher darauf, Kollisionen zu benennen, wenn Sie den Ruhezustand zum Generieren von Bezeichnern verwenden.


1

Ich bin auch auf die gleiche Herausforderung gestoßen. In meinem Fall habe ich ein Objekt, das noch nicht einmal vorhanden war, mit aktualisiert hibernateTemplate.

Tatsächlich habe ich in meiner Anwendung ein DB-Objekt zum Aktualisieren erhalten. Und während ich seine Werte aktualisierte, aktualisierte ich versehentlich auch seine ID und fuhr fort, sie zu aktualisieren und stieß auf diesen Fehler.

Ich benutze hibernateTemplatefür CRUD-Operationen.


1

Nachdem ich alle Antworten gelesen hatte, fand ich niemanden, der über das inverse Attribut des Ruhezustands sprach.

Meiner Meinung nach sollten Sie in Ihrer Beziehungszuordnung auch überprüfen, ob das inverse Schlüsselwort richtig eingestellt ist. Invers Schlüsselwort wird erstellt, um zu definieren, auf welcher Seite der Eigentümer die Beziehung aufrechterhält. Das Verfahren zum Aktualisieren und Einfügen variiert je nach Attribut.

Nehmen wir an, wir haben zwei Tabellen:

Principal_Table , Middle_Table

mit einer Beziehung von eins zu vielen . Die Hiberntate-Mapping-Klassen sind Principal und Middle .

Die Principal- Klasse verfügt also über einen SET von Middle- Objekten. Die XML-Zuordnungsdatei sollte wie folgt aussehen:

<hibernate-mapping>
    <class name="path.to.class.Principal" table="principal_table" ...>
    ...
    <set name="middleObjects" table="middle_table" inverse="true" fetch="select">
        <key>
            <column name="PRINCIPAL_ID" not-null="true" />
        </key>
        <one-to-many class="path.to.class.Middel" />
    </set>
    ...

Da die Umkehrung auf "wahr" gesetzt ist, bedeutet dies, dass "Mittelklasse" der Beziehungseigentümer ist, also wird die Hauptklasse dies tun NICHT AKTUALISIERT die Beziehung .

Das Verfahren zum Aktualisieren könnte also folgendermaßen implementiert werden:

session.beginTransaction();

Principal principal = new Principal();
principal.setSomething("1");
principal.setSomethingElse("2");


Middle middleObject = new Middle();
middleObject.setSomething("1");

middleObject.setPrincipal(principal);
principal.getMiddleObjects().add(middleObject);

session.saveOrUpdate(principal);
session.saveOrUpdate(middleObject); // NOTICE: you will need to save it manually

session.getTransaction().commit();

Dies hat bei mir funktioniert, aber Sie können einige Editionen vorschlagen, um die Lösung zu verbessern. Auf diese Weise werden wir alle lernen.


1

In unserem Fall haben wir endlich die Grundursache für StaleStateException herausgefunden.

Tatsächlich haben wir die Zeile zweimal in einer einzigen Sitzung im Ruhezustand gelöscht. Früher haben wir ojdbc6 lib verwendet, und das war in dieser Version in Ordnung.

Beim Upgrade auf odjc7 oder ojdbc8 löste das zweimalige Löschen von Datensätzen jedoch eine Ausnahme aus. Es gab einen Fehler in unserem Code, bei dem wir zweimal löschten, aber das war in ojdbc6 nicht ersichtlich.

Wir konnten mit diesem Code reproduzieren:

Detail detail = getDetail(Long.valueOf(1396451));
session.delete(detail);
session.flush();
session.delete(detail);
session.flush();

Beim ersten Flush geht der Ruhezustand und nimmt Änderungen in der Datenbank vor. Während des zweiten Ruhezustands vergleicht der Ruhezustand das Sitzungsobjekt mit dem Datensatz der tatsächlichen Tabelle, konnte jedoch keinen finden, daher die Ausnahme.


1

Dieses Problem tritt hauptsächlich auf, wenn wir versuchen, das Objekt zu speichern oder zu aktualisieren, das bereits von einer laufenden Sitzung in den Speicher abgerufen wurde. Wenn Sie ein Objekt aus der Sitzung abgerufen haben und versuchen, es in der Datenbank zu aktualisieren, wird diese Ausnahme möglicherweise ausgelöst.

Ich habe session.evict () verwendet. Um den im Ruhezustand gespeicherten Cache zuerst zu entfernen oder wenn Sie nicht das Risiko eingehen möchten, Daten zu verlieren, sollten Sie ein anderes Objekt zum Speichern der Datentemperatur erstellen.

     try
    {
        if(!session.isOpen())
        {
            session=EmployeyDao.getSessionFactory().openSession();
        }
            tx=session.beginTransaction();

        session.evict(e);
        session.saveOrUpdate(e);
        tx.commit();;
        EmployeyDao.shutDown(session);
    }
    catch(HibernateException exc)
    {
        exc.printStackTrace();
        tx.rollback();
    }

1

Problem mit Hibernate 5.4.1 und HHH-12878

Vor Hibernate 5.4.1 enthielten die optimistischen Ausnahmen für Sperrfehler (z. B. StaleStateExceptionoder OptimisticLockException) nicht die Fehleranweisung.

Das Problem HHH-12878 wurde erstellt, um den Ruhezustand zu verbessern, sodass beim Auslösen einer optimistischen Sperrausnahme auch die JDBC- PreparedStatementImplementierung protokolliert wird:

if ( expectedRowCount > rowCount ) {
    throw new StaleStateException(
            "Batch update returned unexpected row count from update ["
                    + batchPosition + "]; actual row count: " + rowCount
                    + "; expected: " + expectedRowCount + "; statement executed: "
                    + statement
    );
}

Testzeit

Ich habe das BatchingOptimisticLockingTestin meinem Hochleistungs-Java-Persistenz-GitHub-Repository erstellt, um zu demonstrieren, wie das neue Verhalten funktioniert.

Zunächst definieren wir eine PostEntität, die eine @VersionEigenschaft definiert , wodurch der implizite optimistische Sperrmechanismus aktiviert wird :

@Entity(name = "Post")
@Table(name = "post")
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    private String title;

    @Version
    private short version;

    public Long getId() {
        return id;
    }

    public Post setId(Long id) {
        this.id = id;
        return this;
    }

    public String getTitle() {
        return title;
    }

    public Post setTitle(String title) {
        this.title = title;
        return this;
    }

    public short getVersion() {
        return version;
    }
}

Wir werden die JDBC-Stapelverarbeitung mit den folgenden 3 Konfigurationseigenschaften aktivieren:

properties.put("hibernate.jdbc.batch_size", "5");
properties.put("hibernate.order_inserts", "true");
properties.put("hibernate.order_updates", "true");

Wir werden 3 PostEntitäten erstellen :

doInJPA(entityManager -> {
    for (int i = 1; i <= 3; i++) {
        entityManager.persist(
            new Post()
                .setTitle(String.format("Post no. %d", i))
        );
    }
});

Und Hibernate führt eine JDBC-Batch-Einfügung aus:

SELECT nextval ('hibernate_sequence')
SELECT nextval ('hibernate_sequence')
SELECT nextval ('hibernate_sequence')

Query: [
    INSERT INTO post (title, version, id) 
    VALUES (?, ?, ?)
], 
Params:[
    (Post no. 1, 0, 1), 
    (Post no. 2, 0, 2), 
    (Post no. 3, 0, 3)
]

Wir wissen also, dass JDBC-Batching einwandfrei funktioniert.

Lassen Sie uns nun das optimistische Problem beim Sperren wiederholen:

doInJPA(entityManager -> {
    List<Post> posts = entityManager.createQuery("""
        select p 
        from Post p
        """, Post.class)
    .getResultList();

    posts.forEach(
        post -> post.setTitle(
            post.getTitle() + " - 2nd edition"
        )
    );

    executeSync(
        () -> doInJPA(_entityManager -> {
            Post post = _entityManager.createQuery("""
                select p 
                from Post p
                order by p.id
                """, Post.class)
            .setMaxResults(1)
            .getSingleResult();

            post.setTitle(post.getTitle() + " - corrected");
        })
    );
});

Die erste Transaktion wählt alle PostEntitäten aus und ändert die titleEigenschaften.

Bevor der erste EntityManagergelöscht wird, führen wir jedoch einen zweiten Übergang mit der executeSyncMethode aus.

Die zweite Transaktion ändert die erste Post, sodass versionsie erhöht wird:

Query:[
    UPDATE 
        post 
    SET 
        title = ?, 
        version = ? 
    WHERE 
        id = ? AND 
        version = ?
], 
Params:[
    ('Post no. 1 - corrected', 1, 1, 0)
]

Wenn nun die erste Transaktion versucht, das zu leeren EntityManager, erhalten wir Folgendes OptimisticLockException:

Query:[
    UPDATE 
        post 
    SET 
        title = ?, 
        version = ? 
    WHERE 
        id = ? AND 
        version = ?
], 
Params:[
    ('Post no. 1 - 2nd edition', 1, 1, 0), 
    ('Post no. 2 - 2nd edition', 1, 2, 0), 
    ('Post no. 3 - 2nd edition', 1, 3, 0)
]

o.h.e.j.b.i.AbstractBatchImpl - HHH000010: On release of batch it still contained JDBC statements

o.h.e.j.b.i.BatchingBatch - HHH000315: Exception executing batch [
    org.hibernate.StaleStateException: 
    Batch update returned unexpected row count from update [0]; 
    actual row count: 0; 
    expected: 1; 
    statement executed: 
        PgPreparedStatement [
            update post set title='Post no. 3 - 2nd edition', version=1 where id=3 and version=0
        ]
], 
SQL: update post set title=?, version=? where id=? and version=?

Sie müssen also auf Hibernate 5.4.1 oder höher aktualisieren, um von dieser Verbesserung zu profitieren.


0

Dies ist passiert, wenn Sie etwas im Datensatz mithilfe einer nativen SQL-Abfrage ändern, das persistente Objekt für denselben Datensatz jedoch im Sitzungscache vorhanden ist. Verwenden Sie session.evict (yourObject).



0

Einer der Fälle

SessionFactory sf=new Configuration().configure().buildSessionFactory();
Session session=sf.openSession();

UserDetails user=new UserDetails();

session.beginTransaction();
user.setUserName("update user agian");
user.setUserId(12);
session.saveOrUpdate(user);
session.getTransaction().commit();
System.out.println("user::"+user.getUserName());

sf.close();

0

Ich war mit dieser Ausnahme konfrontiert und der Winterschlaf funktionierte gut. Ich habe versucht, einen Datensatz mit pgAdmin manuell einzufügen. Hier wurde das Problem klar. SQL Insert Query gibt 0 Insert zurück. und es gibt eine Triggerfunktion, die dieses Problem verursacht, weil sie null zurückgibt. also muss ich es nur einstellen, um neu zurückzugeben. und schließlich habe ich das Problem gelöst.

hoffe das hilft jedem körper.


0

Ich habe diesen Fehler erhalten, weil ich die IDSpalte fälschlicherweise mit zugeordnet habeId(x => x.Id, "id").GeneratedBy.**Assigned**();

Problem behoben mit Id(x => x.Id, "id").GeneratedBy.**Identity**();


0

Dies passiert mir, weil mir die ID-Deklaration in der Bean-Klasse fehlt.


0

In meinem Fall gab es ein Problem mit der Datenbank, da einer der gespeicherten Prozesse die gesamte CPU verbrauchte und hohe DB-Antwortzeiten verursachte. Sobald dies getötet wurde, wurde das Problem behoben.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.