Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@NotNull does not add not null to persistence unit #46034

Closed
LarsSven opened this issue Feb 2, 2025 · 16 comments · Fixed by #46264
Closed

@NotNull does not add not null to persistence unit #46034

LarsSven opened this issue Feb 2, 2025 · 16 comments · Fixed by #46264
Assignees
Labels
area/hibernate-orm Hibernate ORM kind/bug Something isn't working
Milestone

Comments

@LarsSven
Copy link
Contributor

LarsSven commented Feb 2, 2025

Describe the bug

With Hibernate ORM, we have an entity that has a @NotNull annotation. According to documentation, Hibernate should pick this up and add not null to the column in the Persistent Unit. However, when you do that on Quarkus 3.18.1, not null is not added to the persistence unit.

This used to be properly applied, until at some point we discovered after a few Quarkus updates that this suddenly no longer happens, so I am not sure in which version this stopped working.

I made a small reproducer at https://gitlab.com/l.s.andringa1/quarkus-hibernate-non-null-reproducer

The project is in Kotlin since that is what we are used to. I am not sure whether it only happens on Kotlin or also on Java. I tried to put both kotlin and java entities in the same project to test, but it was not added to the same persistence unit.

Expected behavior

When @NotNull is added to an entity, as follows:

@Entity
class MyKotlinEntity {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    var id: Long? = null

    @NotNull
    var field: String = ""
}

The persistence unit (for MariaDB in this case) should look like:

create table MyKotlinEntity (
    id bigint not null auto_increment,
    field varchar(255) not null,
    primary key (id)
) engine=InnoDB;

Actual behavior

When @NotNull is added to an entity, as follows:

@Entity
class MyKotlinEntity {
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Id
    var id: Long? = null

    @NotNull
    var field: String = ""
}

The persistence unit looks as follows:

create table MyKotlinEntity (
    id bigint not null auto_increment,
    field varchar(255),
    primary key (id)
) engine=InnoDB;

How to Reproduce?

Reproducer:

  1. Clone https://gitlab.com/l.s.andringa1/quarkus-hibernate-non-null-reproducer
  2. Open Quarkus dev dashboard and look at Hibernate ORM persistence unit
  3. Observe the Create Script, which is missing the not null annotation for the field

Output of uname -a or ver

Windows 11 (Also tested on Ubuntu 24.10)

Output of java -version

OpenJDK Runtime Environment Corretto-21.0.5.11.1

Quarkus version or git rev

3.18.1

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

Gradle 8.12.1

Additional information

No response

@LarsSven LarsSven added the kind/bug Something isn't working label Feb 2, 2025
@quarkus-bot quarkus-bot bot added area/kotlin env/windows Impacts Windows machines labels Feb 2, 2025
Copy link

quarkus-bot bot commented Feb 2, 2025

/cc @geoand (kotlin)

@gsmet gsmet added the area/hibernate-orm Hibernate ORM label Feb 2, 2025
@yrodiere
Copy link
Member

yrodiere commented Feb 3, 2025

Hello,

Thanks for reporting, and for the reproducer.

It does seem there is a problem with @NotNull not being reflected in generated schema.

I checked that it's not related to:

  • Kotlin. I tried to replace your Kotlin entity with a Java one in your reproducer and got the same behavior.
  • Our support of public fields. Switching to private fields with manually defined getters/setters yields the same behavior.

For the person doing further investigation (maybe me, maybe not):

  1. The code in Hibernate ORM is here: https://github.com/hibernate/hibernate-orm/blob/9a4d21d71d2620a0093543d84e8f464107e4d2f4/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/TypeSafeActivator.java#L317-L345
  2. That code gets triggered by an integrator here: https://github.com/hibernate/hibernate-orm/blob/3769d4c2339c0c7ad575fc156d56bd8d87b9ef30/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/BeanValidationIntegrator.java#L88

It's just a guess, but I suspect that, in Quarkus, that integrator gets triggered too late. Dev UI code relies on Metadata only, in particular, so it may generate scripts before integrators get... integrated.

@yrodiere yrodiere removed area/kotlin env/windows Impacts Windows machines labels Feb 3, 2025
@LarsSven
Copy link
Contributor Author

LarsSven commented Feb 3, 2025

@yrodiere does that mean that even though the not null may not properly be reflected in the dev UI (which is of course still a problem, especially since we use it to populate flyway), Hibernate still adds not null to it in the actual database scheme?

Also for the env/windows, I also tested it on Ubuntu and had similar issues there.

@yrodiere
Copy link
Member

yrodiere commented Feb 3, 2025

@yrodiere does that mean that even though the not null may not properly be reflected in the dev UI (which is of course still a problem, especially since we use it to populate flyway), Hibernate still adds not null to it in the actual database scheme?

I didn't check. That's possible. Though if I had to bet, I'd rather go for the option "it doesn't work either way, but for different reasons" 😅

Also for the env/windows, I also tested it on Ubuntu and had similar issues there.

Yep, I use Linux as well, that's why I removed env/windows. It was added by a bot because your initial report mentioned Windows.

@LarsSven
Copy link
Contributor Author

@yrodiere what is the status of this issue? Is there still a plan to look into this issue? Since it is quite annoying for our team.

@yrodiere
Copy link
Member

yrodiere commented Feb 11, 2025

It's in the backlog, someone will look into it when they have time. Not the only issue to work on unfortunately.

If your team want to speed things up, both Quarkus and Hibernate ORM gladly welcome contributions -- just make sure to tell us you're working on this.

