"read commited" behaviour in app level transaction

I have a simple KV store implemented in a DB and I have a following code running inside a (outer) transaction (in my case it’s Spring’s Transactional but the concrete way doesn’t seem to matter):

in_new_txn { kvService.save(KeyValue("key", 1)) }
val res1 = repo.read("key").value

in_new_txn { kvService.save(KeyValue("key", 2)) }
val res2 = repo.read("key").value

The key is a PK (@Id) of the KeyValue entity so one would expect a following “logical” sequence of DB queries:

BEGIN
BEGIN
select * from kv where key = "key"
insert into kv values ("key", 1)
COMMIT
select * from kv where key = "key"
BEGIN
select * from kv where key = "key"
update kv set value=2 where key="key"
COMMIT
select * from kv where key = "key"
COMMIT

Everything happens as expected, except the last select is for some reason ommitted, therefore res2 is 1 instead of 2.

Is this expected? For me not, i.e. I would expect a similar behaviour akin to the DB “read commited” transaction isolation level. That is the outer transaction sees all changes commited by other transactions meanwhile.

If it is expected how I can achieve the intended behaviour?

Assuming that your last call to repo.read("key") accesses the same EntityManager as the first one, you will observe that the entity is part of the persistence context, so a subsequent read of the entity will result in returning the same entity from the persistence context again. If you want to refresh the data of your managed entity, you will have to call EntityManager.refresh.

Ok. So it can be said that Hibernate’s “isolation level” is similar to repeatable read?

With respect to managed entities, yes, though the refresh operation allows you to also read committed state.