diff --git a/gradle/java-module.gradle b/gradle/java-module.gradle index 5df68ad7e8..a01576f565 100644 --- a/gradle/java-module.gradle +++ b/gradle/java-module.gradle @@ -337,16 +337,12 @@ task copyResourcesToIntelliJOutFolder(type: Task, dependsOn: project.tasks.proce Afterward, you can run any test from the IDE against that particular DB. */ -task setDataBase { - inputs.property( "db", db ) - doLast { - processTestResources - copyResourcesToIntelliJOutFolder - - println( 'Setting current database to ' + db ) - } +task setDataBase dependsOn( processTestResources, copyResourcesToIntelliJOutFolder ) { + println( 'Setting current database to ' + db ) } +copyResourcesToIntelliJOutFolder.mustRunAfter processTestResources + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Report configs diff --git a/hibernate-core/src/test/java/org/hibernate/event/service/internal/NewlyInstantiatdCollectionSkipDeleteOrphanTest.java b/hibernate-core/src/test/java/org/hibernate/event/service/internal/NewlyInstantiatdCollectionSkipDeleteOrphanTest.java index 59e72494e5..c27d7fe3f3 100644 --- a/hibernate-core/src/test/java/org/hibernate/event/service/internal/NewlyInstantiatdCollectionSkipDeleteOrphanTest.java +++ b/hibernate-core/src/test/java/org/hibernate/event/service/internal/NewlyInstantiatdCollectionSkipDeleteOrphanTest.java @@ -27,6 +27,8 @@ import org.hibernate.Transaction; import org.hibernate.annotations.Cascade; import org.hibernate.annotations.DynamicUpdate; +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; import org.hibernate.testing.TestForIssue; import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; import org.junit.After; @@ -39,6 +41,7 @@ import org.junit.Test; * @author Nathan Xu */ @TestForIssue( jiraKey = "HHH-14178" ) +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) public class NewlyInstantiatdCollectionSkipDeleteOrphanTest extends BaseCoreFunctionalTestCase { private UnversionedParent up; @@ -177,6 +180,8 @@ public class NewlyInstantiatdCollectionSkipDeleteOrphanTest extends BaseCoreFunc private Integer id; private Long version; + private String name; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "Id", nullable = false) @@ -188,6 +193,14 @@ public class NewlyInstantiatdCollectionSkipDeleteOrphanTest extends BaseCoreFunc this.id = id; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + @Version @Column(name = "Version", nullable = false) public Long getVersion() { @@ -265,12 +278,14 @@ public class NewlyInstantiatdCollectionSkipDeleteOrphanTest extends BaseCoreFunc } @Entity(name = "UnversionedParent") - @Table(name = "UnversionedParent") + @Table(name = "UnversParent") @DynamicUpdate public static class UnversionedParent { private Integer id; private Set versionedMappings; + private String name; + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="Id", nullable=false) @@ -291,6 +306,14 @@ public class NewlyInstantiatdCollectionSkipDeleteOrphanTest extends BaseCoreFunc } } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + @OneToMany(mappedBy="parent", cascade={ javax.persistence.CascadeType.DETACH, javax.persistence.CascadeType.MERGE, javax.persistence.CascadeType.REFRESH, javax.persistence.CascadeType.REMOVE }, orphanRemoval=true) @Cascade({ org.hibernate.annotations.CascadeType.DELETE, org.hibernate.annotations.CascadeType.LOCK, org.hibernate.annotations.CascadeType.REPLICATE }) protected Set getVersionedMappings() { @@ -346,12 +369,13 @@ public class NewlyInstantiatdCollectionSkipDeleteOrphanTest extends BaseCoreFunc } @Entity(name = "VersionedParent") - @Table(name = "VersionedParent") + @Table(name = "VersParent") @DynamicUpdate public static class VersionedParent { private Integer id; private Long version; private Set children; + private String name; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -373,6 +397,14 @@ public class NewlyInstantiatdCollectionSkipDeleteOrphanTest extends BaseCoreFunc } } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + @Version @Column(name="Version", nullable=false) public Long getVersion() { @@ -438,12 +470,13 @@ public class NewlyInstantiatdCollectionSkipDeleteOrphanTest extends BaseCoreFunc } @Entity(name = "VersionedMappingUnversionedParent") - @Table(name = "VersionedMappingUnversionedParent") + @Table(name = "VersdMapUnversParent") @DynamicUpdate public static class VersionedMappingUnversionedParent { private MappingId id; private Child child; private Long version; + private String name; @EmbeddedId public MappingId getId() { @@ -464,6 +497,14 @@ public class NewlyInstantiatdCollectionSkipDeleteOrphanTest extends BaseCoreFunc this.version = version; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + protected UnversionedParent parent; @ManyToOne(optional=false, fetch=FetchType.LAZY) @@ -551,12 +592,13 @@ public class NewlyInstantiatdCollectionSkipDeleteOrphanTest extends BaseCoreFunc } @Entity(name = "VersionedMappingVersionedParent") - @Table(name = "VersionedMappingVersionedParent") + @Table(name = "VersMapVersParent") @DynamicUpdate public static class VersionedMappingVersionedParent { private MappingId id; private Child child; private Long version; + private String name; @EmbeddedId public MappingId getId() { @@ -577,6 +619,14 @@ public class NewlyInstantiatdCollectionSkipDeleteOrphanTest extends BaseCoreFunc this.version = version; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + protected VersionedParent parent; @ManyToOne(optional=false, fetch=FetchType.LAZY) diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphAttributeResolutionTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphAttributeResolutionTest.java new file mode 100644 index 0000000000..6e3d7fabee --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/graphs/EntityGraphAttributeResolutionTest.java @@ -0,0 +1,254 @@ +package org.hibernate.jpa.test.graphs; + +import java.util.EnumSet; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.CollectionTable; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EntityGraph; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToMany; +import javax.persistence.NamedAttributeNode; +import javax.persistence.NamedEntityGraph; +import javax.persistence.Table; + +import org.hibernate.graph.GraphSemantic; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Before; +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + + +/** + * @author Benjamin M. + * @author Nathan Xu + */ +@TestForIssue( jiraKey = "HHH-14113" ) +@SuppressWarnings({ "unchecked", "rawtypes" }) +public class EntityGraphAttributeResolutionTest extends BaseEntityManagerFunctionalTestCase { + + private User u; + private Group g1, g2; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { User.class, Group.class }; + } + + @Before + public void setUp() { + doInJPA( this::entityManagerFactory, em -> { + g1 = new Group(); + g1.addPermission( Permission.BAR ); + em.persist( g1 ); + + g2 = new Group(); + g2.addPermission( Permission.BAZ ); + em.persist( g2 ); + + u = new User(); + em.persist( u ); + u.addGroup( g1 ); + u.addGroup( g2 ); + } ); + } + + @Test + public void fetchAssocWithNamedFetchGraph() { + doInJPA( this::entityManagerFactory, em -> { + List result = em.createQuery( "SELECT u.groups FROM User u WHERE u.id = ?1" ) + .setParameter(1, u.getId() ) + .setHint( GraphSemantic.FETCH.getJpaHintName(), em.getEntityGraph( Group.ENTITY_GRAPH ) ) + .getResultList(); + + assertThat( result ).containsExactlyInAnyOrder( g1, g2 ); + } ); + } + + @Test + public void fetchAssocWithNamedFetchGraphAndJoin() { + doInJPA( this::entityManagerFactory, em -> { + List result = em.createQuery( "SELECT g FROM User u JOIN u.groups g WHERE u.id = ?1" ) + .setParameter( 1, u.getId() ) + .setHint( GraphSemantic.FETCH.getJpaHintName(), em.getEntityGraph( Group.ENTITY_GRAPH ) ) + .getResultList(); + + assertThat( result ).containsExactlyInAnyOrder( g1, g2 ); + } ); + } + + @Test + public void fetchAssocWithAdhocFetchGraph() { + doInJPA( this::entityManagerFactory, em -> { + EntityGraph eg = em.createEntityGraph( Group.class ); + eg.addAttributeNodes( "permissions" ); + + List result = em.createQuery( "SELECT u.groups FROM User u WHERE u.id = ?1" ) + .setParameter(1, u.getId() ) + .setHint( GraphSemantic.FETCH.getJpaHintName(), eg ) + .getResultList(); + + assertThat( result ).containsExactlyInAnyOrder( g1, g2 ); + } ); + } + + @Entity(name = "Group") + @NamedEntityGraph(name = Group.ENTITY_GRAPH, + attributeNodes = { + @NamedAttributeNode("permissions") + }) + @Table(name = "groups") // Name 'group' not accepted by H2 + public static class Group { + public static final String ENTITY_GRAPH = "group-with-permissions"; + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Enumerated(EnumType.STRING) + @ElementCollection(targetClass = Permission.class) + @CollectionTable( + name = "GROUPS_PERMISSIONS", + joinColumns = @JoinColumn(name = "gid") + ) + private Set permissions = EnumSet.noneOf( Permission.class ); + + public Group() {} + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Set getPermissions() { + return permissions; + } + + public void setPermissions(Set permissions) { + this.permissions = permissions; + } + + public void addPermission(Permission p) { + this.permissions.add( p ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + + if ( !( o instanceof Group ) ) + return false; + + Group other = (Group) o; + + return id != null && + id.equals( other.getId() ); + } + + @Override + public int hashCode() { + return 31; + } + + @Override + public String toString() { + return "Group{" + + "id=" + id + + '}'; + } + } + + @Entity(name = "User") + @Table(name = "users") + public static class User { + + @Id + @GeneratedValue(strategy = GenerationType.AUTO) + private Long id; + + @Enumerated(EnumType.STRING) + @ElementCollection(targetClass = Permission.class) + @CollectionTable(name = "USERS_PERMISSIONS", joinColumns = @JoinColumn(name = "user_id")) + private Set permissions = EnumSet.of( Permission.FOO ); + + @ManyToMany(fetch = FetchType.LAZY) + private Set groups = new HashSet<>(); + + public User() {} + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Set getPermissions() { + return permissions; + } + + public void setPermissions(Set permissions) { + this.permissions = permissions; + } + public void addPermission(Permission p) { + this.permissions.add( p ); + } + + public Set getGroups() { + return groups; + } + + public void setGroups(Set groups) { + this.groups = groups; + } + + public void addGroup(Group g) { + this.groups.add( g ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) return true; + + if ( !( o instanceof User ) ) + return false; + + User other = (User) o; + + return id != null && + id.equals( other.getId() ); + } + + @Override + public int hashCode() { + return 31; + } + + @Override + public String toString() { + return "User{" + + "id=" + id + + '}'; + } + } + + public enum Permission { + FOO, BAR, BAZ + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/HHH13058Test.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/HHH13058Test.java new file mode 100644 index 0000000000..1961ba60c4 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/HHH13058Test.java @@ -0,0 +1,133 @@ +package org.hibernate.query.criteria.internal.hhh13058; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.From; +import javax.persistence.criteria.JoinType; +import javax.persistence.criteria.Root; +import javax.persistence.criteria.Subquery; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; +import static org.junit.Assert.assertThat; + +/** + * @author Archie Cobbs + * @author Nathan Xu + */ +@TestForIssue( jiraKey = "HHH-13058" ) +public class HHH13058Test extends BaseEntityManagerFunctionalTestCase { + + private Set validSites; + + private Task taskWithoutPatient; + private Task taskWithPatientWithoutSite; + private Task taskWithPatient1WithValidSite1; + private Task taskWithPatient2WithValidSite1; + private Task taskWithPatient3WithValidSite2; + private Task taskWithPatientWithInvalidSite; + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Task.class, + Patient.class, + Site.class + }; + } + + @Before + public void setUp() { + doInJPA( this::entityManagerFactory, entityManager -> { + final Site validSite1 = new Site(); + final Site validSite2 = new Site(); + final Site invalidSite = new Site(); + + entityManager.persist( validSite1 ); + entityManager.persist( validSite2 ); + entityManager.persist( invalidSite ); + + validSites = new HashSet<>( Arrays.asList( validSite1, validSite2 ) ); + + final Patient patientWithoutSite = new Patient(); + final Patient patient1WithValidSite1 = new Patient( validSite1 ); + final Patient patient2WithValidSite1 = new Patient( validSite1 ); + final Patient patient3WithValidSite2 = new Patient( validSite2 ); + final Patient patientWithInvalidSite = new Patient( invalidSite ); + + entityManager.persist( patientWithoutSite ); + entityManager.persist( patient1WithValidSite1 ); + entityManager.persist( patient2WithValidSite1 ); + entityManager.persist( patient3WithValidSite2 ); + entityManager.persist( patientWithInvalidSite ); + + taskWithoutPatient = new Task(); + taskWithoutPatient.description = "taskWithoutPatient"; + + taskWithPatientWithoutSite = new Task( patientWithoutSite ); + taskWithPatientWithoutSite.description = "taskWithPatientWithoutSite"; + + taskWithPatient1WithValidSite1 = new Task( patient1WithValidSite1 ); + taskWithPatient1WithValidSite1.description = "taskWithPatient1WithValidSite1"; + + taskWithPatient2WithValidSite1 = new Task( patient2WithValidSite1 ); + taskWithPatient2WithValidSite1.description = "taskWithPatient2WithValidSite1"; + + taskWithPatient3WithValidSite2 = new Task( patient3WithValidSite2 ); + taskWithPatient3WithValidSite2.description = "taskWithPatient3WithValidSite2"; + + taskWithPatientWithInvalidSite = new Task( patientWithInvalidSite ); + taskWithPatientWithInvalidSite.description = "taskWithPatientWithInvalidSite"; + + entityManager.persist( taskWithoutPatient ); + entityManager.persist( taskWithPatientWithoutSite ); + entityManager.persist( taskWithPatient1WithValidSite1 ); + entityManager.persist( taskWithPatient2WithValidSite1 ); + entityManager.persist( taskWithPatient3WithValidSite2 ); + entityManager.persist( taskWithPatientWithInvalidSite ); + } ); + } + + @Test + public void testCorrelateSubQueryLeftJoin() { + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder builder = entityManager.getCriteriaBuilder(); + final CriteriaQuery outerQuery = builder.createQuery( Task.class ); + final Root outerTask = outerQuery.from( Task.class ); + + final Subquery subquery = outerQuery.subquery( Task.class ); + final Root subtask = subquery.correlate( outerTask ); + final From patient = subtask.join( Task_.patient, JoinType.LEFT ); + final From site = patient.join( Patient_.site, JoinType.LEFT ); + outerQuery.where( + builder.exists( + subquery.select( subtask ) + .where( + builder.or( + patient.isNull(), + site.in( validSites ) + ) + ) + ) + ); + final List tasks = entityManager.createQuery( outerQuery ).getResultList(); + assertThat( new HashSet<>( tasks ), is( new HashSet<>( Arrays.asList( + taskWithoutPatient, + taskWithPatient1WithValidSite1, + taskWithPatient2WithValidSite1, + taskWithPatient3WithValidSite2 + ) ) ) ); + + } ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/Patient.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/Patient.java new file mode 100644 index 0000000000..a314c5908f --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/Patient.java @@ -0,0 +1,32 @@ +package org.hibernate.query.criteria.internal.hhh13058; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author Archie Cobbs + * @author Nathan Xu + */ +@Entity(name = "Patient") +@Table(name = "Patient") +public class Patient { + + @Id + @GeneratedValue + Long id; + + @ManyToOne(fetch = FetchType.LAZY) + Site site; + + public Patient() { + } + + public Patient(Site site) { + this.site = site; + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/Site.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/Site.java new file mode 100644 index 0000000000..3913ad5d6d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/Site.java @@ -0,0 +1,20 @@ +package org.hibernate.query.criteria.internal.hhh13058; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * @author Archie Cobbs + * @author Nathan Xu + */ +@Entity(name = "Site") +@Table(name = "Site") +public class Site { + + @Id + @GeneratedValue + Long id; + +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/Task.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/Task.java new file mode 100644 index 0000000000..48a0ec56f3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh13058/Task.java @@ -0,0 +1,56 @@ +package org.hibernate.query.criteria.internal.hhh13058; + +import java.util.Objects; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +/** + * @author Archie Cobbs + * @author Nathan Xu + */ +@Entity(name = "Task") +@Table(name = "Task") +public class Task { + + @Id + @GeneratedValue + Long id; + + @ManyToOne(fetch = FetchType.LAZY) + Patient patient; + + String description; + + public Task() { + } + + public Task(Patient patient) { + this.patient = patient; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + Task task = (Task) o; + return id.equals( task.id ); + } + + @Override + public int hashCode() { + return Objects.hash( id ); + } + + @Override + public String toString() { + return String.format( "Task(id: %d; description: %s)", id, description == null ? "null" : description ); + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/AbstractPersistent.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/AbstractPersistent.java new file mode 100644 index 0000000000..8ab3a89042 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/AbstractPersistent.java @@ -0,0 +1,30 @@ +package org.hibernate.query.criteria.internal.hhh14197; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.MappedSuperclass; + +/** + * @author Archie Cobbs + */ + +@MappedSuperclass +public abstract class AbstractPersistent { + + private long id; + + protected AbstractPersistent() { + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(nullable = false) + public long getId() { + return this.id; + } + public void setId(long id) { + this.id = id; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Department.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Department.java new file mode 100644 index 0000000000..f3ec2eb5d6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Department.java @@ -0,0 +1,32 @@ +package org.hibernate.query.criteria.internal.hhh14197; + +import java.util.HashSet; +import java.util.Set; +import javax.persistence.Entity; +import javax.persistence.OneToMany; + +/** + * @author Archie Cobbs + */ + +@Entity +public class Department extends AbstractPersistent { + + private String name; + private Set employees = new HashSet<>(); + + public String getName() { + return this.name; + } + public void setName(String name) { + this.name = name; + } + + @OneToMany(mappedBy = "department") + public Set getEmployees() { + return this.employees; + } + public void setEmployees(Set employees) { + this.employees = employees; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Employee.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Employee.java new file mode 100644 index 0000000000..20ef889104 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/Employee.java @@ -0,0 +1,118 @@ +package org.hibernate.query.criteria.internal.hhh14197; + +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.OneToMany; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +/** + * @author Archie Cobbs + */ + +@Entity +public class Employee extends AbstractPersistent { + + private String name; + private float salary; + private Seniority seniority; + private Date startDate; + private Department department; + private Employee manager; + private Set directReports = new HashSet<>(); + private Map annotations = new HashMap<>(); + + public String getName() { + return this.name; + } + public void setName(String name) { + this.name = name; + } + + public float getSalary() { + return this.salary; + } + public void setSalary(float salary) { + this.salary = salary; + } + + @Enumerated(EnumType.STRING) + public Seniority getSeniority() { + return this.seniority; + } + public void setSeniority(Seniority seniority) { + this.seniority = seniority; + } + + @Temporal(TemporalType.TIMESTAMP) + public Date getStartDate() { + return this.startDate; + } + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + @ManyToOne + public Employee getManager() { + return this.manager; + } + public void setManager(Employee manager) { + this.manager = manager; + } + + @ManyToOne + public Department getDepartment() { + return this.department; + } + public void setDepartment(Department department) { + this.department = department; + } + + @OneToMany(mappedBy = "manager") + public Set getDirectReports() { + return this.directReports; + } + public void setDirectReports(Set directReports) { + this.directReports = directReports; + } + + @ElementCollection + @MapKeyColumn(name = "name", length = 180) + @Column(name = "value", nullable = false) + public Map getAnnotations() { + return this.annotations; + } + public void setAnnotations(Map annotations) { + this.annotations = annotations; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + + "[name=" + (this.name != null ? "\"" + this.name + "\"" : null) + + ",salary=" + this.salary + + ",startDate=" + this.startDate + + ",department=" + this.department + + ",manager=" + this.manager + + ",directReports=" + this.directReports + + ",annotations=" + this.annotations + + "]"; + } + +// Seniority + + public enum Seniority { + JUNIOR, + SENIOR; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/HHH14197Test.java b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/HHH14197Test.java new file mode 100644 index 0000000000..42eb514cff --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/query/criteria/internal/hhh14197/HHH14197Test.java @@ -0,0 +1,60 @@ +package org.hibernate.query.criteria.internal.hhh14197; + +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.MapJoin; +import javax.persistence.criteria.Root; +import javax.persistence.criteria.SetJoin; +import javax.persistence.criteria.Subquery; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.FailureExpected; +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Archie Cobbs + * @author Nathan Xu + */ +@TestForIssue( jiraKey = "HHH-14197" ) +public class HHH14197Test extends BaseEntityManagerFunctionalTestCase { + + @Override + public Class[] getAnnotatedClasses() { + return new Class[] { + Department.class, + Employee.class + }; + } + + @Test + public void testValidSQLGenerated() { + // without fixing HHH-14197, invalid SQL would be generated without root + // "... where exists (select employee0_.id as id1_1_ from where ...) ... " + doInJPA( this::entityManagerFactory, entityManager -> { + final CriteriaBuilder cb = entityManager.getCriteriaBuilder(); + final CriteriaQuery query = cb.createQuery( Employee.class ); + final Root employee = query.from( Employee.class ); + + final Subquery subquery1 = query.subquery( Employee.class ); + final Root employee2 = subquery1.correlate( employee ); + final SetJoin directReport = employee2.join( Employee_.directReports ); + + final Subquery subquery2 = subquery1.subquery( Employee.class ); + final SetJoin directReport2 = subquery2.correlate( directReport ); + directReport2.join( Employee_.annotations ); + + subquery2.select( directReport2 ); + + subquery1.select( employee2 ).where( cb.exists( subquery2 ) ); + + query.select( employee ).where( cb.exists( subquery1 ) ); + + entityManager.createQuery( query ).getResultList(); + } ); + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/hql/FetchNonRootRelativeElementCollectionAndAssociationTest.java b/hibernate-core/src/test/java/org/hibernate/test/hql/FetchNonRootRelativeElementCollectionAndAssociationTest.java new file mode 100644 index 0000000000..cf9e99cea7 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/hql/FetchNonRootRelativeElementCollectionAndAssociationTest.java @@ -0,0 +1,70 @@ +package org.hibernate.test.hql; + +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import javax.persistence.*; + +import java.util.HashMap; +import java.util.Map; + +import static javax.persistence.CascadeType.ALL; +import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; + +/** + * @author Moritz Becker + */ +@TestForIssue(jiraKey = "HHH-13201") +public class FetchNonRootRelativeElementCollectionAndAssociationTest extends BaseEntityManagerFunctionalTestCase { + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { ProductNaturalId.class, Product.class, ProductDetail.class }; + } + + @Test + public void testJoinedSubclassUpdateWithCorrelation() { + doInJPA( this::entityManagerFactory, entityManager -> { + // DO NOT CHANGE this query: it used to trigger an error caused + // by the origin FromElement for the association fetch being resolved to the wrong FromElement due to the + // presence of an element collection join. + String u = "select prod from ProductNaturalId nat inner join nat.product prod " + + "left join fetch prod.productDetail " + + "left join fetch prod.normalizedPricesByUnit"; + Query query = entityManager.createQuery( u, Product.class ); + query.getResultList(); + } ); + } + + @Entity(name = "ProductNaturalId") + public class ProductNaturalId { + @Id + private String naturalId; + @OneToOne(optional = false) + private Product product; + } + + @Entity(name = "Product") + public class Product { + @Id + private Long id; + @OneToOne(mappedBy = "product", cascade = ALL, fetch = FetchType.LAZY) + private ProductDetail productDetail; + @OneToOne(mappedBy = "product", cascade = ALL, fetch = FetchType.LAZY) + private ProductNaturalId naturalId; + @ElementCollection(fetch = FetchType.LAZY) + private Map normalizedPricesByUnit = new HashMap<>(); + } + + @Entity(name = "ProductDetail") + public class ProductDetail { + @Id + private Long id; + @OneToOne(optional = false) + @JoinColumn(name = "id") + @MapsId + private Product product; + } +} diff --git a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/xml/JpaDescriptorParser.java b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/xml/JpaDescriptorParser.java index 1de95db579..71cad464e8 100644 --- a/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/xml/JpaDescriptorParser.java +++ b/tooling/metamodel-generator/src/main/java/org/hibernate/jpamodelgen/xml/JpaDescriptorParser.java @@ -108,7 +108,7 @@ public class JpaDescriptorParser { private Persistence getPersistence() { Persistence persistence = null; String persistenceXmlLocation = context.getPersistenceXmlLocation(); - InputStream stream = xmlParserHelper.getInputStreamForResource( persistenceXmlLocation ); + final InputStream stream = xmlParserHelper.getInputStreamForResource( persistenceXmlLocation ); if ( stream == null ) { return null; } @@ -122,12 +122,13 @@ public class JpaDescriptorParser { Diagnostic.Kind.WARNING, "Unable to parse persistence.xml: " + e.getMessage() ); } - - try { - stream.close(); - } - catch (IOException e) { - // eat it + finally { + try { + stream.close(); + } + catch (IOException e) { + // eat it + } } return persistence; @@ -135,29 +136,29 @@ public class JpaDescriptorParser { private void loadEntityMappings(Collection mappingFileNames) { for ( String mappingFile : mappingFileNames ) { - InputStream stream = xmlParserHelper.getInputStreamForResource( mappingFile ); + final InputStream stream = xmlParserHelper.getInputStreamForResource( mappingFile ); if ( stream == null ) { continue; } - EntityMappings mapping = null; try { - Schema schema = xmlParserHelper.getSchema( ORM_SCHEMA ); - mapping = xmlParserHelper.getJaxbRoot( stream, EntityMappings.class, schema ); + final Schema schema = xmlParserHelper.getSchema( ORM_SCHEMA ); + final EntityMappings mapping = xmlParserHelper.getJaxbRoot( stream, EntityMappings.class, schema ); + if ( mapping != null ) { + entityMappings.add( mapping ); + } } catch (XmlParsingException e) { context.logMessage( Diagnostic.Kind.WARNING, "Unable to parse " + mappingFile + ": " + e.getMessage() ); } - if ( mapping != null ) { - entityMappings.add( mapping ); - } - - try { - stream.close(); - } - catch (IOException e) { - // eat it + finally { + try { + stream.close(); + } + catch (IOException e) { + // eat it + } } } }