From 1be3e5eb1eb418590776ee77d11cb0fb0e4535b4 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Mon, 18 Dec 2023 10:49:54 +0800 Subject: [PATCH] Fallback to Path.getModel() if ManagedType is erased type ManagedType may be erased type if the attribute is declared as generic, take Hibernate 6.x for example, exception is thrown like: ``` java.lang.IllegalArgumentException: Unable to locate Attribute with the given name [name] on this ManagedType [java.lang.Object] at org.hibernate.metamodel.model.domain.AbstractManagedType.checkNotNull(AbstractManagedType.java:225) ~[hibernate-core-6.3.1.Final.jar:6.3.1.Final] at org.hibernate.metamodel.model.domain.AbstractManagedType.getAttribute(AbstractManagedType.java:148) ~[hibernate-core-6.3.1.Final.jar:6.3.1.Final] at org.hibernate.metamodel.model.domain.AbstractManagedType.getAttribute(AbstractManagedType.java:43) ~[hibernate-core-6.3.1.Final.jar:6.3.1.Final] at org.springframework.data.jpa.repository.query.QueryUtils.requiresOuterJoin(QueryUtils.java:836) ~[spring-data-jpa-3.2.0.jar:3.2.0] ``` Fix GH-3274 Fix GH-3307 --- .../data/jpa/repository/query/QueryUtils.java | 11 +++- .../data/jpa/domain/sample/Book.java | 36 +++++++++++ .../data/jpa/domain/sample/Owner.java | 36 +++++++++++ .../jpa/domain/sample/OwnerContainer.java | 37 +++++++++++ .../EclipseLinkGenericsIntegrationTests.java | 40 ++++++++++++ .../generics/GenericsIntegrationTests.java | 64 +++++++++++++++++++ .../jpa/repository/sample/BookRepository.java | 29 +++++++++ .../test/resources/META-INF/persistence.xml | 2 + .../test/resources/META-INF/persistence2.xml | 2 + 9 files changed, 256 insertions(+), 1 deletion(-) create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Book.java create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Owner.java create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OwnerContainer.java create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/EclipseLinkGenericsIntegrationTests.java create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/GenericsIntegrationTests.java create mode 100644 spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/BookRepository.java diff --git a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java index 7a4fed2ea8..e29ef48886 100644 --- a/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java +++ b/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/query/QueryUtils.java @@ -92,6 +92,7 @@ * @author Donghun Shin * @author Pranav HS * @author Eduard Dudar + * @author Yanming Zhou */ public abstract class QueryUtils { @@ -844,7 +845,15 @@ private static boolean requiresOuterJoin(From from, PropertyPath property, managedType = (ManagedType) ((SingularAttribute) model).getType(); } if (managedType != null) { - propertyPathModel = (Bindable) managedType.getAttribute(segment); + try { + propertyPathModel = (Bindable) managedType.getAttribute(segment); + } catch (IllegalArgumentException ex) { + // ManagedType may be erased type for some vendor if the attribute is declared as generic + // see: https://hibernate.atlassian.net/browse/HHH-16144 + // see: https://github.com/hibernate/hibernate-orm/pull/7630 + // see: https://github.com/jakartaee/persistence/issues/562 + propertyPathModel = from.get(segment).getModel(); + } } else { propertyPathModel = from.get(segment).getModel(); } diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Book.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Book.java new file mode 100644 index 0000000000..66ff3e375a --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Book.java @@ -0,0 +1,36 @@ +/* + * Copyright 2008-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.domain.sample; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import org.springframework.data.jpa.domain.AbstractPersistable; + +/** + * @author Yanming Zhou + */ +@Entity +public class Book extends OwnerContainer { + + @Id + @GeneratedValue + private Long id; + + public Long getId() { + return id; + } +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Owner.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Owner.java new file mode 100644 index 0000000000..7661f038a1 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/Owner.java @@ -0,0 +1,36 @@ +/* + * Copyright 2008-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.domain.sample; + +import jakarta.persistence.Entity; +import org.springframework.data.jpa.domain.AbstractPersistable; + +/** + * @author Yanming Zhou + */ +@Entity +public class Owner extends AbstractPersistable { + + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OwnerContainer.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OwnerContainer.java new file mode 100644 index 0000000000..177b8b48ba --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/domain/sample/OwnerContainer.java @@ -0,0 +1,37 @@ +/* + * Copyright 2008-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.domain.sample; + +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; + +/** + * @author Yanming Zhou + */ +@MappedSuperclass +public class OwnerContainer { + + @ManyToOne + T owner; + + public T getOwner() { + return owner; + } + + public void setOwner(T owner) { + this.owner = owner; + } +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/EclipseLinkGenericsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/EclipseLinkGenericsIntegrationTests.java new file mode 100644 index 0000000000..ed5a466545 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/EclipseLinkGenericsIntegrationTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.generics; + +import jakarta.persistence.EntityManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.domain.sample.Book; +import org.springframework.data.jpa.domain.sample.Owner; +import org.springframework.data.jpa.repository.sample.BookRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Yanming Zhou + */ +@Transactional +@ExtendWith(SpringExtension.class) +@ContextConfiguration({ "classpath:eclipselink.xml", "classpath:config/namespace-application-context.xml" }) +class EclipseLinkGenericsIntegrationTests extends GenericsIntegrationTests { + +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/GenericsIntegrationTests.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/GenericsIntegrationTests.java new file mode 100644 index 0000000000..d24f328273 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/generics/GenericsIntegrationTests.java @@ -0,0 +1,64 @@ +/* + * Copyright 2014-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.generics; + +import jakarta.persistence.EntityManager; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.domain.AbstractPersistable; +import org.springframework.data.jpa.domain.sample.Book; +import org.springframework.data.jpa.domain.sample.CustomAbstractPersistable; +import org.springframework.data.jpa.domain.sample.Owner; +import org.springframework.data.jpa.repository.sample.BookRepository; +import org.springframework.data.jpa.repository.sample.CustomAbstractPersistableRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Yanming Zhou + */ +@Transactional +@ExtendWith(SpringExtension.class) +@ContextConfiguration(locations = { "classpath:config/namespace-autoconfig-context.xml" }) +class GenericsIntegrationTests { + + @Autowired + BookRepository repository; + + @Autowired + EntityManager entityManager; + + @BeforeEach + void setUp() { + Owner owner = new Owner(); + owner.setName("owner"); + entityManager.persist(owner); + Book book = new Book(); + book.setOwner(owner); + entityManager.persist(book); + } + + @Test + void findAllByGenericAssociationProperty() { + assertThat(repository.findAllByOwnerName("owner")).hasSize(1); + } + +} diff --git a/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/BookRepository.java b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/BookRepository.java new file mode 100644 index 0000000000..dd4af2f2d6 --- /dev/null +++ b/spring-data-jpa/src/test/java/org/springframework/data/jpa/repository/sample/BookRepository.java @@ -0,0 +1,29 @@ +/* + * Copyright 2014-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.jpa.repository.sample; + +import org.springframework.data.jpa.domain.sample.Book; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +/** + * @author Yanming Zhou + */ +public interface BookRepository extends JpaRepository { + + List findAllByOwnerName(String ownerName); +} diff --git a/spring-data-jpa/src/test/resources/META-INF/persistence.xml b/spring-data-jpa/src/test/resources/META-INF/persistence.xml index c5e0e90b06..1c3be472e0 100644 --- a/spring-data-jpa/src/test/resources/META-INF/persistence.xml +++ b/spring-data-jpa/src/test/resources/META-INF/persistence.xml @@ -12,6 +12,7 @@ org.springframework.data.jpa.domain.sample.AuditableUser org.springframework.data.jpa.domain.sample.AuditableEntity org.springframework.data.jpa.domain.sample.AuditableEmbeddable + org.springframework.data.jpa.domain.sample.Book org.springframework.data.jpa.domain.sample.Category org.springframework.data.jpa.domain.sample.Child org.springframework.data.jpa.domain.sample.ConcreteType1 @@ -33,6 +34,7 @@ org.springframework.data.jpa.domain.sample.MailSender org.springframework.data.jpa.domain.sample.MailUser org.springframework.data.jpa.domain.sample.Order + org.springframework.data.jpa.domain.sample.Owner org.springframework.data.jpa.domain.sample.Parent org.springframework.data.jpa.domain.sample.PersistableWithIdClass org.springframework.data.jpa.domain.sample.PersistableWithSingleIdClass diff --git a/spring-data-jpa/src/test/resources/META-INF/persistence2.xml b/spring-data-jpa/src/test/resources/META-INF/persistence2.xml index 683457aac8..f4f7adb6b2 100644 --- a/spring-data-jpa/src/test/resources/META-INF/persistence2.xml +++ b/spring-data-jpa/src/test/resources/META-INF/persistence2.xml @@ -8,6 +8,7 @@ org.springframework.data.jpa.domain.sample.AuditableUser org.springframework.data.jpa.domain.sample.AuditableEntity org.springframework.data.jpa.domain.sample.AuditableEmbeddable + org.springframework.data.jpa.domain.sample.Book org.springframework.data.jpa.domain.sample.Category org.springframework.data.jpa.domain.sample.CustomAbstractPersistable org.springframework.data.jpa.domain.sample.EntityWithAssignedId @@ -20,6 +21,7 @@ org.springframework.data.jpa.domain.sample.Role org.springframework.data.jpa.domain.sample.Site org.springframework.data.jpa.domain.sample.SpecialUser + org.springframework.data.jpa.domain.sample.Owner org.springframework.data.jpa.domain.sample.User org.springframework.data.jpa.domain.sample.Dummy true