Skip to content

Commit

Permalink
Merge pull request #31574 from geoand/#31570
Browse files Browse the repository at this point in the history
Support multiple levels of repository interfaces in Spring Data JPA
  • Loading branch information
geoand authored Mar 3, 2023
2 parents c9d1909 + 646d13d commit e0c386f
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
Expand Down Expand Up @@ -95,15 +95,15 @@ IgnorableNonIndexedClasses ignorable() {

@BuildStep
void registerReflection(BuildProducer<ReflectiveClassBuildItem> producer) {
producer.produce(new ReflectiveClassBuildItem(true, false,
producer.produce(ReflectiveClassBuildItem.builder(
"org.springframework.data.domain.Page",
"org.springframework.data.domain.Slice",
"org.springframework.data.domain.PageImpl",
"org.springframework.data.domain.SliceImpl",
"org.springframework.data.domain.Sort",
"org.springframework.data.domain.Chunk",
"org.springframework.data.domain.PageRequest",
"org.springframework.data.domain.AbstractPageRequest"));
"org.springframework.data.domain.AbstractPageRequest").methods(true).fields(false).build());
}

@BuildStep
Expand All @@ -118,7 +118,7 @@ void build(CombinedIndexBuildItem index,
detectAndLogSpecificSpringPropertiesIfExist();

IndexView indexView = index.getIndex();
List<ClassInfo> interfacesExtendingRepository = getAllInterfacesExtending(DotNames.SUPPORTED_REPOSITORIES,
LinkedHashSet<ClassInfo> interfacesExtendingRepository = getAllInterfacesExtending(DotNames.SUPPORTED_REPOSITORIES,
indexView);

addRepositoryDefinitionInstances(indexView, interfacesExtendingRepository);
Expand All @@ -134,21 +134,15 @@ void build(CombinedIndexBuildItem index,
}

private void addInterfacesExtendingIntermediateRepositories(IndexView indexView,
List<ClassInfo> interfacesExtendingRepository) {
Set<ClassInfo> interfacesExtendingRepository) {
Collection<DotName> noRepositoryBeanRepos = getAllNoRepositoryBeanInterfaces(indexView);
Iterator<DotName> iterator = noRepositoryBeanRepos.iterator();
while (iterator.hasNext()) {
DotName interfaceName = iterator.next();
if (DotNames.SUPPORTED_REPOSITORIES.contains(interfaceName)) {
iterator.remove();
}
}
List<ClassInfo> interfacesExtending = getAllInterfacesExtending(noRepositoryBeanRepos, indexView);
noRepositoryBeanRepos.removeIf(DotNames.SUPPORTED_REPOSITORIES::contains);
Set<ClassInfo> interfacesExtending = getAllInterfacesExtending(noRepositoryBeanRepos, indexView);
interfacesExtendingRepository.addAll(interfacesExtending);
}

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

private void removeNoRepositoryBeanClasses(List<ClassInfo> interfacesExtendingRepository) {
Iterator<ClassInfo> iterator = interfacesExtendingRepository.iterator();
while (iterator.hasNext()) {
ClassInfo next = iterator.next();
if (next.classAnnotation(DotNames.SPRING_DATA_NO_REPOSITORY_BEAN) != null) {
iterator.remove();
}
}
private void removeNoRepositoryBeanClasses(Set<ClassInfo> interfacesExtendingRepository) {
interfacesExtendingRepository.removeIf(
next -> next.declaredAnnotation(DotNames.SPRING_DATA_NO_REPOSITORY_BEAN) != null);
}

// inefficient implementation, see: https://github.com/wildfly/jandex/issues/65
private List<ClassInfo> getAllInterfacesExtending(Collection<DotName> targets, IndexView index) {
List<ClassInfo> result = new ArrayList<>();
Collection<ClassInfo> knownClasses = index.getKnownClasses();
for (ClassInfo clazz : knownClasses) {
if (!Modifier.isInterface(clazz.flags())) {
continue;
}
List<DotName> interfaceNames = clazz.interfaceNames();
boolean found = false;
for (DotName interfaceName : interfaceNames) {
if (targets.contains(interfaceName)) {
found = true;
break;
}
}
if (found) {
result.add(clazz);
}
private LinkedHashSet<ClassInfo> getAllInterfacesExtending(Collection<DotName> targets, IndexView index) {
LinkedHashSet<ClassInfo> result = new LinkedHashSet<>();
for (DotName target : targets) {
result.addAll(index.getAllKnownSubinterfaces(target));
}
return result;
}
Expand All @@ -269,7 +243,7 @@ private Set<String> implementCrudRepositories(BuildProducer<GeneratedBeanBuildIt
BuildProducer<GeneratedClassBuildItem> generatedClasses,
BuildProducer<AdditionalBeanBuildItem> additionalBeans,
BuildProducer<ReflectiveClassBuildItem> reflectiveClasses,
List<ClassInfo> crudRepositoriesToImplement, IndexView index) {
Set<ClassInfo> crudRepositoriesToImplement, IndexView index) {

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

Set<String> entities = new HashSet<>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;

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

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

//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
for (DotName extendedInterface : repositoryClassInfo.interfaceNames()) {
if (GenerationUtil.isIntermediateRepository(extendedInterface, index)) {
List<MethodInfo> methods = index.getClassByName(extendedInterface).methods();
repoMethods.addAll(methods);
}
addAllMethodOfIntermediateRepository(extendedInterface, repoMethods);
}
for (MethodInfo method : repoMethods) {
if (method.annotation(DotNames.SPRING_DATA_QUERY) != null) { // handled by CustomQueryMethodsAdder
Expand Down Expand Up @@ -288,6 +287,17 @@ public void add(ClassCreator classCreator, FieldDescriptor entityClassFieldDescr
}
}

private void addAllMethodOfIntermediateRepository(DotName interfaceDotName, Set<MethodInfo> result) {
if (GenerationUtil.isIntermediateRepository(interfaceDotName, index)) {
ClassInfo classInfo = index.getClassByName(interfaceDotName);
List<MethodInfo> methods = classInfo.methods();
result.addAll(methods);
for (DotName superInterface : classInfo.interfaceNames()) {
addAllMethodOfIntermediateRepository(superInterface, result);
}
}
}

private void generateCustomResultTypes(DotName interfaceName, DotName implName, ClassInfo entityClassInfo,
List<String> queryMethods) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,17 @@ static List<DotName> extendedSpringDataRepos(ClassInfo repositoryToImplement, In
if (DotNames.SUPPORTED_REPOSITORIES.contains(interfaceName)) {
result.add(interfaceName);
} else {
ClassInfo intermediateInterfaces = index.getClassByName(interfaceName);
List<DotName> dns = intermediateInterfaces.interfaceNames();
for (DotName in : dns) {
result.addAll(extendedSpringDataRepos(intermediateInterfaces, index));
}
result.addAll(extendedSpringDataRepos(index.getClassByName(interfaceName), index));
}
}
return result;
}

static boolean isIntermediateRepository(DotName interfaceName, IndexView indexView) {
if (!DotNames.SUPPORTED_REPOSITORIES.contains(interfaceName)) {
ClassInfo intermediateInterface = indexView.getClassByName(interfaceName);
List<DotName> extendedSpringDataRepos = extendedSpringDataRepos(intermediateInterface, indexView);
return DotNames.SUPPORTED_REPOSITORIES.stream().anyMatch(item -> extendedSpringDataRepos.contains(item));
if (DotNames.SUPPORTED_REPOSITORIES.contains(interfaceName)) {
return false;
}
return false;
return !extendedSpringDataRepos(indexView.getClassByName(interfaceName), indexView).isEmpty();
}

static Set<MethodInfo> interfaceMethods(Collection<DotName> interfaces, IndexView index) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.quarkus.it.spring.data.jpa;

public interface ByPassHolder {

boolean isBypass();
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import org.springframework.data.repository.NoRepositoryBean;

@NoRepositoryBean
public interface IntermediateRepository<T, ID extends Serializable> extends JpaRepository<T, ID> {
public interface BypassHolderRepository<T extends ByPassHolder, ID extends Serializable> extends JpaRepository<T, ID> {

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

Post findFirstByBypassTrue();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.quarkus.it.spring.data.jpa;

import java.time.ZonedDateTime;
import java.util.List;

/**
* Used to ensure that entity relationships work correctly
*/
public interface IntermediatePostRepository extends BypassHolderRepository<Post, Long> {

List<Post> findByPostedBefore(ZonedDateTime zdt);
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

@Entity(name = "Post")
@Table(name = "post")
public class Post {
public class Post implements ByPassHolder {

@Id
@SequenceGenerator(name = "postSeqGen", sequenceName = "postSeq", initialValue = 100, allocationSize = 1)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package io.quarkus.it.spring.data.jpa;

import java.time.ZonedDateTime;
import java.util.List;

/**
* Used to ensure that entity relationships work correctly
*/
public interface PostRepository extends IntermediateRepository<Post, Long> {

Post findFirstByBypassTrue();

List<Post> findByPostedBefore(ZonedDateTime zdt);
public interface PostRepository extends IntermediatePostRepository {

List<Post> findAllByOrganization(String organization);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,3 @@ quarkus.native.additional-build-args=--initialize-at-run-time=io.quarkus.it.spri
#quarkus.hibernate-orm.log.sql=true

%prod.quarkus.hibernate-orm.sql-load-script=import.sql

# added only to ensure that building the native binary with this flag works properly
quarkus.native.inline-before-analysis=true

0 comments on commit e0c386f

Please sign in to comment.