0

I don't quite understand the following behaviour:

We take an Entity where we add a @Version field to use hibernate's Optimistic locking

@Entity
public class MyEntity {
    @Id
    @GeneratedValue
    public Long id;

    @Version
    public Long rowVersion;

    public String field;
}

We then create a @QuarkusTest and test if we can consecutively update and fetch the entity:

@QuarkusTest
public class RowVersionTest {

    @Inject
    EntityManager em;

    @Test
    public void testRowVersion(){
        MyEntity entity = createEntity();
        Long id = entity.id;

        for (int i=0; i < 10; i++){
            MyEntity entityById = getById(id);
            entityById.field += i;
            System.out.println("Before: " +entityById.field + " - RowVersion: " + entityById.rowVersion);
            update(entityById);
            System.out.println("After: " +entityById.field + " - RowVersion: " + entityById.rowVersion);
        }
    }

    @Transactional
    public MyEntity createEntity() {
        MyEntity newEntity = new MyEntity();
        newEntity.field = "Hello";
        em.persist(newEntity);
        return newEntity;
    }

    public MyEntity getById(Long id){
        TypedQuery<MyEntity> query = em.createQuery("Select e from MyEntity e where e.id = :id", MyEntity.class);
        query.setParameter("id", id);
        return query.getSingleResult();
    }

    @Transactional
    public void update(MyEntity entity){
        em.merge(entity);
    }
}

What I would expect to happen:

  1. We persist the entity and commit by setting the @Transactional boundary
  2. We fetch the entity from the database with the current rowversion
  3. We update (merge) the entity and change the field, thereby increase the rowversion in the database
  4. We repeat 2-3

What actually happens, is that we are only able to update the entity the first try. On the second try, we don't seem to get the latest rowVersion from the database

Before: Hello0 - RowVersion: 0
After: Hello0 - RowVersion: 0
Before: Hello01 - RowVersion: 0

jakarta.persistence.OptimisticLockException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [org.acme.MyEntity#1]

Why is this behaviour? Shouldn't we get the correct rowversion from the database every time, as we are running outside of Transaction boundary?

Full source: https://github.com/arnehehe/jpa-question/tree/main/code-with-quarkus

arnehehe
  • 1,386
  • 1
  • 17
  • 33
  • To be clear `@Transactional` won't commit the transaction. It creates a new transaction or joins to an existing one if it needed. In this case the transaction boundary is the injection boundary. Move service methods to an other CDI bean under the test folder and inject that service into test class. You will see the difference. – zforgo May 26 '23 at 10:41
  • I'm afraid I don't quite understand; if I define an @ApplicationScoped EntityService class, where I move the create, update and get methods to - with the same Transactional annotations on the update & persist method, the application still behaves the same. If I move the annotation to the entire service, everything works as expected, but I don't see why the fetch operation requires Transactional to work – arnehehe May 31 '23 at 07:41

0 Answers0