Home > Enterprise >  How to trigger org.springframework.orm.ObjectOptimisticLockingFailureException in tests?
How to trigger org.springframework.orm.ObjectOptimisticLockingFailureException in tests?

Time:10-06

I have following error in code, when I save objects in 2 different transactions:

org.springframework.orm.ObjectOptimisticLockingFailureException: Object of class [] with identifier []: optimistic locking failed; nested exception is org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : []

I have been trying to write test for the exception. However, all tests have been passed.

My test:

@Slf4j
@Rollback
@Transactional
@DataJpaTest
@ActiveProfiles({"test", "h2"})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@Import(value = {
        StatusService.class
})
class StatusServiceTest {

    @Autowired
    private RequestRepository repository;
    @Autowired
    private StatusService statusService;

    private Request request;
    private final UUID requestUID = UUID.randomUUID();

    @BeforeEach
    void setUp() {
        request = new Request();
        request.setRequestUID(requestUID);
        repository.save(request);
    }

    @Test
    void updateStatus() {
        Request request1 = repository.findByRequestUID(requestUID);
        Request request2 = repository.findByRequestUID(requestUID);
        statusService.updateStatus(request1, "Done");
        statusService.updateStatus(request2, "In_progress");
    }
}

Service:

@Service
@Slf4j
@RequiredArgsConstructor
public class StatusService {
    private final RequestRepository repository;

    @Transactional
    public void updateStatus(Request request, String status) {
        // Some simple logic
        request.setStatus(status);
        repository.save(request); // The line with error
    }
}

Model:

@Entity
@Getter
@Setter
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Request {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @JsonIgnore
    protected Long id;

    protected UUID requestUID;

    private String status;
    
    // Some additional fields
    
    @JsonIgnore
    @Version
    private Integer version;

}

Where am I wrong? How to write test for triggering error?

CodePudding user response:

@DataJpaTest includes @Transactional so your test runs in a single transaction.

But for a single transaction the 1st level cache of JPA ensures that you'll get always the same instance for a given class and id. This means request1 and request2 are actually the same instance and there happens only a single flush will can't create an optimistic locking exception.

There are two approaches to create an optimistic locking exception.

  1. Actually use two or more transactions. TransactionTemplate is very useful for this. You could put the loading of the request1 in a first transaction then load, modify, save, and flush request2 and then modify, save, and flush request1. If you want to have it even closer to what is happen in production you could start two threads doing those operations and by using CountDownLatch make sure they proceed in the desired order.

  2. Alternatively you should be able to trigger the desired exception by loading a request, modifying its version attribute and flush the transaction. This of course is rather far from what happens in production but as said it should trigger the exception.

  • Related