Skip to content

Commit e0c386f

Browse files
authored
Merge pull request #31574 from geoand/#31570
Support multiple levels of repository interfaces in Spring Data JPA
2 parents c9d1909 + 646d13d commit e0c386f

File tree

9 files changed

+58
-69
lines changed

9 files changed

+58
-69
lines changed

extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/SpringDataJPAProcessor.java

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import java.util.ArrayList;
88
import java.util.Collection;
99
import java.util.HashSet;
10-
import java.util.Iterator;
10+
import java.util.LinkedHashSet;
1111
import java.util.List;
1212
import java.util.Optional;
1313
import java.util.Set;
@@ -95,15 +95,15 @@ IgnorableNonIndexedClasses ignorable() {
9595

9696
@BuildStep
9797
void registerReflection(BuildProducer<ReflectiveClassBuildItem> producer) {
98-
producer.produce(new ReflectiveClassBuildItem(true, false,
98+
producer.produce(ReflectiveClassBuildItem.builder(
9999
"org.springframework.data.domain.Page",
100100
"org.springframework.data.domain.Slice",
101101
"org.springframework.data.domain.PageImpl",
102102
"org.springframework.data.domain.SliceImpl",
103103
"org.springframework.data.domain.Sort",
104104
"org.springframework.data.domain.Chunk",
105105
"org.springframework.data.domain.PageRequest",
106-
"org.springframework.data.domain.AbstractPageRequest"));
106+
"org.springframework.data.domain.AbstractPageRequest").methods(true).fields(false).build());
107107
}
108108

109109
@BuildStep
@@ -118,7 +118,7 @@ void build(CombinedIndexBuildItem index,
118118
detectAndLogSpecificSpringPropertiesIfExist();
119119

120120
IndexView indexView = index.getIndex();
121-
List<ClassInfo> interfacesExtendingRepository = getAllInterfacesExtending(DotNames.SUPPORTED_REPOSITORIES,
121+
LinkedHashSet<ClassInfo> interfacesExtendingRepository = getAllInterfacesExtending(DotNames.SUPPORTED_REPOSITORIES,
122122
indexView);
123123

124124
addRepositoryDefinitionInstances(indexView, interfacesExtendingRepository);
@@ -134,21 +134,15 @@ void build(CombinedIndexBuildItem index,
134134
}
135135

136136
private void addInterfacesExtendingIntermediateRepositories(IndexView indexView,
137-
List<ClassInfo> interfacesExtendingRepository) {
137+
Set<ClassInfo> interfacesExtendingRepository) {
138138
Collection<DotName> noRepositoryBeanRepos = getAllNoRepositoryBeanInterfaces(indexView);
139-
Iterator<DotName> iterator = noRepositoryBeanRepos.iterator();
140-
while (iterator.hasNext()) {
141-
DotName interfaceName = iterator.next();
142-
if (DotNames.SUPPORTED_REPOSITORIES.contains(interfaceName)) {
143-
iterator.remove();
144-
}
145-
}
146-
List<ClassInfo> interfacesExtending = getAllInterfacesExtending(noRepositoryBeanRepos, indexView);
139+
noRepositoryBeanRepos.removeIf(DotNames.SUPPORTED_REPOSITORIES::contains);
140+
Set<ClassInfo> interfacesExtending = getAllInterfacesExtending(noRepositoryBeanRepos, indexView);
147141
interfacesExtendingRepository.addAll(interfacesExtending);
148142
}
149143

150144
// classes annotated with @RepositoryDefinition behave exactly as if they extended Repository
151-
private void addRepositoryDefinitionInstances(IndexView indexView, List<ClassInfo> interfacesExtendingRepository) {
145+
private void addRepositoryDefinitionInstances(IndexView indexView, Set<ClassInfo> interfacesExtendingRepository) {
152146
Collection<AnnotationInstance> repositoryDefinitions = indexView
153147
.getAnnotations(DotNames.SPRING_DATA_REPOSITORY_DEFINITION);
154148
for (AnnotationInstance repositoryDefinition : repositoryDefinitions) {
@@ -223,35 +217,15 @@ private void detectAndLogSpecificSpringPropertiesIfExist() {
223217
}
224218
}
225219

226-
private void removeNoRepositoryBeanClasses(List<ClassInfo> interfacesExtendingRepository) {
227-
Iterator<ClassInfo> iterator = interfacesExtendingRepository.iterator();
228-
while (iterator.hasNext()) {
229-
ClassInfo next = iterator.next();
230-
if (next.classAnnotation(DotNames.SPRING_DATA_NO_REPOSITORY_BEAN) != null) {
231-
iterator.remove();
232-
}
233-
}
220+
private void removeNoRepositoryBeanClasses(Set<ClassInfo> interfacesExtendingRepository) {
221+
interfacesExtendingRepository.removeIf(
222+
next -> next.declaredAnnotation(DotNames.SPRING_DATA_NO_REPOSITORY_BEAN) != null);
234223
}
235224

236-
// inefficient implementation, see: https://github.com/wildfly/jandex/issues/65
237-
private List<ClassInfo> getAllInterfacesExtending(Collection<DotName> targets, IndexView index) {
238-
List<ClassInfo> result = new ArrayList<>();
239-
Collection<ClassInfo> knownClasses = index.getKnownClasses();
240-
for (ClassInfo clazz : knownClasses) {
241-
if (!Modifier.isInterface(clazz.flags())) {
242-
continue;
243-
}
244-
List<DotName> interfaceNames = clazz.interfaceNames();
245-
boolean found = false;
246-
for (DotName interfaceName : interfaceNames) {
247-
if (targets.contains(interfaceName)) {
248-
found = true;
249-
break;
250-
}
251-
}
252-
if (found) {
253-
result.add(clazz);
254-
}
225+
private LinkedHashSet<ClassInfo> getAllInterfacesExtending(Collection<DotName> targets, IndexView index) {
226+
LinkedHashSet<ClassInfo> result = new LinkedHashSet<>();
227+
for (DotName target : targets) {
228+
result.addAll(index.getAllKnownSubinterfaces(target));
255229
}
256230
return result;
257231
}
@@ -269,7 +243,7 @@ private Set<String> implementCrudRepositories(BuildProducer<GeneratedBeanBuildIt
269243
BuildProducer<GeneratedClassBuildItem> generatedClasses,
270244
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
271245
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
272-
List<ClassInfo> crudRepositoriesToImplement, IndexView index) {
246+
Set<ClassInfo> crudRepositoriesToImplement, IndexView index) {
273247

274248
ClassOutput beansClassOutput = new GeneratedBeanGizmoAdaptor(generatedBeans);
275249
ClassOutput otherClassOutput = new GeneratedClassGizmoAdaptor(generatedClasses, true);
@@ -282,7 +256,7 @@ private Set<String> implementCrudRepositories(BuildProducer<GeneratedBeanBuildIt
282256
(className -> {
283257
// the generated classes that implement interfaces for holding custom query results need
284258
// to be registered for reflection here since this is the only point where the generated class is known
285-
reflectiveClasses.produce(new ReflectiveClassBuildItem(true, false, className));
259+
reflectiveClasses.produce(ReflectiveClassBuildItem.builder(className).methods(true).fields(false).build());
286260
}), JavaJpaTypeBundle.BUNDLE);
287261

288262
Set<String> entities = new HashSet<>();

extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/DerivedMethodsAdder.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@
66
import java.lang.reflect.Modifier;
77
import java.util.ArrayList;
88
import java.util.HashMap;
9+
import java.util.LinkedHashSet;
910
import java.util.List;
1011
import java.util.Map;
12+
import java.util.Set;
1113
import java.util.function.Consumer;
1214

1315
import jakarta.transaction.Transactional;
@@ -56,18 +58,15 @@ public DerivedMethodsAdder(IndexView index, TypeBundle typeBundle, ClassOutput n
5658
public void add(ClassCreator classCreator, FieldDescriptor entityClassFieldDescriptor,
5759
String generatedClassName, ClassInfo repositoryClassInfo, ClassInfo entityClassInfo) {
5860
MethodNameParser methodNameParser = new MethodNameParser(entityClassInfo, index);
59-
List<MethodInfo> repoMethods = new ArrayList<>(repositoryClassInfo.methods());
61+
LinkedHashSet<MethodInfo> repoMethods = new LinkedHashSet<>(repositoryClassInfo.methods());
6062

6163
// Remember custom return type methods: {resultType:[methodName]}
6264
Map<DotName, List<String>> customResultTypes = new HashMap<>(3);
6365
Map<DotName, DotName> customResultTypeImplNames = new HashMap<>(3);
6466

6567
//As intermediate interfaces are supported for spring data repositories, we need to search the methods declared in such interfaced and add them to the methods to implement list
6668
for (DotName extendedInterface : repositoryClassInfo.interfaceNames()) {
67-
if (GenerationUtil.isIntermediateRepository(extendedInterface, index)) {
68-
List<MethodInfo> methods = index.getClassByName(extendedInterface).methods();
69-
repoMethods.addAll(methods);
70-
}
69+
addAllMethodOfIntermediateRepository(extendedInterface, repoMethods);
7170
}
7271
for (MethodInfo method : repoMethods) {
7372
if (method.annotation(DotNames.SPRING_DATA_QUERY) != null) { // handled by CustomQueryMethodsAdder
@@ -288,6 +287,17 @@ public void add(ClassCreator classCreator, FieldDescriptor entityClassFieldDescr
288287
}
289288
}
290289

290+
private void addAllMethodOfIntermediateRepository(DotName interfaceDotName, Set<MethodInfo> result) {
291+
if (GenerationUtil.isIntermediateRepository(interfaceDotName, index)) {
292+
ClassInfo classInfo = index.getClassByName(interfaceDotName);
293+
List<MethodInfo> methods = classInfo.methods();
294+
result.addAll(methods);
295+
for (DotName superInterface : classInfo.interfaceNames()) {
296+
addAllMethodOfIntermediateRepository(superInterface, result);
297+
}
298+
}
299+
}
300+
291301
private void generateCustomResultTypes(DotName interfaceName, DotName implName, ClassInfo entityClassInfo,
292302
List<String> queryMethods) {
293303

extensions/spring-data-jpa/deployment/src/main/java/io/quarkus/spring/data/deployment/generate/GenerationUtil.java

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,17 @@ static List<DotName> extendedSpringDataRepos(ClassInfo repositoryToImplement, In
3131
if (DotNames.SUPPORTED_REPOSITORIES.contains(interfaceName)) {
3232
result.add(interfaceName);
3333
} else {
34-
ClassInfo intermediateInterfaces = index.getClassByName(interfaceName);
35-
List<DotName> dns = intermediateInterfaces.interfaceNames();
36-
for (DotName in : dns) {
37-
result.addAll(extendedSpringDataRepos(intermediateInterfaces, index));
38-
}
34+
result.addAll(extendedSpringDataRepos(index.getClassByName(interfaceName), index));
3935
}
4036
}
4137
return result;
4238
}
4339

4440
static boolean isIntermediateRepository(DotName interfaceName, IndexView indexView) {
45-
if (!DotNames.SUPPORTED_REPOSITORIES.contains(interfaceName)) {
46-
ClassInfo intermediateInterface = indexView.getClassByName(interfaceName);
47-
List<DotName> extendedSpringDataRepos = extendedSpringDataRepos(intermediateInterface, indexView);
48-
return DotNames.SUPPORTED_REPOSITORIES.stream().anyMatch(item -> extendedSpringDataRepos.contains(item));
41+
if (DotNames.SUPPORTED_REPOSITORIES.contains(interfaceName)) {
42+
return false;
4943
}
50-
return false;
44+
return !extendedSpringDataRepos(indexView.getClassByName(interfaceName), indexView).isEmpty();
5145
}
5246

5347
static Set<MethodInfo> interfaceMethods(Collection<DotName> interfaces, IndexView index) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package io.quarkus.it.spring.data.jpa;
2+
3+
public interface ByPassHolder {
4+
5+
boolean isBypass();
6+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import org.springframework.data.repository.NoRepositoryBean;
77

88
@NoRepositoryBean
9-
public interface IntermediateRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
9+
public interface BypassHolderRepository<T extends ByPassHolder, ID extends Serializable> extends JpaRepository<T, ID> {
1010

1111
default public void doNothing() {
1212
}
@@ -15,4 +15,5 @@ default public T findMandatoryById(ID id) {
1515
return findById(id).orElseThrow(() -> new IllegalStateException("not found: " + id));
1616
}
1717

18+
Post findFirstByBypassTrue();
1819
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.quarkus.it.spring.data.jpa;
2+
3+
import java.time.ZonedDateTime;
4+
import java.util.List;
5+
6+
/**
7+
* Used to ensure that entity relationships work correctly
8+
*/
9+
public interface IntermediatePostRepository extends BypassHolderRepository<Post, Long> {
10+
11+
List<Post> findByPostedBefore(ZonedDateTime zdt);
12+
}

integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/Post.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
@Entity(name = "Post")
2020
@Table(name = "post")
21-
public class Post {
21+
public class Post implements ByPassHolder {
2222

2323
@Id
2424
@SequenceGenerator(name = "postSeqGen", sequenceName = "postSeq", initialValue = 100, allocationSize = 1)

integration-tests/spring-data-jpa/src/main/java/io/quarkus/it/spring/data/jpa/PostRepository.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
package io.quarkus.it.spring.data.jpa;
22

3-
import java.time.ZonedDateTime;
43
import java.util.List;
54

65
/**
76
* Used to ensure that entity relationships work correctly
87
*/
9-
public interface PostRepository extends IntermediateRepository<Post, Long> {
10-
11-
Post findFirstByBypassTrue();
12-
13-
List<Post> findByPostedBefore(ZonedDateTime zdt);
8+
public interface PostRepository extends IntermediatePostRepository {
149

1510
List<Post> findAllByOrganization(String organization);
1611

integration-tests/spring-data-jpa/src/main/resources/application.properties

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,3 @@ quarkus.native.additional-build-args=--initialize-at-run-time=io.quarkus.it.spri
1111
#quarkus.hibernate-orm.log.sql=true
1212

1313
%prod.quarkus.hibernate-orm.sql-load-script=import.sql
14-
15-
# added only to ensure that building the native binary with this flag works properly
16-
quarkus.native.inline-before-analysis=true

0 commit comments

Comments
 (0)