Skip to content

Commit 1d6d833

Browse files
Merge branch 'spring-projects:main' into feat/2409_intro_JSQLParses
2 parents 9c614a8 + 92c8b8a commit 1d6d833

File tree

7 files changed

+109
-3
lines changed

7 files changed

+109
-3
lines changed

src/main/java/org/springframework/data/jpa/mapping/JpaPersistentPropertyImpl.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public JpaPersistentPropertyImpl(JpaMetamodel metamodel, Property property,
109109

110110
this.isIdProperty = Lazy.of(() -> ID_ANNOTATIONS.stream().anyMatch(it -> isAnnotationPresent(it)) //
111111
|| metamodel.isSingleIdAttribute(getOwner().getType(), getName(), getType()));
112-
this.isEntity = Lazy.of(() -> metamodel.isJpaManaged(getActualType()));
112+
this.isEntity = Lazy.of(() -> metamodel.isMappedType(getActualType()));
113113
}
114114

115115
/*

src/main/java/org/springframework/data/jpa/repository/query/JpaQueryCreator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ protected CriteriaQuery<? extends Object> complete(@Nullable Predicate predicate
176176
selections.add(toExpressionRecursively(root, path, true).alias(property));
177177
}
178178

179-
Class<?> typeToRead = returnedType.getTypeToRead();
179+
Class<?> typeToRead = returnedType.getReturnedType();
180180

181181
query = typeToRead.isInterface()
182182
? query.multiselect(selections)

src/main/java/org/springframework/data/jpa/util/JpaMetamodel.java

+36
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,20 @@
1616
package org.springframework.data.jpa.util;
1717

1818
import java.util.Collection;
19+
import java.util.EnumSet;
1920
import java.util.Map;
2021
import java.util.Optional;
22+
import java.util.Set;
2123
import java.util.concurrent.ConcurrentHashMap;
2224

25+
import javax.persistence.Embeddable;
2326
import javax.persistence.metamodel.EntityType;
2427
import javax.persistence.metamodel.ManagedType;
2528
import javax.persistence.metamodel.Metamodel;
2629
import javax.persistence.metamodel.SingularAttribute;
30+
import javax.persistence.metamodel.Type.PersistenceType;
2731

32+
import org.springframework.core.annotation.AnnotatedElementUtils;
2833
import org.springframework.data.util.Lazy;
2934
import org.springframework.data.util.StreamUtils;
3035
import org.springframework.util.Assert;
@@ -39,10 +44,13 @@
3944
public class JpaMetamodel {
4045

4146
private static final Map<Metamodel, JpaMetamodel> CACHE = new ConcurrentHashMap<>(4);
47+
private static final Set<PersistenceType> ENTITY_OR_MAPPED_SUPERCLASS = EnumSet.of(PersistenceType.ENTITY,
48+
PersistenceType.MAPPED_SUPERCLASS);
4249

4350
private final Metamodel metamodel;
4451

4552
private Lazy<Collection<Class<?>>> managedTypes;
53+
private Lazy<Collection<Class<?>>> jpaEmbeddables;
4654

4755
/**
4856
* Creates a new {@link JpaMetamodel} for the given JPA {@link Metamodel}.
@@ -54,10 +62,17 @@ private JpaMetamodel(Metamodel metamodel) {
5462
Assert.notNull(metamodel, "Metamodel must not be null!");
5563

5664
this.metamodel = metamodel;
65+
5766
this.managedTypes = Lazy.of(() -> metamodel.getManagedTypes().stream() //
5867
.map(ManagedType::getJavaType) //
5968
.filter(it -> it != null) //
6069
.collect(StreamUtils.toUnmodifiableSet()));
70+
71+
this.jpaEmbeddables = Lazy.of(() -> metamodel.getEmbeddables().stream() //
72+
.map(ManagedType::getJavaType)
73+
.filter(it -> it != null)
74+
.filter(it -> AnnotatedElementUtils.isAnnotated(it, Embeddable.class))
75+
.collect(StreamUtils.toUnmodifiableSet()));
6176
}
6277

6378
public static JpaMetamodel of(Metamodel metamodel) {
@@ -96,6 +111,27 @@ public boolean isSingleIdAttribute(Class<?> entity, String name, Class<?> attrib
96111
.orElse(false);
97112
}
98113

114+
/**
115+
* Returns whether the given type is considered a mapped type, i.e. an actually JPA persisted entity, mapped
116+
* superclass or native JPA embeddable.
117+
*
118+
* @param entity must not be {@literal null}.
119+
* @return
120+
*/
121+
public boolean isMappedType(Class<?> entity) {
122+
123+
Assert.notNull(entity, "Type must not be null!");
124+
125+
if (!isJpaManaged(entity)) {
126+
return false;
127+
}
128+
129+
ManagedType<?> managedType = metamodel.managedType(entity);
130+
131+
return !managedType.getPersistenceType().equals(PersistenceType.EMBEDDABLE)
132+
|| jpaEmbeddables.get().contains(entity);
133+
}
134+
99135
/**
100136
* Wipes the static cache of {@link Metamodel} to {@link JpaMetamodel}.
101137
*/

src/test/java/org/springframework/data/jpa/repository/UserRepositoryTests.java