@marko-bekhta
Copy link
Contributor

hey,
I think this behaves as expected. For validation constraints to get reflected in the generated schema the jakarta.persistence.validation.mode has to be set to DDL (https://docs.jboss.org/hibernate/orm/6.6/javadocs/org/hibernate/boot/beanvalidation/ValidationMode.html#DDL) but since in Quarkus we have the boolean flag (https://quarkus.io/guides/hibernate-orm#quarkus-hibernate-orm_quarkus-hibernate-orm-validation-enabled) to enable the Validatior integration with ORM we only allow users to pick CALLBACK or NONE values.

// Hibernate Validator integration: we force the callback mode to have bootstrap errors reported rather than validation ignored
// if there is any issue when bootstrapping Hibernate Validator.
if (capabilities.isPresent(Capability.HIBERNATE_VALIDATOR)) {
if (persistenceUnitConfig.validation().enabled()) {
descriptor.getProperties().setProperty(AvailableSettings.JAKARTA_VALIDATION_MODE,
ValidationMode.CALLBACK.name());
} else {
descriptor.getProperties().setProperty(AvailableSettings.JAKARTA_VALIDATION_MODE, ValidationMode.NONE.name());
}
}

I'd suggest using Flyway/Liquibase for production-kind applications to manage the schema.

@yrodiere
Copy link
Member

I'd suggest using Flyway/Liquibase for production-kind applications to manage the schema.

True, but we do allow Hibernate ORM to generate the first iteration of the schema and use it as a Flyway script, and we do allow validating the (Flyway-managed) schema with Hibernate ORM, both of which are reasonable and would be affected by this behavior.

I think this behaves as expected

Given your explanation, it makes sense... but the report mentions this is a regression compared to previous versions... ? Something changed in this area, didn't it? Was DDL the default previously, and we changed it to CALLBACK by accident? Should we consider allowing to pick DDL explicitly, or using it by default?

@gsmet
Copy link
Member

gsmet commented Feb 13, 2025

I think it was always set to callback. Do we have an option that both provides the DDL and allow HV integration?

@LarsSven
Copy link
Contributor Author

True, but we do allow Hibernate ORM to generate the first iteration of the schema and use it as a Flyway script, and we do allow validating the (Flyway-managed) schema with Hibernate ORM, both of which are reasonable and would be affected by this behavior.

This is a really important point. Also for us, we use the dev UI persistence unit's create script to upgrade our initial flyway-managed scheme, so if our scheme has to deviate from what Hibernate generates, it becomes extremely cumbersome to update the initial script for products that have not been released yet (and thus we can still update the initial migration).

@LarsSven
Copy link
Contributor Author

LarsSven commented Feb 13, 2025

Given your explanation, it makes sense... but the report mentions this is a regression compared to previous versions... ? Something changed in this area, didn't it? Was DDL the default previously, and we changed it to CALLBACK by accident? Should we consider allowing to pick DDL explicitly, or using it by default?

So about this, I looked a bit further into it and it seems that it happened when we switched from Hibernate reactive to Hibernate ORM. I'm not necessarily sure something changed in previous versions anymore, I think it's simply that the not null works for Hibernate reactive, but not for Hibernate ORM. I have no clue why this would have worked for reactive then, maybe someone has an idea about that?

@marko-bekhta
Copy link
Contributor

I think it was always set to callback. Do we have an option that both provides the DDL and allow HV integration?

from the ORM side, from what I see both DDL and AUTO would apply the constraint to the schema, but what seems more applicable for this case:

https://github.com/hibernate/hibernate-orm/blob/cab048b8275c5bc527a8a35d79f82498f68e6d5a/hibernate-core/src/main/java/org/hibernate/boot/beanvalidation/ValidationMode.java#L36-L51

Hibernate ORM allows passing multiple values here, so maybe let's just pass CALLBACK,DDL when the validation is enabled (quarkus.hibernate-orm."persistence-unit-name".validation.enabled=true)?

@yrodiere
Copy link
Member

yrodiere commented Feb 13, 2025

Hibernate ORM allows passing multiple values here, so maybe let's just pass CALLBACK,DDL when the validation is enabled (quarkus.hibernate-orm."persistence-unit-name".validation.enabled=true)?

I think the DDL part should be configurable, because it's not uncommon for apps to have "incorrect" existing data (does't comply with not null in the DB), but to require any new edits to comply with the @NotNull annotation -- progressively cleaning up the data as users update it. These use cases require CALLBACK without DDL.

So, +1 to allow adding DDL, and even to enable it by default if you want (and if it's documented in the migration guide), but I think we need a way to opt out, at least. Do you concur @gsmet @marko-bekhta?

Also we'd need to see what's different in Hibernate Reactive...

@gsmet
Copy link
Member

gsmet commented Feb 13, 2025

I think we should have a configuration property.

And I would default to auto. I think I made the error of using callback because it's the only way to actually know if the BV integration is properly set up. But in the context of Quarkus, we are positive the integration is correct.

We could deprecate the existing property, make it optional and disable BV integration if set to false.

@LarsSven
Copy link
Contributor Author

@gsmet for my understanding (and maybe for a bit of context if someone reads this issue): What does BV/HV integration mean?

@dreab8 dreab8 self-assigned this Feb 13, 2025
@gsmet
Copy link
Member

gsmet commented Feb 13, 2025

Ah yes sorry, It's Bean Validation and Hibernate Validator (a Bean Validation implementation).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area/hibernate-orm Hibernate ORM kind/bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants