Skip to content

Support for (Nested) Projections #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
reda-alaoui opened this issue Jul 15, 2022 Discussed in #69 · 4 comments
Closed

Support for (Nested) Projections #70

reda-alaoui opened this issue Jul 15, 2022 Discussed in #69 · 4 comments

Comments

@reda-alaoui
Copy link
Member

Discussed in #69

Originally posted by dohorn July 15, 2022
First of all, I think this project is a super useful and necessary addition to Spring Data JPA 👍 What I would like to accomplish right now is to use this for nested projections on lazily loaded 1-To-Many Relations.

Doing this without any entity graph usually ends in a LazyInitializationException. However annotating the projection method with an entity graph solves this issue, like so:

public interface ProductDto {
    String getName();
    List<RatingDto> getRatings();
}

public interface RatingDto {
    int getRating();
}

public interface BaseProductRepository<T extends AbstractProduct> extends Repository<T, String>, EntityGraphQuerydslPredicateExecutor<T> {
    @EntityGraph(attributePaths = {"ratings"})
    Optional<ProductDto> findById(String id);
}

As I want to do this in a dynamic way, this project would suit my purposes perfectly, if you decided to add support.

@reda-alaoui
Copy link
Member Author

reda-alaoui commented Jul 18, 2022

@dohorn
Thinking more about it, I doubt Spring Data takes into account the EntityGraph passed by the annotation.
To my knowledge, Spring Data performs a multiselect to perform the query in the case of a projection.
I think that Spring data just ignores the EntityGraph that you provide via the annotation in your example:

public interface ProductDto {
    String getName();
    List<RatingDto> getRatings();
}

public interface RatingDto {
    int getRating();
}

public interface BaseProductRepository<T extends AbstractProduct> extends Repository<T, String>, EntityGraphQuerydslPredicateExecutor<T> {
    @EntityGraph(attributePaths = {"ratings"})
    Optional<ProductDto> findById(String id);
}

Can you check that please?

@reda-alaoui
Copy link
Member Author

@dohorn I am closing this for now. If you think I am missing something, tell me.

@reda-alaoui reda-alaoui closed this as not planned Won't fix, can't repro, duplicate, stale Jul 19, 2022
@SimoneFalzone
Copy link

Hello @reda-alaoui,

I've been exploring your project recently, and I must say I am interested on this feature.

I steped a little bit through the code an found this lines of code, in the method com.cosium.spring.data.jpa.entity.graph.repository.support.EntityGraphQueryHintCandidates#canApplyEntityGraph.

private boolean canApplyEntityGraph(ResolvableType repositoryMethodReturnType) {

    Class<?> resolvedReturnType = repositoryMethodReturnType.resolve();
    if (resolvedReturnType != null
        && (Void.TYPE.equals(resolvedReturnType)
            || domainClass.isAssignableFrom(resolvedReturnType))) {
      return true;
    }
    for (Class<?> genericType : repositoryMethodReturnType.resolveGenerics()) {
      if (domainClass.isAssignableFrom(genericType)) {
        return true;
      }
    }
    return false;
  }

I am not sure what the purpose is, but here if a DTO Class is the repositoryMethodReturnType, the method always returns false. If the method would return true on Dto Projections, Spring would parse it correctly in org.springframework.data.repository.query.ResultProcessor#processResult(java.lang.Object, org.springframework.core.convert.converter.Converter<java.lang.Object,java.lang.Object>)

public <T> T processResult(@Nullable Object source, Converter<Object, Object> preparingConverter) {

  	if (source == null || type.isInstance(source) || !type.isProjecting()) {
  		return (T) source;
  	}

  	Assert.notNull(preparingConverter, "Preparing converter must not be null");

  	ChainingConverter converter = ChainingConverter.of(type.getReturnedType(), preparingConverter).and(this.converter);

  	if (source instanceof Window<?> && method.isScrollQuery()) {
  		return (T) ((Window<?>) source).map(converter::convert);
  	}

  	if (source instanceof Slice && (method.isPageQuery() || method.isSliceQuery())) {
  		return (T) ((Slice<?>) source).map(converter::convert);
  	}

  	if (source instanceof Collection<?> collection && method.isCollectionQuery()) {

  		Collection<Object> target = createCollectionFor(collection);

  		for (Object columns : collection) {
  			target.add(type.isInstance(columns) ? columns : converter.convert(columns));
  		}

  		return (T) target;
  	}

  	if (source instanceof Stream && method.isStreamQuery()) {
  		return (T) ((Stream<Object>) source).map(t -> type.isInstance(t) ? t : converter.convert(t));
  	}

  	if (ReactiveWrapperConverters.supports(source.getClass())) {
  		return (T) ReactiveWrapperConverters.map(source, it -> processResult(it, preparingConverter));
  	}

  	return (T) converter.convert(source);
  }

And Spring does incount EntityGraphs annotations to projections, it than buidls a join query.

I look forward to hearing back from you

@Eng-Fouad
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants