diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index aa48bd8769dc..570b0446e675 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -7,9 +7,11 @@ package org.hibernate.persister.entity; import java.util.ArrayList; +import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.hibernate.HibernateException; import org.hibernate.Internal; @@ -349,10 +351,10 @@ public SingleTableEntityPersister( ); // SUBCLASSES - final List subclasses = persistentClass.getSubclasses(); + final List subclasses = persistentClass.getSubclasses().stream().sorted(Comparator.comparing(PersistentClass::getEntityName)).collect(Collectors.toList()); for ( int k = 0; k < subclasses.size(); k++ ) { Subclass subclass = subclasses.get( k ); - subclassClosure[k] = subclass.getEntityName(); + subclassClosure[k+1] = subclass.getEntityName(); Object subclassDiscriminatorValue = DiscriminatorHelper.getDiscriminatorValue( subclass ); addSubclassByDiscriminatorValue( subclassesByDiscriminatorValueLocal, diff --git a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java index 24df6feecb23..f45ff1f1fe64 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/ast/spi/SqlExpressionResolver.java @@ -8,13 +8,13 @@ import java.util.function.Function; +import org.hibernate.metamodel.mapping.DiscriminatorMapping; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.SelectableMapping; import org.hibernate.metamodel.mapping.SelectablePath; import org.hibernate.sql.ast.tree.expression.ColumnReference; import org.hibernate.sql.ast.tree.expression.Expression; import org.hibernate.sql.ast.tree.expression.NestedColumnReference; -import org.hibernate.sql.ast.tree.from.EmbeddableFunctionTableReference; import org.hibernate.sql.ast.tree.from.TableReference; import org.hibernate.sql.results.graph.FetchParent; import org.hibernate.type.NullType; @@ -90,7 +90,10 @@ static ColumnReferenceKey createColumnReferenceKey(String tableExpression, Selec static ColumnReferenceKey createColumnReferenceKey(TableReference tableReference, SelectableMapping selectable) { assert tableReference.containsAffectedTableName( selectable.getContainingTableExpression() ) : String.format( ROOT, "Expecting tables to match between TableReference (%s) and SelectableMapping (%s)", tableReference.getTableId(), selectable.getContainingTableExpression() ); - return createColumnReferenceKey( tableReference, selectable.getSelectablePath(), selectable.getJdbcMapping() ); + final JdbcMapping jdbcMapping = (selectable instanceof DiscriminatorMapping) ? + ((DiscriminatorMapping)selectable).getUnderlyingJdbcMapping() : + selectable.getJdbcMapping(); + return createColumnReferenceKey( tableReference, selectable.getSelectablePath(), jdbcMapping ); } default Expression resolveSqlExpression(TableReference tableReference, SelectableMapping selectableMapping) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/SingleTableNativeQueryTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/SingleTableNativeQueryTest.java new file mode 100644 index 000000000000..a76f708ef237 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/inheritance/SingleTableNativeQueryTest.java @@ -0,0 +1,454 @@ +/* + * 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 . + */ +package org.hibernate.orm.test.inheritance; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import jakarta.persistence.CascadeType; +import jakarta.persistence.Column; +import jakarta.persistence.DiscriminatorColumn; +import jakarta.persistence.DiscriminatorType; +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Embeddable; +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.Inheritance; +import jakarta.persistence.InheritanceType; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.OneToMany; +import jakarta.persistence.OneToOne; +import org.hibernate.Session; +import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.core.Is.is; +import static org.hibernate.cfg.JdbcSettings.FORMAT_SQL; +import static org.hibernate.cfg.JdbcSettings.SHOW_SQL; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; + +@JiraKey("HHH-18610") +public class SingleTableNativeQueryTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + options.put(SHOW_SQL, true); + options.put(FORMAT_SQL, true); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Toy.class, + Color.class, + Family.class, + Person.class, + Child.class, + Man.class, + Woman.class + }; + } + + @Test + public void itShouldGetPersons() { + doInHibernate(this::entityManagerFactory, session -> { + loadData(session); + List results = session.createNativeQuery("select {p.*} from person p order by p.name", Object.class).addEntity("p", Person.class).list(); + assertThat( results.stream().map(p -> ((Person)p).getName()).collect(Collectors.toList()), contains("Jane", "John", "Mark", "Susan") ); + } ); + } + + @Test + public void itShouldGetWife() { + doInHibernate(this::entityManagerFactory, session -> { + loadData(session); + List results = session.createNativeQuery("select {m.*}, {w.*} from person m left join person w on m.wife_name = w.name where m.TYPE = 'MAN'", Object[].class) + .addEntity("m", Person.class) + .addEntity("w", Person.class) + .list(); + assertThat(results.size(), is(1)); + assertThat(results.get(0)[0], instanceOf(Man.class)); + assertThat(((Man)results.get(0)[0]).getName(), is("John")); + assertThat(results.get(0)[1], instanceOf(Woman.class)); + assertThat(((Woman)results.get(0)[1]).getName(), is("Jane")); + }); + } + + @Test + public void itShouldGetFamilyMembers() { + doInHibernate(this::entityManagerFactory, session -> { + loadData(session); + List results = session.createNativeQuery("select {f.*} from family f", Object.class).addEntity("f", Family.class).list(); + Family family = (Family)results.get(0); + List members = family.getMembers(); + assertThat( members.size(), is( familyMembers.size() ) ); + } ); + } + + + private Toy marioToy; + private Toy fidgetSpinner; + private Man john; + private Woman jane; + private Child susan; + private Child mark; + private Family family; + private List children; + private List familyMembers; + + public void loadData(Session session) { + + marioToy = new Toy(1L, "Super Mario retro Mushroom"); + fidgetSpinner = new Toy(2L, "Fidget Spinner"); + john = new Man( "John", "Riding Roller Coasters" ); + jane = new Woman( "Jane", "Hippotherapist" ); + susan = new Child( "Susan", marioToy ); + mark = new Child( "Mark", fidgetSpinner ); + family = new Family( "McCloud" ); + children = new ArrayList<>( Arrays.asList( susan, mark ) ); + familyMembers = Arrays.asList( john, jane, susan, mark ); + + + session.persist(marioToy); + session.persist(fidgetSpinner); + + jane.setColor(new Color("pink")); + jane.setHusband( john ); + jane.setChildren( children ); + + john.setColor(new Color("blue")); + john.setWife( jane ); + john.setChildren( children ); + + for ( Child child : children ) { + child.setFather( john ); + child.setMother( jane ); + } + + for ( Person person : familyMembers ) { + family.add( person ); + } + + session.persist( family ); + + session.flush(); + session.clear(); + } + + + @Embeddable + public static class Color { +// @JdbcTypeCode(SqlTypes.JSON) +// @Column(name = "color", columnDefinition = "json") + @Column(name = "color") + private String attributes; + + public Color() { + } + + public Color(final String attributes) { + this.attributes = attributes; + } + + public String getAttributes() { + return attributes; + } + + public void setAttributes(final String attributes) { + this.attributes = attributes; + } + } + + @Entity(name = "Family") + public static class Family { + + @Id + private String name; + + @OneToMany(mappedBy = "familyName", cascade = CascadeType.ALL, orphanRemoval = true) + private List members = new ArrayList<>(); + + public Family() { + } + + public Family(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getMembers() { + return members; + } + + public void setMembers(List members) { + this.members = members; + } + + public void add(Person person) { + person.setFamilyName( this ); + members.add( person ); + } + + @Override + public String toString() { + return "Family [name=" + name + "]"; + } + } + + @Entity(name = "Person") + @Inheritance(strategy = InheritanceType.SINGLE_TABLE) + @DiscriminatorColumn(name = "TYPE", discriminatorType = DiscriminatorType.STRING) + public static class Person { + + @Id + private String name; + + @ManyToOne + private Family familyName; + + public Person() { + } + + public Person(String name) { + this.name = name; + } + + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Family getFamilyName() { + return familyName; + } + + public void setFamilyName(Family familyName) { + this.familyName = familyName; + } + + @Override + public String toString() { + return name; + } + } + + + @Entity(name = "Toy") + public static class Toy { + + @Id + private Long id; + + private String name; + +// @OneToMany(mappedBy = "favoriteThing", cascade = CascadeType.ALL, orphanRemoval = true) +// List favorite = new ArrayList<>(); + + public Toy() { + } + + public Toy(final Long id, final String name) { + this.id = id; + this.name = name; + } + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(final String name) { + this.name = name; + } + } + + @Entity(name = "Child") + @DiscriminatorValue("CHILD") + public static class Child extends Person { + + @ManyToOne + @JoinColumn(name="fav_toy") + private Toy favoriteThing; + + @ManyToOne + private Woman mother; + + @ManyToOne + private Man father; + + public Child() { + } + + public Child(String name, Toy favouriteThing) { + super( name ); + this.favoriteThing = favouriteThing; + } + + public Toy getFavoriteThing() { + return favoriteThing; + } + + public void setFavoriteThing(Toy favouriteThing) { + this.favoriteThing = favouriteThing; + } + + public Man getFather() { + return father; + } + + public void setFather(Man father) { + this.father = father; + } + + public Woman getMother() { + return mother; + } + + public void setMother(Woman mother) { + this.mother = mother; + } + } + + @Entity(name = "Man") + @DiscriminatorValue("MAN") + public static class Man extends Person { + + private Color color; + + @Column(name="fav_hobby") + private String favoriteThing; + + @OneToOne + private Woman wife; + + @OneToMany(mappedBy = "father") + private List children = new ArrayList<>(); + + public Man() { + } + + public Man(String name, String favoriteThing) { + super( name ); + this.favoriteThing = favoriteThing; + } + + public String getFavoriteThing() { + return favoriteThing; + } + + public void setFavoriteThing(String favoriteThing) { + this.favoriteThing = favoriteThing; + } + + public Woman getWife() { + return wife; + } + + public void setWife(Woman wife) { + this.wife = wife; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public Color getColor() { + return color; + } + + public void setColor(final Color color) { + this.color = color; + } + } + + @Entity(name = "Woman") + @DiscriminatorValue("WOMAN") + public static class Woman extends Person { + + private Color color; + + private String job; + + @OneToOne + private Man husband; + + @OneToMany(mappedBy = "mother") + private List children = new ArrayList<>(); + + public Woman() { + } + + public Woman(String name, String job) { + super( name ); + this.job = job; + } + + + public String getJob() { + return job; + } + + public void setJob(String job) { + this.job = job; + } + + public Man getHusband() { + return husband; + } + + public void setHusband(Man husband) { + this.husband = husband; + } + + public List getChildren() { + return children; + } + + public void setChildren(List children) { + this.children = children; + } + + public Color getColor() { + return color; + } + + public void setColor(final Color color) { + this.color = color; + } + } +}