quarkusio / quarkus

Quarkus: Supersonic Subatomic Java.
https://quarkus.io
Apache License 2.0
13.82k stars 2.69k forks source link

Hibernate Reactive persist does nothing and does not fail #35704

Closed mklueh closed 1 year ago

mklueh commented 1 year ago

Describe the bug

I'm facing an odd behavior trying to persist some entities using Hibernate Reactive.

The persist call works without errors and the primary key is attached to the entity, but nothing is persisted to the database and there is no error at all.

Expected behavior

To either fail if something is wrong or store the entity accordingly, but not silently do nothing.

Actual behavior

These are the logs I'm getting for

2023-09-04 09:43:20,237 INFO  [com.lau.aut.dev.DevAuthApi] (vert.x-eventloop-thread-2) now storing User
2023-09-04 09:43:20,243 INFO  [io.qua.mut.run.MutinyInfrastructure] (vert.x-eventloop-thread-2) user.0 | onSubscribe()
Hibernate:
    select
        nextval('myschema.User_SEQ')
2023-09-04 09:43:20,256 INFO  [io.qua.mut.run.MutinyInfrastructure] (vert.x-eventloop-thread-2) user.0 | onItem(User(super=BaseUser(super=BaseUUIDEntity(super=BaseEntity(id=51, createdAt=null, updatedAt=null), uuid=28378386-
8d83-41bb-a6d3-c09a7850c581),  name=Dev, email=dev@dev.com, birthdate=null, role=null, password=null, verified=false, address=null,  additionalProperties=null)))
2023-09-04 09:43:20,258 INFO  [com.lau.aut.dev.DevAuthApi] (vert.x-eventloop-thread-2) User newly created
Hibernate: 
    select
        nextval('myschema.file_SEQ')

This related code

    private <T extends BaseEntity> Function<? super T, Uni<?>> storeUserInDatabase() {
        return user -> {
            log.info("now storing User");
            return repository.persist((User) user)
                      .log("user")
                      .onItem().ifNotNull().invoke(() -> log.info("User newly created"));
        };
    }

The id is assigned by the sequence generator


    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    @GenericGenerator(name = "sequenceIdGenerator",
            type = SequenceStyleGenerator.class,
            parameters = @Parameter(name = SequenceStyleGenerator.
                    CONFIG_SEQUENCE_PER_ENTITY_SUFFIX, value = "true"))
    public Long id;

and the persist call does not persist anything

How to Reproduce?

Not sure if this is related to any config properties, entity setup, reactive dependencies or anything else.

Output of uname -a or ver

No response

Output of java -version

No response

GraalVM version (if different from Java)

No response

Quarkus version or git rev

3.3.1

Build tool (ie. output of mvnw --version or gradlew --version)

8.3

Additional information

No response

quarkus-bot[bot] commented 1 year ago

/cc @DavideD (hibernate-reactive), @Sanne (hibernate-reactive), @gavinking (hibernate-reactive)

geoand commented 1 year ago

The most elementary question is whether or not the Uni is being subscribed to, because as I'm sure you aware, Uni is lazy, so if not subscription exists, nothing happens.

Sanne commented 1 year ago

Remember a "persist" is just making the object's state to become persisted. The actual insert operation on the DB happens either when the session is flushed or when the transaction is committed.

mklueh commented 1 year ago

@geoand thanks. I guess it is subscribed as expected, otherwise, I'd assume there were none of the log outputs either that are part of the stream.

@Sanne I'm just using the reactive PanacheRepository and then just call "persist", with no additional transactions. I'm trying to use persistAndFlush later, in case this makes a difference, but I see no point why it won't get flushed right away or why the transaction would stay open forever even after the REST response has happened. Am I missing something?

Forgot to mention, it's by the way not the only method in my code where the persist is not working.

geoand commented 1 year ago

I guess it is subscribed as expected, otherwise, I'd assume there were none of the log outputs either that are part of the stream.

That is correct. I had not seem the specific line in the logs, hence my question

Sanne commented 1 year ago

@mklueh transaction boundaries need to be controlled explicitly.

See:

mklueh commented 1 year ago

@Sanne thanks, will try that later and keep you up to date👍

Wouldn't calling persist with no explicit transaction be a valid error case that should throw an exception or is there any use case to it?

Is this behavior related to Quarkus Reactive only? In Spring for example I don't need a transaction at all to persist via repo.

Sanne commented 1 year ago

It's a good question; I believe that if you were tro try it with the non-reactive Hibernate ORM you'd get an exception, reminding of the transactional requirement; however in that case we bind to the regular Transaction Manager, which isn't an option for Hibernate Reactive as the TMX APIs are not reactive-friendly: so patterns might diverge a little bit as XA transactions (transactions on multiple resources) aren't available to Hibernate Reactive - this also implies the necessity of sometimes needing to run an operation outside of a transaction scope.

There very rarely might be a use case for doing a non-transactional operation; we might want to enforce the need for a transaction also on Hibernate Reactive, but before doing that we might need some more experience with it - it's still a relatively new technology and "best practices" aren't fully defined yet.

geoand commented 1 year ago

There very rarely might be a use case for doing a non-transactional operation

One idea would be to have a blog post explaining our thinking. It would come in handy in a lot of cases where we could point folks to it

Sanne commented 1 year ago

There very rarely might be a use case for doing a non-transactional operation

One idea would be to have a blog post explaining our thinking. It would come in handy in a lot of cases where we could point folks to it

I agree, but I'd rather let power-users explore and define best practices .. it's not like I had much experience with it myself yet. We could enforce the transactions - that would align it with the blocking ORM semantics, and in case I'm missing something that would trigger power users to come forward with their use cases.

geoand commented 1 year ago

but I'd rather let power-users explore and define best practices

What about non-power users though that look to us for suggestions?

Sanne commented 1 year ago

but I'd rather let power-users explore and define best practices

What about non-power users though that look to us for suggestions?

Always use transactions, like we do in all examples.

geoand commented 1 year ago

Now that's a good blog post title :)

mklueh commented 1 year ago

Works with transactions. Thanks for helping me out.

A blog post would be nice, but a short log message hint would have been even nicer in this regard, esp when it's different from the expected behavior :)