javaspring-bootspring-data-jpatransactionsoptimistic-locking

Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect):


I'm working on a Spring Boot application where I create an order using a createOrder method. Everytime, I encounter the following error:

OrderService

public class OrderService {

OrderRepository orderRepository;
ItemRepository itemRepository;
UserRepository userRepository;

private OrderView mapToOrderView(Order order) {
    return OrderView.builder().orderId(order.getOrderId())
            .userId(order.getUserId())
            .createdTime(order.getCreatedTime())
            .updatedTime(order.getUpdatedTime())
            .completedTime(order.getCompletedTime())
            .orderItems(order.getOrderItems())
            .deliveryMethod(order.getDeliveryMethod())
            .orderStatus(order.getOrderStatus())
            .additionalInstructions(order.getAdditionalInstructions())
            .rating(order.getRating())
            .feedback(order.getFeedback())
            .build();
}

@Transactional
public OrderView createOrder(PostOrderRequest request) {

    Order order = Order.builder()
            .orderId(UUID.randomUUID())
            .userId(request.getUserId())
            .createdTime(LocalDateTime.now())
            .deliveryMethod(request.getDeliveryMethod())
            .orderStatus(OrderStatus.PLACED)
            .build();

    List<OrderItem> orderItems = mapOrderItems(request.getOrderItems(), order.getOrderId());
    order.setOrderItems(orderItems);

    order.setUpdatedTime(LocalDateTime.now());
    orderRepository.save(order);
    return mapToOrderView(order);
}

private List<OrderItem> mapOrderItems(List<OrderItem> itemRequests, UUID orderId) {
    if (itemRequests == null || itemRequests.isEmpty()) {
        throw new IllegalArgumentException("Order must contain at least one item.");
    }

    List<OrderItem> orderItems = new ArrayList<>();
    for (OrderItem itemRequest : itemRequests) {
        Optional<Item> item = itemRepository.findById(itemRequest.getItemId());
        if (item.isEmpty()) {
            throw new IllegalArgumentException("Item not available: " + itemRequest.getItemId());
        }

        orderItems.add(OrderItem.builder()
                .orderItemId(UUID.randomUUID())
                .itemId(item.get().getItemId())
                .quantity(itemRequest.getQuantity())
                .build());

    }

    return orderItems;
}

Order.class

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private UUID orderId;
    private UUID userId;
    @Column(updatable = false, insertable = false)
    private LocalDateTime createdTime;
    private LocalDateTime updatedTime;
    private LocalDateTime completedTime;

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<OrderItem> orderItems;

    private DeliveryMethod deliveryMethod;
    private OrderStatus orderStatus;

    @OneToOne
    @JoinColumn(name = "payment_id")
    private Payment payment;
    private String additionalInstructions;
    private int rating;
    private String feedback;

    @Version
    private int version;

}
   

Error log

"org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect): [order.mgt.core.model.Order#be58d23c-010b-4f3c-9917-f9aa0df95c4f]The resource you are trying to update has been modified by another transaction.

Hibernate logs

    Hibernate: 
    select
        u1_0.user_id,
        u1_0.country,
        u1_0.county,
        u1_0.number,
        u1_0.post_code,
        u1_0.street_address,
        u1_0.street_address2,
        u1_0.town,
        u1_0.email,
        u1_0.phone_number,
        u1_0.created_date,
        u1_0.name 
    from
        user u1_0 
    where
        u1_0.user_id=?
Hibernate: 
    select
        i1_0.item_id,
        i1_0.category,
        i1_0.description,
        i1_0.image,
        i1_0.is_available,
        i1_0.name,
        i1_0.tag 
    from
        item i1_0 
    where
        i1_0.item_id=?
Hibernate: 
    select
        o1_0.order_id,
        o1_0.additional_instructions,
        o1_0.completed_time,
        o1_0.created_time,
        o1_0.delivery_method,
        o1_0.feedback,
        o1_0.order_status,
        p1_0.paymentid,
        p1_0.amount,
        p1_0.currency,
        p1_0.order_id,
        p1_0.status,
        p1_0.type,
        o1_0.rating,
        o1_0.updated_time,
        o1_0.user_id,
        o1_0.version,
        oi1_0.order_id,
        oi1_0.order_item_id,
        c1_0.customization_id,
        c1_0.customization_option,
        c1_0.price_change,
        i1_0.item_id,
        i1_0.category,
        i1_0.description,
        i1_0.image,
        i1_0.is_available,
        i1_0.name,
        i1_0.tag,
        oi1_0.item_id,
        oi1_0.portion_size,
        oi1_0.quantity 
    from
        orders o1_0 
    left join
        payment p1_0 
            on p1_0.paymentid=o1_0.payment_id 
    left join
        order_item oi1_0 
            on o1_0.order_id=oi1_0.order_id 
    left join
        customization c1_0 
            on c1_0.customization_id=oi1_0.customization_id 
    left join
        item i1_0 
            on i1_0.item_id=oi1_0.item_id2 
    where
        o1_0.order_id=?
Hibernate: 
    select
        o1_0.order_id,
        o1_0.additional_instructions,
        o1_0.completed_time,
        o1_0.created_time,
        o1_0.delivery_method,
        o1_0.feedback,
        o1_0.order_status,
        p1_0.paymentid,
        p1_0.amount,
        p1_0.currency,
        p1_0.order_id,
        p1_0.status,
        p1_0.type,
        o1_0.rating,
        o1_0.updated_time,
        o1_0.user_id,
        o1_0.version 
    from
        orders o1_0 
    left join
        payment p1_0 
            on p1_0.paymentid=o1_0.payment_id 
    where
        o1_0.order_id=?

Any help or suggestions to overcome this issue? I am working on a Spring Boot application that handles order creation and involves multiple database operations, such as fetching user and item details, and saving orders and related entities. I have optimistic locking in place using a @Version field in my entities to handle concurrent updates. The service method responsible for order creation is annotated with @Transactional to ensure all database operations happen within a single transaction


Solution

  • The error occurs because the @Id field (orderId) is annotated with @GeneratedValue(strategy = GenerationType.AUTO), which means Hibernate automatically generates the value for this field. However, in my service class, iam manually assigning a value to orderId using UUID.randomUUID(). This conflict leads to the exception

    Hibernate relies on the @GeneratedValue annotation to automatically manage the orderId field. When we assign a UUID manually, Hibernate does not recognize it as a managed entity. This results in inconsistencies in the persistence context.

    Additionally, manually setting the ID might create conflicts with Hibernate’s expectation of the orderId lifecycle, causing the optimistic locking failure.

    Removing orderId(UUID.randomUUID()) from order Builder works like a charm.