@OneToMany with interface type return variable (role column) @Any

Hello,

I have a collection that has a return variable that can be of multiple types and therefore uses a ROLE (or D-TYPE) column.

For this return variable I cannot use @ManyToOne because it does not allow the use of ROLE columns.
So I use an @ANY

Everything works except that the ROLE column (here ELEMENTDETRAVAIL_ROLE) is not updated during the persisterCollection.

Do you have any idea how to handle this case?

Thanking you.

public class Agent implements IElementDeTravail 
...
@OneToMany
@JoinColumn(name = "ELEMENTDETRAVAIL_ID")
private Set<Tache> taches = new HashSet<>();

public class Tache
....
@Any(metaDef = "IElementDeTravailMetaDef", metaColumn = @Column(name = "ELEMENTDETRAVAIL_ROLE"))
@JoinColumn(name = "ELEMENTDETRAVAIL_ID")
private IElementDeTravail elementDeTravail;

For information, we are migrating to hibernate 6.1.5 but I don’t think it changes much, except the @AnyValue and the metadef

22/12/2022 : small correction there was no mappedby which is not compatible with the @JoinColumn

Might be a bug. Please create an issue in the issue tracker(https://hibernate.atlassian.net) with a test case(hibernate-test-case-templates/JPAUnitTestCase.java at main · hibernate/hibernate-test-case-templates · GitHub) that reproduces the issue.

I do this as soon as possible. Thanks very much.

In fact to be more precise the real problem is rather with the use of mappedBy (normal use).

And there it tells us the collection foreign key mapping has wrong number of column.

Logical because the primary key of Agent consists only of an ID column while the foreign key of two columns ID + ROLE

Caused by: org.hibernate.MappingException: collection foreign key mapping has wrong number of columns: org.hibernate.bugs.entity.Affaire.taches type: long
	at org.hibernate.mapping.Collection.validate(Collection.java:379)
	at org.hibernate.mapping.Set.validate(Set.java:51)
	at org.hibernate.boot.internal.MetadataImpl.validate(MetadataImpl.java:380)
	at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:301)
	at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:415)
	at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1425)

I add the source code of the TU.

Do I still create a JIRA ticket?

package org.hibernate.bugs.entity;

import java.util.Set;
import jakarta.persistence.*;

@Entity
@DiscriminatorColumn
@DiscriminatorValue("affaire")
public class Affaire implements IElementDeTravail {

  @Id
  @GeneratedValue
  private Long id;

  @OneToMany(mappedBy = "elementDeTravail", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
  private Set<Tache> taches;

  public void setId(final Long id) {
    this.id = id;
  }

  public Long getId() {
    return id;
  }

  public Set<Tache> getTaches() {
    return taches;
  }

  public void setTaches(final Set<Tache> taches) {
    this.taches = taches;
  }
}

package org.hibernate.bugs.entity;

import jakarta.persistence.*;
import org.hibernate.annotations.*;
import org.hibernate.annotations.JavaType;
import org.hibernate.type.descriptor.java.*;

@Entity
public class Tache {

  @Id
  @GeneratedValue
  private Long id;

  @Any
  @AnyDiscriminator(DiscriminatorType.STRING)
  @AnyDiscriminatorValue(discriminator = "affaire", entity = Affaire.class)
  @AnyKeyJavaClass(Long.class)
  @Column(name = "ELEMENTDETRAVAIL_ROLE")
  @JoinColumn(name = "ELEMENTDETRAVAIL_ID")
  private IElementDeTravail elementDeTravail;

  public void setId(final Long id) {
    this.id = id;
  }

  public Long getId() {
    return id;
  }

  public IElementDeTravail getElementDeTravail() {
    return elementDeTravail;
  }

  public void setElementDeTravail(final IElementDeTravail objetMaitre) {
    this.elementDeTravail = objetMaitre;
  }
}

package org.hibernate.bugs;

import java.util.Set;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.Persistence;

import org.hibernate.Session;
import org.hibernate.bugs.entity.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * This template demonstrates how to develop a test case for Hibernate ORM, using the Java Persistence API.
 */
public class JPAUnitTestCase {

 private EntityManagerFactory entityManagerFactory;

 @Before
 public void init() {
  entityManagerFactory = Persistence.createEntityManagerFactory( "templatePU" );
 }

 @After
 public void destroy() {
  entityManagerFactory.close();
 }

 // Entities are auto-discovered, so just add them anywhere on class-path
 // Add your tests, using standard JUnit.
 @Test
 public void hhh123Test() throws Exception {
  EntityManager entityManager = entityManagerFactory.createEntityManager();
  entityManager.getTransaction().begin();
  // Do stuff...

  Affaire affaire = new Affaire();
  final Set<Tache> taches = Set.of(new Tache(), new Tache());
  affaire.setTaches(taches);

  entityManager.persist(affaire);
  entityManager.flush();
  entityManager.clear();

// KO here
  final Affaire affaireReload = entityManager.unwrap(Session.class).byId(affaire.getClass()).load(affaire.getId());

  entityManager.getTransaction().commit();
  entityManager.close();

 }
}

You’re not the first one to ask for this. You can track this issue: [HHH-15722] - Hibernate JIRA

It’s actually the same issue.
I wait then, no need to recreate a JIRA ticket that would be duplicated.

Thanks to you

Do you know if this topic will be taken into consideration?

Because in our project, we are in the process of replacing mappedBy with @JoinColumn everywhere for relationships with @Any .

Do you know if this topic will be taken into consideration?

Depends what you mean by “taken into consideration”. The Hibernate ORM team is willing to review and work through a PR contributed by the community, but we do not actively work on this or have plans to do so in the future.

If it was on the to-do list or not at all ^^.
And also, if it’s expected to use ‘mappedBy’ with an ‘@Any’ soon.

If yes, I can look into it with my knowledge to see if I can solve the issue, but I was worried it might be outside my expertise.
However, I can give it a try and provide feedback on this post if that’s okay with you

Sure, give it a try.

I would have liked to know if this possibility of being able to use mappedBy with an @Any was on the list of planned features? Because on our side, we have more than a hundred cases like this.

What surprises me is that we seem to be the only ones, or few, asking for it.

I’m not demanding anything at all, just asking! :slight_smile:

No, it’s not planned at this time or anytime in the future. At this point, it’s up to the community to drive this forward.

Ok, thank you, that’s clear ^^, I’ll check on my end.

I note here my modification because i cannot modify a old post

/*
 * Hibernate, Relational Persistence for Idiomatic Java
 *
 * License: GNU Lesser General Public License (LGPL), version 2.1 or later
 * See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
 */
package org.hibernate.orm.test.mapping.manytoone;

import java.util.Set;

import org.hibernate.Session;
import org.hibernate.annotations.Any;
import org.hibernate.annotations.AnyDiscriminator;
import org.hibernate.annotations.AnyDiscriminatorValue;
import org.hibernate.annotations.AnyKeyJavaClass;
import org.hibernate.cfg.JdbcSettings;

import org.hibernate.testing.orm.junit.EntityManagerFactoryScope;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.Jpa;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;

import static jakarta.persistence.DiscriminatorType.STRING;

/**
 * @author Andrea Boriero
 */
@Jpa(
		annotatedClasses = {
				ManyToOneWithAnyTest.Library.class,
				ManyToOneWithAnyTest.Book.class
		},
		integrationSettings = @Setting(name = JdbcSettings.SHOW_SQL, value = "true")
)
@JiraKey("HHH-15722")
public class ManyToOneWithAnyTest {

	@Test
	public void basicTest(EntityManagerFactoryScope scope) {

		scope.inTransaction(
				entityManager -> {
					Library library = new Library();
					final Set<Book> books = Set.of( new Book(), new Book() );
					library.setBooks( books );

					entityManager.persist( library );
					entityManager.flush();
					entityManager.clear();

					library = entityManager.unwrap( Session.class )
							.byId( library.getClass() )
							.load( library.getId() );

					Assertions.assertNotNull( library );
				}
		);
	}

	@Entity(name = "library")
	@Table(name = "TLIBRARY")
	public static class Library implements Store {

		@Id
		@GeneratedValue
		private Long id;

		@OneToMany(mappedBy = "store", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
		private Set<Book> books;

		public void setId(final Long id) {
			this.id = id;
		}

		public Long getId() {
			return id;
		}

		public Set<Book> getBooks() {
			return books;
		}

		public void setBooks(final Set<Book> books) {
			books.forEach( book -> book.setStore( this ) );
			this.books = books;
		}
	}

	@Entity(name = "book")
	@Table(name = "TBOOK")
	public static class Book {

		@Id
		@GeneratedValue
		private Long id;

		@Any
		@AnyDiscriminator(STRING)
		@AnyDiscriminatorValue(discriminator = "library", entity = Library.class)
		@AnyKeyJavaClass(Long.class)
		@Column(name = "STORE_ROLE")
		@JoinColumn(name = "STORE_ID")
		private Store store;

		public void setId(final Long id) {
			this.id = id;
		}

		public Long getId() {
			return id;
		}

		public Store getStore() {
			return store;
		}

		public void setStore(final Store store) {
			this.store = store;
		}
	}

	public interface Store {
	}
}


I have proposed a solution with a test

HHH-15722 : @OneToMany mappedBy with a @Any by boutss · Pull Request #8109 · hibernate/hibernate-orm (github.com)

1 Like