Parent/Child ManyToOne query causing internal Hibernate exception when migrating to 6.x

Hi everyone. First timer here.

We have some ManyToOne parent/child associations in our data model. The objects all derive from a base component type, and each ultimate type is identified by a discriminator. Whenever there is a parent and a child, we tie them together with an association. E.g.:

public class Association {
  private Component parent;
  private Component child;
}

The query to retrieve some of these parent/child relationships is fairly complex, but Hibernate has handled it fine for the last 10+ years. The objects returned from a query are fully initialized, and the parent and child proxies are of the correct type.

We are now in the process of upgrading our technology stack so that we are using Hibernate 6.1.18 (upgrading from 5.6.15). In so doing, we have found that Hibernate no longer seems to be happy with the object model or query. The query generated by Hibernate does work; The query can be extracted from the log and pasted into SSMS and the query returns the expected result set. But an exception is thrown from the bowels of Hibernate when Hibernate attempts to run the query at runtime.

I’ll post several topics below:

  • A small excerpt from the stack trace.
  • A simplified class structure using simplified names to give an idea of how the model is set up.
  • Some general information on why a particular inheritance strategy was used.

I am looking for some guidance on how we should go about dealing with this issue.

  • Are ManyToOne parent/child associations still supported by Hibernate 6?
  • Should I expect to have to rework the query, despite the fact that the query itself still works?
  • Should I expect to have to rework our object model? Or the InheritanceType?

Any and all feedback would be greatly appreciated. I’ve spent many hours in Googleland but I can’t seem to get anywhere.

Previously, our code did this:

    NativeQuery<Object[ ]> sqlQuery = session.createNativeQuery(complexSqlWithUnionsAndJoinsAndRecursiveCTEs);
    sqlQuery.addEntity("entry", Association.class);
    sqlQuery.addJoin(joinAlias, "entry.component");
    ...
    List<Object[ ]> result = sqlQuery.getResultList(); <-- exception here

Stack trace excerpt:

Caused by: java.lang.NullPointerException: Cannot invoke "org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping.getForeignKeyDescriptor()" because "toOneAttributeMapping" is null
at org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy.buildFetch(DynamicFetchBuilderLegacy.java:167)
at org.hibernate.query.results.DomainResultCreationStateImpl.lambda$createFetchableConsumer$3(DomainResultCreationStateImpl.java:543)
at org.hibernate.metamodel.mapping.internal.ImmutableAttributeMappingList.forEach(ImmutableAttributeMappingList.java:44)
at org.hibernate.persister.entity.AbstractEntityPersister.visitFetchables(AbstractEntityPersister.java:6364)

Simplified example class structure follows. Note the InheritanceType of SINGLE_TABLE.

The object model and table structure was designed over 10 years ago by folks that are no longer with the company. The authors claimed that though an object model like this would seem to have lent itself to an InheritanceType of JOINED, they claimed that, at the time, Hibernate did not support InheritanceType.JOINED because of the existence of the discriminator. Nor did Hibernate write the discriminator value to the database. So instead, SINGLE_TABLE was used. This had a number of side effects. One being that sub classes with their own table need to specify the table name in every @Column annotation.

Whether the aforementioned limitations of InheritenceType.JOINED remain true today, I do not know. But since the model and queries continued to work all these years it was left as-is.

Here are some code excerpts. Some names changed for brevity and clarity.

This is the base class for all components:

@Entity
@Table(name = "COMPONENT")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "COMPONENT_TYPE", discriminatorType = DiscriminatorType.INTEGER)
public class Component implements Serializable {

  @Column(name = "COMPONENT_TYPE", columnDefinition = "INTEGER", insertable = false, updatable = false)
  private Integer componentType;

  @Column(name = "NAME", columnDefinition = "CHAR")
  @Size(max = 50)
  private String name;

  @Column(name = "DESCRIPTION", columnDefinition = "CHAR")
  @Size(max = 50)
  private String description;

  other fields...
}

A Menu is an ultimate type. It might be a parent (a top-level menu) or a child (a sub-menu).

@Entity
@DiscriminatorValue(Menu.DISCRIMINATOR_VALUE_STRING)
public class Menu extends Component {
  static final String DISCRIMINATOR_VALUE_STRING = "0";
  public static final int DISCRIMINATOR_VALUE = Integer.valueOf(DISCRIMINATOR_VALUE_STRING);

  other fields...
}

A report is also an ultimate type.

@Entity
@DiscriminatorValue(Report.DISCRIMINATOR_VALUE_STRING)
@SecondaryTable(name = "REPORT", pkJoinColumns = @PrimaryKeyJoinColumn(name = "REPORT_ID"))
public class Report extends Component {
  static final String DISCRIMINATOR_VALUE_STRING = "4";
  public static final int DISCRIMINATOR_VALUE = Integer.valueOf(DISCRIMINATOR_VALUE_STRING);

  other fields...
}

And so is a method

@Entity
@DiscriminatorValue(Method.DISCRIMINATOR_VALUE_STRING)
@SecondaryTable(name = "METHOD", pkJoinColumns = @PrimaryKeyJoinColumn(name = "METHOD_ID"))
public class Method extends Component {
  static final String DISCRIMINATOR_VALUE_STRING = "5";
  public static final int DISCRIMINATOR_VALUE = Integer.valueOf(DISCRIMINATOR_VALUE_STRING);

  other fields...
}

This is the relationship class that ties a parent to a child, and presumably what is causing the exception.

@Entity
@Table(name = "ASSOCIATION")
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "ASSOCIATION_TYPE", discriminatorType = DiscriminatorType.INTEGER)
@DiscriminatorValue("0")
public class Association implements Serializable {

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "CHILD_ID", referencedColumnName = "CHILD_ID")
  private Component child;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "PARENT_ID", referencedColumnName = "PARENT_ID")
  private Component parent;

  other fields...
}

Let’s start with the fact that Hibernate ORM 6.1 is not supported anymore. Update to at least the latest ORM 6.6 version or even better, the latest ORM 7.0. If the problem continues, then we can continue.

My apologies, I mis spoke. We are upgrading to 6.6.18, not 6.1.18 as I previously stated.