Vlad
Vlad

Reputation: 21

Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.mysqlplanefinder.models.Aircraft#34]

I'm reading a book "spring-boot up and running". And I have the following problem, I don't understand how to solve it.

org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [com.example.mysqlplanefinder.models.Aircraft#34]

Aircraft.java

package com.example.mysqlplanefinder.models;

import com.fasterxml.jackson.annotation.JsonProperty;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import java.time.Instant;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Aircraft {
    @Id
    @GeneratedValue
    private Long id;

    private String callsign, squawk, reg, flightno, route, type, category;

    private int altitude, heading, speed;
    @JsonProperty("vert_rate")
    private int vertRate;
    @JsonProperty("selected_altitude")
    private int selectedAltitude;

    private double lat, lon, barometer;
    @JsonProperty("polar_distance")
    private double polarDistance;
    @JsonProperty("polar_bearing")
    private double polarBearing;

    @JsonProperty("is_adsb")
    private boolean isADSB;
    @JsonProperty("is_on_ground")
    private boolean isOnGround;

    @JsonProperty("last_seen_time")
    private Instant lastSeenTime;
    @JsonProperty("pos_update_time")
    private Instant posUpdateTime;
    @JsonProperty("bds40_seen_time")
    private Instant bds40SeenTime;
}

AircraftRepository.java

package com.example.mysqlplanefinder.repositories;

import com.example.mysqlplanefinder.models.Aircraft;
import org.springframework.data.repository.CrudRepository;

public interface AircraftRepository extends CrudRepository<Aircraft, Long> { }

PlanePoller.java

package com.example.mysqlplanefinder;

import com.example.mysqlplanefinder.models.Aircraft;
import com.example.mysqlplanefinder.repositories.AircraftRepository;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.client.WebClient;

@EnableScheduling
@Component
@RequiredArgsConstructor
class PlanePoller {
    @NonNull
    private final AircraftRepository repository;
    private WebClient client =
            WebClient.create("http://localhost:7634/aircraft");

    @Scheduled(fixedRate = 1000)
    private void pollPlanes() {
        repository.deleteAll();

        client.get()
                .retrieve()
                .bodyToFlux(Aircraft.class)
                .filter(plane -> !plane.getReg().isEmpty())
                .toStream()
                .forEach(repository::save);

        repository.findAll().forEach(System.out::println);
    }
}

I've tried to delete method .deleteAll() and add Versions how was recommended Ai, but nothing helped.

Upvotes: 1

Views: 278

Answers (2)

Andy Zhang
Andy Zhang

Reputation: 314

You are creating an entity that appears with a given ID (fetched from the web), but the field is annotated with @GeneratedValue. However, starting from Hibernate 6.6 (the version used since Spring Boot 3.4.0), this is no longer allowed.

If the id field is provided, Hibernate assumes the entity already exists in the database. If no matching row is found, Hibernate interprets this as the entity having been deleted by another transaction, resulting in an OptimisticLockException.

Since your id field is configured as a generated value, it must remain unset for new entities to allow Hibernate to correctly handle its generation. To prevent errors, ensure the id field is reset to null before saving a new entity.

See also: https://docs.jboss.org/hibernate/orm/6.6/migration-guide/migration-guide.html#merge-versioned-deleted

Previously, merging a detached entity resulted in a SQL insert whenever there was no matching row in the database (for example, if the object had been deleted in another transaction). This behavior was unexpected and violated the rules of optimistic locking.

An OptimisticLockException is now thrown when it is possible to determine that an entity is definitely detached, but there is no matching row. For this determination to be possible, the entity must have either:

a generated @Id field, or

a non-primitive @Version field.

For entities which have neither, it’s impossible to distinguish a new instance from a deleted detached instance, and there is no change from the previous behavior.

Upvotes: 1

Vlad
Vlad

Reputation: 21

The solution turned out to be too simple. What happened: The author of the book in the code in the Aircraft model made the id field as autogenerated (in my case, this was the problem, perhaps it was supposed to be like this, but I literally copied it from the book). After studying the forum a little, I came across a similar situation, where a person wrote that if the id comes from the other side, then a conflict of this type occurs. In particular, since my method sends a request to an external API every second, then the same object of my plane will be in different threads, and since hibernate "marks" what needs to be done with this object, it gives this ill-fated error. My solution was to add a field with the @Version annotation and remove @GeneratedValue from id .

Aircraft.java

@Id
private Long id;

@Version
private Long version;

Upvotes: 1

Related Questions