diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/GenericAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/GenericAssociationTest.java new file mode 100644 index 000000000000..49630f466911 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/GenericAssociationTest.java @@ -0,0 +1,194 @@ +/* + * 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.annotations.generics; + +import java.util.HashSet; +import java.util.Set; + +import org.hibernate.query.criteria.JpaPath; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OneToMany; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Root; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Yoann Rodière + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel( annotatedClasses = { + GenericAssociationTest.AbstractParent.class, + GenericAssociationTest.Parent.class, + GenericAssociationTest.AbstractChild.class, + GenericAssociationTest.Child.class +} ) +@Jira( "https://hibernate.atlassian.net/browse/HHH-16378" ) +public class GenericAssociationTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final Parent parent = new Parent( 1L ); + final Child child = new Child( 2L ); + child.setParent( parent ); + parent.getChildren().add( child ); + session.persist( parent ); + session.persist( child ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createQuery( "from Parent", Parent.class ) + .getResultList() + .forEach( p -> p.getChildren().clear() ) ); + scope.inTransaction( session -> { + session.createMutationQuery( "delete from Child" ).executeUpdate(); + session.createMutationQuery( "delete from Parent" ).executeUpdate(); + } ); + } + + @Test + public void testParentQuery(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select parent.id from Child", + Long.class + ).getSingleResult() ).isEqualTo( 1L ) ); + } + + @Test + public void testParentCriteriaQuery(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( Long.class ); + final Root root = query.from( Child.class ); + final Path parent = root.get( "parent" ); + // generic attributes are always reported as Object java type + assertThat( parent.getJavaType() ).isEqualTo( Object.class ); + assertThat( parent.getModel() ).isSameAs( root.getModel().getAttribute( "parent" ) ); + assertThat( ( (JpaPath) parent ).getResolvedModel().getBindableJavaType() ).isEqualTo( Parent.class ); + final Long result = session.createQuery( query.select( parent.get( "id" ) ) ).getSingleResult(); + assertThat( result ).isEqualTo( 1L ); + } ); + } + + @Test + public void testChildQuery(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select c.id from Parent p join p.children c", + Long.class + ).getSingleResult() ).isEqualTo( 2L ) ); + } + + @Test + public void testChildCriteriaQuery(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( Long.class ); + final Root root = query.from( Parent.class ); + final Join join = root.join( "children" ); + // generic attributes are always reported as Object java type + assertThat( join.getJavaType() ).isEqualTo( Object.class ); + assertThat( join.getModel() ).isSameAs( root.getModel().getAttribute( "children" ) ); + assertThat( ( (JpaPath) join ).getResolvedModel().getBindableJavaType() ).isEqualTo( Child.class ); + final Long result = session.createQuery( query.select( join.get( "id" ) ) ).getSingleResult(); + assertThat( result ).isEqualTo( 2L ); + } ); + } + + @Test + public void testElementQuery(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select element(c).id from Parent p join p.children c", + Long.class + ).getSingleResult() ).isEqualTo( 2L ) ); + } + + @MappedSuperclass + public abstract static class AbstractParent { + @OneToMany + private Set children; + + public AbstractParent() { + this.children = new HashSet<>(); + } + + public Set getChildren() { + return children; + } + } + + @Entity( name = "Parent" ) + public static class Parent extends AbstractParent { + @Id + private Long id; + + public Parent() { + } + + public Parent(Long id) { + this.id = id; + } + + public Long getId() { + return this.id; + } + } + + @MappedSuperclass + public abstract static class AbstractChild { + @ManyToOne + private T parent; + + public AbstractChild() { + } + + public abstract Long getId(); + + public T getParent() { + return this.parent; + } + + public void setParent(T parent) { + this.parent = parent; + } + } + + @Entity( name = "Child" ) + public static class Child extends AbstractChild { + @Id + protected Long id; + + public Child() { + } + + public Child(Long id) { + this.id = id; + } + + @Override + public Long getId() { + return this.id; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/GenericBasicValuedPathTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/GenericBasicValuedPathTest.java new file mode 100644 index 000000000000..42430ce7e629 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/GenericBasicValuedPathTest.java @@ -0,0 +1,106 @@ +/* + * 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.annotations.generics; + +import org.hibernate.query.criteria.JpaPath; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Path; +import jakarta.persistence.criteria.Root; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel( annotatedClasses = { + GenericBasicValuedPathTest.MySuper.class, + GenericBasicValuedPathTest.MyEntity.class +} ) +@Jira( "https://hibernate.atlassian.net/browse/HHH-16378" ) +public class GenericBasicValuedPathTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> session.persist( new MyEntity( 1, "my_entity" ) ) ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> session.createMutationQuery( "delete from MyEntity" ).executeUpdate() ); + } + + @Test + public void testId(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery(); + final Root root = query.from( MyEntity.class ); + final Path idPath = root.get( "id" ); + // generic attributes are always reported as Object java type + assertThat( idPath.getJavaType() ).isEqualTo( Object.class ); + assertThat( idPath.getModel() ).isSameAs( root.getModel().getAttribute( "id" ) ); + assertThat( ( (JpaPath) idPath ).getResolvedModel().getBindableJavaType() ).isEqualTo( Integer.class ); + final Object result = session.createQuery( query.select( idPath ) ).getSingleResult(); + assertThat( result ).isEqualTo( 1 ); + } ); + } + + @Test + public void testProperty(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery(); + final Root root = query.from( MyEntity.class ); + final Path dataPath = root.get( "data" ); + // generic attributes are always reported as Object java type + assertThat( dataPath.getJavaType() ).isEqualTo( Object.class ); + assertThat( dataPath.getModel() ).isSameAs( root.getModel().getAttribute( "data" ) ); + assertThat( ( (JpaPath) dataPath ).getResolvedModel().getBindableJavaType() ).isEqualTo( String.class ); + final Object result = session.createQuery( query.select( dataPath ) ).getSingleResult(); + assertThat( result ).isEqualTo( "my_entity" ); + } ); + } + + @MappedSuperclass + public abstract static class MySuper { + @Id + private T id; + private S data; + + public MySuper() { + } + + public MySuper(T id, S data) { + this.id = id; + this.data = data; + } + } + + @Entity( name = "MyEntity" ) + public static class MyEntity extends MySuper { + public MyEntity() { + } + + public MyEntity(Integer id, String data) { + super( id, data ); + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/GenericMapAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/GenericMapAssociationTest.java new file mode 100644 index 000000000000..837ebcbd3433 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/annotations/generics/GenericMapAssociationTest.java @@ -0,0 +1,191 @@ +/* + * 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.annotations.generics; + +import java.util.HashMap; +import java.util.Map; + +import org.hibernate.query.criteria.JpaPath; + +import org.hibernate.testing.orm.junit.DomainModel; +import org.hibernate.testing.orm.junit.Jira; +import org.hibernate.testing.orm.junit.SessionFactory; +import org.hibernate.testing.orm.junit.SessionFactoryScope; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import jakarta.persistence.MappedSuperclass; +import jakarta.persistence.OneToMany; +import jakarta.persistence.Table; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Join; +import jakarta.persistence.criteria.Root; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Marco Belladelli + */ +@SessionFactory +@DomainModel( annotatedClasses = { + GenericMapAssociationTest.AbstractParent.class, + GenericMapAssociationTest.MapContainerEntity.class, + GenericMapAssociationTest.MapKeyEntity.class, + GenericMapAssociationTest.MapValueEntity.class +} ) +@Jira( "https://hibernate.atlassian.net/browse/HHH-16378" ) +public class GenericMapAssociationTest { + @BeforeAll + public void setUp(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final MapContainerEntity container = new MapContainerEntity( 1L ); + final MapKeyEntity key = new MapKeyEntity( 2L, "key" ); + final MapValueEntity value = new MapValueEntity( 3L, "value" ); + container.getMap().put( key, value ); + session.persist( container ); + session.persist( key ); + session.persist( value ); + } ); + } + + @AfterAll + public void tearDown(SessionFactoryScope scope) { + scope.inTransaction( session -> { + session.createMutationQuery( "delete from MapContainerEntity" ).executeUpdate(); + session.createMutationQuery( "delete from MapValueEntity" ).executeUpdate(); + session.createMutationQuery( "delete from MapKeyEntity" ).executeUpdate(); + } ); + } + + @Test + public void testChildQuery(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select c.id from MapContainerEntity p join p.map c", + Long.class + ).getSingleResult() ).isEqualTo( 3L ) ); + } + + @Test + public void testChildCriteriaQuery(SessionFactoryScope scope) { + scope.inTransaction( session -> { + final CriteriaBuilder cb = session.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( Long.class ); + final Root root = query.from( MapContainerEntity.class ); + final Join join = root.join( "map" ); + // generic attributes are always reported as Object java type + assertThat( join.getJavaType() ).isEqualTo( Object.class ); + assertThat( join.getModel() ).isSameAs( root.getModel().getAttribute( "map" ) ); + assertThat( ( (JpaPath) join ).getResolvedModel() + .getBindableJavaType() ).isEqualTo( MapValueEntity.class ); + query.select( join.get( "id" ) ); + final Long result = session.createQuery( query ).getSingleResult(); + assertThat( result ).isEqualTo( 3L ); + } ); + } + + @Test + public void testKeyQuery(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select key(p.map).keyName from MapContainerEntity p", + String.class + ).getSingleResult() ).isEqualTo( "key" ) ); + } + + @Test + public void testValueQuery(SessionFactoryScope scope) { + scope.inTransaction( session -> assertThat( session.createQuery( + "select value(p.map).valueName from MapContainerEntity p", + String.class + ).getSingleResult() ).isEqualTo( "value" ) ); + } + + @MappedSuperclass + public static abstract class AbstractParent { + @OneToMany + private Map map; + + public AbstractParent() { + this.map = new HashMap<>(); + } + + public Map getMap() { + return map; + } + } + + @Entity( name = "MapContainerEntity" ) + @Table( name = "map_container_entity" ) + public static class MapContainerEntity extends AbstractParent { + @Id + private Long id; + + public MapContainerEntity() { + } + + public MapContainerEntity(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + } + + @Entity( name = "MapKeyEntity" ) + @Table( name = "map_key_entity" ) + public static class MapKeyEntity { + @Id + private Long id; + + private String keyName; + + public MapKeyEntity() { + } + + public MapKeyEntity(Long id, String keyName) { + this.id = id; + this.keyName = keyName; + } + + public Long getId() { + return id; + } + + public String getKeyName() { + return keyName; + } + } + + @Entity( name = "MapValueEntity" ) + @Table( name = "map_value_entity" ) + public static class MapValueEntity { + @Id + private Long id; + + private String valueName; + + public MapValueEntity() { + } + + public MapValueEntity(Long id, String valueName) { + this.id = id; + this.valueName = valueName; + } + + public Long getId() { + return id; + } + + public String getValueName() { + return valueName; + } + } +}