Hibernate @JoinColumnsOrFormulas throws org.hibernate.AssertionFailure: value involves formulas Exception

I am trying to use Multi Join Columns for a @ManyToOne relationship. My use case is exactly the same as mentioned in this StackOverflow answer - java - Hibernate composite key and overlapping field - how to avoid column duplication - Stack Overflow.

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumnsOrFormulas(
        value = {
                @JoinColumnOrFormula(column = @JoinColumn(referencedColumnName = "id", name = "author_id")),
                @JoinColumnOrFormula(formula = @JoinFormula(referencedColumnName = "tenant_id", value = "tenant_id"))
        })
private Entity entity;

I followed the same approach of using @JoinColumnsOrFormulas annotation, and it throws this exception during application startup, causing the Spring Boot application not to run -

org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘entityManagerFactory’ defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: value involves formulas

Caused by: org.hibernate.AssertionFailure: value involves formulas
at org.hibernate.mapping.SimpleValue.getColumns(SimpleValue.java:282)
at org.hibernate.boot.model.internal.TableBinder.mappedByColumns(TableBinder.java:773)
at org.hibernate.boot.model.internal.TableBinder.bindUnownedAssociation(TableBinder.java:755)
at org.hibernate.boot.model.internal.TableBinder.bindForeignKey(TableBinder.java:546)
at org.hibernate.boot.model.internal.CollectionBinder.bindCollectionSecondPass(CollectionBinder.java:2587)
at org.hibernate.boot.model.internal.CollectionBinder.bindOneToManySecondPass(CollectionBinder.java:1642)
at org.hibernate.boot.model.internal.CollectionBinder.bindStarToManySecondPass(CollectionBinder.java:1545)
at org.hibernate.boot.model.internal.CollectionBinder$1.secondPass(CollectionBinder.java:1534)
at org.hibernate.boot.model.internal.CollectionSecondPass.doSecondPass(CollectionSecondPass.java:45)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1857)
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1814)
at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:328)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1380)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1451)
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75)

Observed this error while using Hibernate v6.2.6.Final. Also tried downgrading to v6.0.0.CR1 and still seeing the same error.

Any help here would be really appreciated. Thanks in advance.

1 Like

You don’t need @JoinColumnOrFormula. Just use this mapping instead:

    @IdClass(TenantAwareKey.class)
    @Entity
    @Table(name = "BOOK")
    @Data
    public class Book {

        @Id
        @GeneratedValue
        @Column(name = "ID")
        private UUID id;
        @Id
        @Column(name = "TENANT_ID")
        private Integer tenantId;

        private String title;

        @ManyToOne
        @JoinColumns(
            value = {
                @JoinColumn(referencedColumnName = "id", name = "author_id", insertable = false, updatable = false),
                @JoinColumn(referencedColumnName = "tenant_id", name = "tenant_id", insertable = false, updatable = false)
            })
        private Author author;
        @Column(name = "author_id")
        private UUID authorId;
    }

And sync the authorId field whenever you set a author. The other option you have is to create a separate column autor_tenant_id and map it like this:

    @IdClass(TenantAwareKey.class)
    @Entity
    @Table(name = "BOOK")
    @Data
    public class Book {

        @Id
        @GeneratedValue
        @Column(name = "ID")
        private UUID id;
        @Id
        @Column(name = "TENANT_ID")
        private Integer tenantId;

        private String title;

        @ManyToOne
        @JoinColumns(
            value = {
                @JoinColumn(referencedColumnName = "id", name = "author_id"),
                @JoinColumn(referencedColumnName = "tenant_id", name = "author_tenant_id")
            })
        private Author author;
    }
1 Like

Thanks @beikov for the response. The first approach is what we have proceeded with, and it works pretty well.

If @TenantId supports adding the tenant filter for lazy fetching and joins, then there would essentially be no need to do these workarounds. Are there any plans to include this feature with @TenantId annotation?

The lazy fetching part is related to this existing feature request which you can track: [HHH-16830] - Hibernate JIRA
Tenant filters for HQL/Criteria joins should be applied though. If that doesn’t happen, that would be a bug IMO.

1 Like