Skip to content

JpaQueryCreator fails using interfaces for projections in SpringDataJPA 2.6.1 #2427

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
jcconca opened this issue Jan 31, 2022 · 3 comments
Closed
Labels
status: duplicate A duplicate of another issue

Comments

@jcconca
Copy link

jcconca commented Jan 31, 2022

Hi guys:

In one of the projects I'm working now we decide to upgrade to SpringBoot 2.6.3

And as part of the upgrade one of the problems that we detect is:

Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.List infrastructure.document.SpringDataDocumentRepository. findByMortgageRequestIdOrderByCreationDateDesc(java.lang.String)! null
	at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:96)
	at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:113)
	at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:254)
	at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:87)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:102)
	... 80 common frames omitted
Caused by: java.lang.NullPointerException: null
	at org.springframework.data.jpa.repository.query.JpaQueryCreator.complete(JpaQueryCreator.java:181)
	at org.springframework.data.jpa.repository.query.JpaQueryCreator.complete(JpaQueryCreator.java:152)
	at org.springframework.data.jpa.repository.query.JpaQueryCreator.complete(JpaQueryCreator.java:59)
	at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:95)
	at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:81)
	at org.springframework.data.jpa.repository.query.PartTreeJpaQuery$QueryPreparer.<init>(PartTreeJpaQuery.java:217)
	at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:92)
	... 84 common frames omitted

After having a look, seems that the method getTypeToRead() is returning a null under certain circumstances:

        @Nullable
        public Class<?> getTypeToRead() {
            return this.isProjecting() && this.information.isClosed() ? null : this.domainType;
        }

but is not controlled in the code that requested in JpaQueryCreator and fails running this command:

query = typeToRead.isInterface() ? query.multiselect(selections) : query.select((Selection) builder.construct(typeToRead, selections.toArray(new Selection[0])));

JpaQueryCreator:

      protected CriteriaQuery<? extends Object> complete(@Nullable Predicate predicate, Sort sort,
			CriteriaQuery<? extends Object> query, CriteriaBuilder builder, Root<?> root) {

		if (returnedType.needsCustomConstruction()) {

			List<Selection<?>> selections = new ArrayList<>();

			for (String property : returnedType.getInputProperties()) {

				PropertyPath path = PropertyPath.from(property, returnedType.getDomainType());
				selections.add(toExpressionRecursively(root, path, true).alias(property));
			}

			Class<?> typeToRead = returnedType.getTypeToRead();

			query = typeToRead.isInterface()
					? query.multiselect(selections)
					: query.select((Selection) builder.construct(typeToRead,
							selections.toArray(new Selection[0])));

		} else if (tree.isExistsProjection()) {

			if (root.getModel().hasSingleIdAttribute()) {

				SingularAttribute<?, ?> id = root.getModel().getId(root.getModel().getIdType().getJavaType());
				query = query.multiselect(root.get((SingularAttribute) id).alias(id.getName()));

			} else {

				query = query.multiselect(root.getModel().getIdClassAttributes().stream()//
						.map(it -> (Selection<?>) root.get((SingularAttribute) it).alias(it.getName()))
						.collect(Collectors.toList()));
			}

		} else {
			query = query.select((Root) root);
		}

		CriteriaQuery<? extends Object> select = query.orderBy(QueryUtils.toOrders(sort, root, builder));
		return predicate == null ? select : select.where(predicate);
	}

Why if the interface projection has the same properties as the input properties, is declared as closed and is required to return null?

public boolean isClosed() { return this.properties.equals(this.getInputProperties()); }

and why check if the TypeToRead is an interface over a possible null value?

If this helps, this are the classes that I have in my project and produce the error after the migration from SpringDataJpa 2.4.X to 2.6.X.

Entity embeddable id:

@Embeddable
public class DocumentId extends StringEntityId {

    public DocumentId() {
    }

    public DocumentId(String value) {
        super(value);
    }
}

Document entity:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Document implements Anonymizable, Serializable {

    @EmbeddedId
    private DocumentId id;

    @Column(nullable = false)
    private String mortgageRequestId;

    @Column(nullable = false)
    @Lob
    private byte[] document;

    @CreatedDate
    @Column(nullable = false, updatable = false)
    private LocalDate creationDate;

    @Column(nullable = false)
    private String dataId;

    @Column(nullable = false)
    private String fileName;

    @Column(nullable = false)
    private String mimeType;

Projection interface

public interface DocumentName {

    DocumentId getId();

    String getFileName();

    String getMimeType();

    LocalDate getCreationDate();
}

Repository

public interface SpringDataDocumentRepository extends JpaRepository<Document, DocumentId> {

    Document findByDataId(final String dataId);

    List<DocumentName> findByMortgageRequestId(final String mortgageRequestId);

    List<DocumentName> findByMortgageRequestIdOrderByCreationDateDesc(final String mortgageRequestId);

    Optional<Document> findByIdAndMortgageRequestId(DocumentId id, String mortgageRequestId);
}

Any idea to solve this specific scenario?

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label Jan 31, 2022
@schauder
Copy link
Contributor

schauder commented Feb 1, 2022

Duplicate of #2408

@schauder schauder marked this as a duplicate of #2408 Feb 1, 2022
@schauder schauder closed this as completed Feb 1, 2022
@schauder schauder added status: duplicate A duplicate of another issue and removed status: waiting-for-triage An issue we've not yet triaged labels Feb 1, 2022
@jcconca
Copy link
Author

jcconca commented Feb 1, 2022

How is posible that is solved in 2.6.1 and fails in that exact version? @schauder

Which version has the bug fixed? I only see this comment about the version:

This should be fixed now in 2.6, 2.7 and 3.0 snapshots. by @odrotbohm

@schauder
Copy link
Contributor

schauder commented Feb 1, 2022

How is posible that is solved in 2.6.1 and fails in that exact version?
I don't think anybody said this is fixed in 2.6.1

The fix will be included in the next releases of the 2.6, 2.7 and 3.0 branches: 2.6.2, 2.7.0-M3, and 3.0.0-M2

You can tell by the branches and tags the fixing commits are contained in.
If it is in a tag for a version the fix is included in that version. if it only in main or a version.x branch it will be included in those branches next version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status: duplicate A duplicate of another issue
Projects
None yet
Development

No branches or pull requests

3 participants