spring-projects / spring-projects/spring-data-rest

PagedResourcesAssembler not converting to PagedModel when using @Query

Open
#2,140 1 comment 0 reactions 1 assignee View on GitHub

@odrotbohm is already working on this.

Since May 23, 2022.

status: waiting-for-triage
Dominant language
Java
Stars
958
Forks
568
PR merge metrics
No merged PRs in 30d

Description

I'm trying to convert the result of a paged query to a PagedModel to generate the links automatically. Basically, the endpoint in the controller will return ResponseEntity<PagedModel<EntityModel<MyEntityProjection>>>, instead of returning Page<MyEntityProjection>.

For the client to get the list, usually the request will change from 'data.content' to 'data._embedded.myEntities'. I was able to make that conversion using PagedResourcesAssembler, but it only seems to work for custom queries using Query Lookup Strategies, but not with @Query.

Here's the code.

Repository:

@RepositoryRestResource
public interface MyEntityRepository extends PagingAndSortingRepository<MyEntity, Long>{
     Page<MyEntityProjection> findAllByMyField(String value, Pageable pageable);

     @Query("...")
     Page<MyEntityProjection> findAllByMyFieldButWithQuery(String value, Pageable pageable);

     @Query(value = "...", nativeQuery = true)
     Page<MyEntityProjection> findAllByMyFieldButWithNativeQuery(String value, Pageable pageable);
}

The repository is doing the same query in three different ways. First with a query lookup strategy, second with JPQL and third with native query.

Controller:

@RepositoryRestController
public class MyEntityController {
	private final MyEntityRepository repository;
	private final PagedService pagedService;

	@Autowired
	public MyEntityController(...) {...}

	@GetMapping(value = "/my-entities/find/{value}")
	@ResponseBody
	public ResponseEntity<PagedModel<EntityModel<MyEntityProjection>>> find(@PathVariable String value,
			Pageable pageable, PagedResourcesAssembler<MyEntityProjection> assembler) {

		Page<MyEntityProjection> myEntities = findByMyField(value, pageable);
		return pagedService.buildPagedResponse(myEntities, MyEntityProjection.class, assembler); // works
	}

	... findButWithQuery(...) {
		Page<MyEntityProjection> myEntities = findByMyFieldButWithQuery(value, pageable);
		return pagedService.buildPagedResponse(myEntities, MyEntityProjection.class, assembler); // throws error here
	}

	... findByMyFieldButWithNativeQuery(...) {
		Page<MyEntityProjection> myEntities = findByMyFieldButWithNativeQuery(value, pageable);
		return pagedService.buildPagedResponse(myEntities, MyEntityProjection.class, assembler); // throws error here
	}
}

The controller has three endpoints where each one calls one of the queries. It then uses the following service to convert a Page<MyEntityProjection> into a ResponseEntity<PagedModel<EntityModel<MyEntityProjection>>>.

Service:

@Service
public class PagedService {
	
	@SuppressWarnings("unchecked")
	public <P> ResponseEntity<PagedModel<EntityModel<P>>> buildPagedResponse(Page<P> pagedEntities,
			Class<P> projectionClazz, PagedResourcesAssembler<P> assembler) {
		PagedModel<EntityModel<P>> model = null;
		if (pagedEntities.isEmpty()) {
			model = (PagedModel<EntityModel<P>>) assembler.toEmptyModel(pagedEntities, projectionClazz);
		} else {
			model = assembler.toModel(pagedEntities);
		}

		return (ResponseEntity<PagedModel<EntityModel<P>>>) new ResponseEntity<PagedModel<EntityModel<P>>>(model,
				HttpStatus.OK);
	}
}

The service is just a generic implementation that converts to the desired response format.

When the endpoint tries to return the response, Jackson throws a NullPointerException. I presume that the assembler didn't build the '_embedded' part of the response. Here's the error:

Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: (was java.lang.NullPointerException); nested exception is com.fasterxml.jackson.databind.JsonMappingException: (was java.lang.NullPointerException) (through reference chain: org.springframework.hateoas.PagedModel["_embedded"]->java.util.LinkedHashMap["tupleBackedMaps"]->java.util.ArrayList[0]->org.springframework.hateoas.EntityModel["content"])]

Shouldn't this conversion when using JPQL and native queries work in a similar way to the query lookup strategy?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.