+10
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@
5757
import org.springframework.dao.InvalidDataAccessApiUsageException;
5858
import org.springframework.data.domain.Example;
5959
import org.springframework.data.domain.ExampleMatcher;
60+
import org.springframework.data.domain.ExampleMatcher.GenericPropertyMatcher;
61+
import org.springframework.data.domain.ExampleMatcher.StringMatcher;
6062
import org.springframework.data.domain.Page;
6163
import org.springframework.data.domain.PageImpl;
6264
import org.springframework.data.domain.PageRequest;
@@ -2632,6 +2634,14 @@ void readsDtoProjections() {
26322634
assertThat(repository.findAllDtoProjectedBy()).hasSize(4);
26332635
}
26342636

2637+
@Test // GH-2408, GH-2363
2638+
void readsDerivedInterfaceProjections() {
2639+
2640+
flushTestUsers();
2641+
2642+
assertThat(repository.findAllInterfaceProjectedBy()).hasSize(4);
2643+
}
2644+
26352645
private Page<User> executeSpecWithSort(Sort sort) {
26362646

26372647
flushTestUsers();

src/test/java/org/springframework/data/jpa/repository/query/PartTreeJpaQueryIntegrationTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ void errorsDueToMissingPropertyContainNameOfMethodAndInterface() throws Exceptio
222222
assertThatExceptionOfType(IllegalArgumentException.class) //
223223
.isThrownBy(() -> new PartTreeJpaQuery(method, entityManager)) //
224224
.withMessageContaining("findByNoSuchProperty") // the method being analyzed
225-
.withMessageContaining(" noSuchProperty ") // the property we are looking for
225+
.withMessageContaining("'noSuchProperty'") // the property we are looking for
226226
.withMessageContaining("UserRepository"); // the repository
227227
}
228228

src/test/java/org/springframework/data/jpa/repository/sample/UserRepository.java

+3
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,9 @@ Page<User> findAllOrderedBySpecialNameMultipleParams(@Param("name") String name,
614614
// #2363
615615
List<NameOnlyDto> findAllDtoProjectedBy();
616616

617+
// GH-2408
618+
List<NameOnly> findAllInterfaceProjectedBy();
619+
617620
interface RolesAndFirstname {
618621

619622
String getFirstname();

src/test/java/org/springframework/data/jpa/util/JpaMetamodelUnitTests.java

+57
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,17 @@
1818
import static org.assertj.core.api.Assertions.*;
1919
import static org.mockito.Mockito.*;
2020

21+
import java.util.Arrays;
2122
import java.util.Collections;
23+
import java.util.HashSet;
2224

25+
import javax.persistence.Embeddable;
26+
import javax.persistence.Entity;
27+
import javax.persistence.metamodel.EmbeddableType;
2328
import javax.persistence.metamodel.EntityType;
29+
import javax.persistence.metamodel.ManagedType;
2430
import javax.persistence.metamodel.Metamodel;
31+
import javax.persistence.metamodel.Type.PersistenceType;
2532

2633
import org.junit.jupiter.api.Test;
2734
import org.junit.jupiter.api.extension.ExtendWith;
@@ -60,4 +67,54 @@ void cacheIsEffectiveUnlessCleared() {
6067
JpaMetamodel.clear();
6168
assertThat(model).isNotEqualTo(JpaMetamodel.of(metamodel));
6269
}
70+
71+
@Test // #2421
72+
void doesNotConsiderNonNativeEmbeddablesJpaManaged() {
73+
74+
JpaMetamodel model = JpaMetamodel.of(metamodel);
75+
76+
ManagedType<?> entity = getEntity(Wrapper.class);
77+
ManagedType<?> embeddable = getEmbeddable(ExplicitEmbeddable.class);
78+
ManagedType<?> inner = getEmbeddable(Inner.class);
79+
80+
doReturn(new HashSet<>(Arrays.asList(entity, embeddable, inner))).when(metamodel).getManagedTypes();
81+
doReturn(new HashSet<>(Arrays.asList(embeddable, inner))).when(metamodel).getEmbeddables();
82+
83+
assertThat(model.isMappedType(Wrapper.class)).isTrue();
84+
assertThat(model.isMappedType(ExplicitEmbeddable.class)).isTrue();
85+
assertThat(model.isMappedType(Inner.class)).isFalse();
86+
}
87+
88+
private EmbeddableType<?> getEmbeddable(Class<?> type) {
89+
90+
EmbeddableType<?> managedType = getManagedType(type, EmbeddableType.class);
91+
doReturn(PersistenceType.EMBEDDABLE).when(managedType).getPersistenceType();
92+
93+
return managedType;
94+
}
95+
96+
private EntityType<?> getEntity(Class<?> type) {
97+
98+
EntityType<?> managedType = getManagedType(type, EntityType.class);
99+
doReturn(PersistenceType.ENTITY).when(managedType).getPersistenceType();
100+
101+
return managedType;
102+
}
103+
104+
private <T extends ManagedType<?>> T getManagedType(Class<?> type, Class<T> baseType) {
105+
106+
T managedType = mock(baseType);
107+
doReturn(type).when(managedType).getJavaType();
108+
doReturn(managedType).when(metamodel).managedType(type);
109+
110+
return managedType;
111+
}
112+
113+
@Entity
114+
static class Wrapper {}
115+
116+
@Embeddable
117+
static class ExplicitEmbeddable {}
118+
119+
static class Inner {}
63120
}

0 commit comments

Comments
 (0)