Search code examples
javasql-serverspringhibernatejpa

Hibernate forces foreign key to be non-null


I want a Product to have an optional ProductType. But during schema generation, Hibernate for some reason ignores my definition and creates a non-nullable column for a foreign-key. We are using Hibernate for schema management and SQL Server 16.

This is what Hibernate logs out during schema creation:

Hibernate: drop table if exists my_product
Hibernate: drop table if exists my_producttype
create table my_product (id uniqueidentifier not null, name varchar(255) not null, typeId uniqueidentifier not null, primary key (id))
create table my_producttype (id uniqueidentifier not null, name varchar(255) not null, primary key (id))
alter table pg_product add constraint FK123456789 foreign key (typeId) references my_producttype

This is the schema as defined in Java:

@Entity
@Table(name = "my_product")
public class Product extends CoreModel<Product> {

    @NotNull
    private String name;

    @ManyToOne(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY)
    @JoinColumn(name = "typeId", nullable = true)
    @JsonIgnore
    @org.springframework.lang.Nullable
    private ProductType productType;
}

and this abstract model class

@MappedSuperclass
public abstract class CoreModel<T extends CoreModel<T>> {

    @JsonProperty(access = JsonProperty.Access.READ_ONLY)
    @Column(updatable = false)
    @GeneratedValue(strategy = GenerationType.UUID)
    @Id
    private UUID id;
}

and finally the referenced entity

@Entity
@Table(name = "my_producttype")
public class ProductType extends CoreModel<ProductType> {


    @NotNull
    private String name;
}

the relevant application.yml

spring:
  datasource:
    driverClassName: com.microsoft.sqlserver.jdbc.SQLServerDriver
  jpa:
    database: sql_server
    open-in-view: false
    hibernate:
      ddl-auto: create
      naming:
        implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
    properties:
      hibernate:
        dialect: org.hibernate.dialect.SQLServerDialect
        jdbc:
          time_zone: UTC

What I tried:

System information

  • Spring 3.3.0
  • Hibernate 6.5.2
  • MS SQL Server 16.00.4125
  • mssql-jdbc 12.6.1

Solution

  • Found the culprit, and I have to apologize, since you couldn't have figured it out.

    The property productType is private and therefore has getters and setters, which I omitted. Crucially, during the refactor, I forgot to remove the @NotNull annotation there - and today I learned, that Spring/Hibernate introspects these as well during Schema generation, not just the field annotations.

    Fix was to change from this:

        public @NotNull ProductType getProductType() {
            return productType;
        }
    
        public void setProductType(@NotNull ProductType productType) {
            this.productType = productType;
        }
    

    to this

        public ProductType getProductType() {
            return productType;
        }
    
        public void setProductType(ProductType productType) {
            this.productType = productType;
        }
    

    Sorry about that, but thanks for being my rubber-duck