Pageable REST API - spring

I'm using Pageable to return json results with pages. One example of my controller
#GetMapping("/nome/{nome}")
public ResponseEntity<Page<Cidadao>> consultaPorNome(
#PathVariable(value="nome",required=true) String nome,
#RequestParam(value="pagina", defaultValue="0") Integer page,
#RequestParam(value="quantidade", defaultValue="10") Integer linesPerPage,
#RequestParam(value="ordem", defaultValue="nome") String orderBy,
#RequestParam(value="direcao", defaultValue="ASC") String direction){
Page<Cidadao> lista = cidadaoService.findByNome(nome,page,linesPerPage,orderBy, direction);
return ResponseEntity.ok(lista);
}
And i get json like
{
"content": [
{
"id": 1,
"name": "Lucas"
},
{
"id": 2,
"name": "Erick"
},
],
"totalElements": 23,
"totalPages": 3,
"last": false,
"numberOfElements": 10,
"first": true,
"sort": [
{
"direction": "ASC",
"property": "nome",
"ignoreCase": false,
"nullHandling": "NATIVE",
"ascending": true,
"descending": false
}
],
"size": 10,
"number": 0
}
but i would like to return this way
{
"data":{
"content" : [
{
"id": 1,
"name": "Lucas"
},
{
"id": 1,
"name": "Lucas"
}
],
"totalElements": 23,
"totalPages": 3,
"last": false,
"numberOfElements": 10,
"first": true,
"sort": [
{
"direction": "ASC",
"property": "nome",
"ignoreCase": false,
"nullHandling": "NATIVE",
"ascending": true,
"descending": false
}
],
"size": 10,
"number": 0
},
errors:[]
}
It is possible implements anything like this to change or add attributes like errors ? Or if possible change the name attributes from Page like totalElements for numberofTotalElementBySomething.... ??

When I was looking for, I didn't find a beautiful solution to do this. So, I did this way:
Create a class called PageAdapter (or whatever you like)
public class PageAdapter<T extends Serializable> implements Serializable {
private long totalElements;
private long totalPages;
private long size;
private List<T> content;
public PageAdapter(Page<T> page){
this.totalElements = page.getTotalElements();
this.totalPages = page.getTotalPages();
this.size = page.getSize();
this.content = page.getContent();
}
}
And receive within constructor the original Pageable.
You can customize your properties, these properties were used in my case.
After this, you can return the PageAdapter in your controller/resource.
if there is a better solution, I would like to know too.
A suggestion for you, spring can receive an instance of Pageable, so, you don't need to receive each parameter for size, page, order and etc.
Just do this:
public ResponseEntity<Page<Cidadao>> consultaPorNome(
#PathVariable(value="nome",required=true) String nome, Pageable pageable){
You will have all information about size, page, order...
I'm not a native English speaker, so, may contains grammar errors.

Related

Spring Data: Page information missing fron response on using CollectionModel with RepositoryRestController

My repository method is:
public Page<Order> findByStatusIn(List<OrderStatus> orderStatuses, Pageable pageable);
Which is called from Controller (#RepositoryRestController) as :
Pageable paging = PageRequest.of(page, pageSize, Sort.by(Sort.Direction.DESC,"id"));
Page<Order> myOrders = orderRepository.findByStatusIn(orderStatuses, paging);
The Page is converted to CollectionModel:
CollectionModel<Order> resources = CollectionModel.of(myOrders);
resources.add(linkTo(methodOn(OrderController.class).userOrders(page,pageSize,currentUser)).withSelfRel());
return new ResponseEntity<>(resources,HttpStatus.OK);
The output json is like:
{
"_embedded": {
"orders": [
{
"orderId": 10011,
"createdAt": "2022-05-18T16:28:19+05:30",
"lastUpdatedAt": "2022-06-10T16:28:15+05:30",
"createdBy": "User01",
"status": "PENDING"
},
{
"orderId": 10012,
"createdAt": "2017-05-03T14:28:19+05:30",
"lastUpdatedAt": "2022-06-10T16:28:15+05:30",
"createdBy": "User01",
"status": "SHIPPED"
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/orders/userOrders?page=0&pageSize=11"
}
}
}
The data I got is correct but the response is missing the Page information as from the response getting from repository methods in Spring data.
ie, the section like
"page": {
"size": 20,
"totalElements": 20,
"totalPages": 1,
"number": 0
}
is missing.
How the page information can be added to the response when #RepositoryRestController is used?
PagedModel should be used to create representations of pageable collections, instead of CollectionModel. To easily convert Page instances into PagedModel use PagedResourcesAssembler as described here.

Feign client sorting problem with Turkish characters into response

I use feign to make request to another microservice with pageable and my custom encoder to manage sorting/paging;
#GetMapping(
value = {"/add-ben"},
produces = {"application/json"}
)
BaseResponse<MyPageImpl<AddBenDTO>> getAddBenDTOList(#SpringQueryMap RequestGetAddBen request, Pageable pageable);
I am using below JSON to use pageable;
{
"offset": 0,
"sort": "name,DESC",
"unpaged": true,
"paged": true,
"pageNumber": 0,
"pageSize": 0
}
After the response accepted, I saw that the 'name' values are sorted into DESCENDING order with using "Turkish" characters at first (not starting from Z, but with turkish characters such as İ,Ü,Ş is at the beginning);
{
"data": {
"content": [
{
(...)
"name": "İSDEMİR ADDBEN TANIMI",
},
{
(...)
"name": "ÜCRETSİZ HAYAT SİGORTA"
},
{
(...)
"name": "ZUNICO _ADD BEN"
},
{
(...)
"name": "TPATVAKF_ FON",
},
{
(...)
"name": "PROMET END.VE TİC.LAST."
},
{
(...)
"name": "OYAK RENAULT OTOMOBİL FABRİKALARI A.Ş."
}
], (...)
}
I think, this is related with the default charset. According to feign doc. UTF-8 is the default charset.
How can I correct the sorting with correct order including Turkish characters also?
Thanks

Show only object id in OneToMany List

I have these two classes:
Quota Class
#Entity
#Data
#EqualsAndHashCode(callSuper=false)
#Table(name="quotas")
#Relation(collectionRelation = "quotas", itemRelation = "quota")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Quota extends RepresentationModel<Quota> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "quota")
#JsonIdentityReference(alwaysAsId = true)
private List<Customer> customers;
private String type;
private boolean flatrate;
#CreatedDate
private long createdDate;
#LastModifiedDate
private long lastModifiedDate;
And Customer:
#Entity
#Data
#EqualsAndHashCode(callSuper=false)
#Table(name = "customers")
#Relation(collectionRelation = "customers", itemRelation = "customer")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Customer extends RepresentationModel<Customer> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String customerNumber;
#CreatedDate
private long createdDate;
#LastModifiedDate
private long lastModifiedDate;
#ManyToOne
#JoinColumn(name = "quota_id", referencedColumnName = "id")
#JsonProperty("quotaId")
#JsonIdentityReference(alwaysAsId = true)
private Quota quota;
}
What i get when i make a GET request on all Customers:
{
"_embedded": {
"customers": [
{
"id": 3,
"name": "Customer Test3",
"customerNumber": "45678",
"createdDate": 1596117132045,
"lastModifiedDate": 1596117132045,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/3"
}
},
"quotaId": 1
},
{
"id": 1,
"name": "test3",
"customerNumber": "12345",
"createdDate": 1596111304535,
"lastModifiedDate": 1596186450456,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/1"
}
},
"quotaId": 1
},
{
"id": 2,
"name": "Customer Test2",
"customerNumber": "23456",
"createdDate": 1596112131934,
"lastModifiedDate": 1596112131934,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/2"
}
},
"quotaId": 2
},
{
"id": 4,
"name": "Customer Test4",
"customerNumber": "34567",
"createdDate": 1596117145795,
"lastModifiedDate": 1596117145795,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/4"
}
},
"quotaId": 2
},
{
"id": 6,
"name": "Customer Test6",
"customerNumber": "12345",
"createdDate": 1596187250598,
"lastModifiedDate": 1596187250598,
"_links": {
"self": {
"href": "http://localhost:2502/seminars/6"
}
},
"quotaId": null
}
]
},
"_links": {
"self": {
"href": "http://localhost:2502/seminars?page=0&size=20&sort=quota,asc"
}
},
"page": {
"size": 20,
"totalElements": 5,
"totalPages": 1,
"number": 0
}
}
What i get when i make a GET request in all Quotas:
{
"_embedded": {
"quotas": [
{
"id": 1,
"customers": [
{
"id": 3,
"name": "Customer Test3",
"customerNumber": "45678",
"createdDate": 1596117132045,
"lastModifiedDate": 1596117132045,
"quotaId": 1
},
{
"id": 1,
"name": "test3",
"customerNumber": "12345",
"createdDate": 1596111304535,
"lastModifiedDate": 1596186450456,
"quotaId": 1
}
],
"type": "paid",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/1"
}
}
},
{
"id": 2,
"customers": [
{
"id": 2,
"name": "Customer Test2",
"customerNumber": "23456",
"createdDate": 1596112131934,
"lastModifiedDate": 1596112131934,
"quotaId": 2
},
{
"id": 4,
"name": "Customer Test4",
"customerNumber": "34567",
"createdDate": 1596117145795,
"lastModifiedDate": 1596117145795,
"quotaId": 2
}
],
"type": "demo",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/2"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:2502/quotas?page=0&size=10&sort=id,asc"
}
},
"page": {
"size": 10,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
I am happy with the result from Customers, but the result from quotas i would want to look like this:
{
"_embedded": {
"quotas": [
{
"id": 1,
"customers": [3,1],
"type": "paid",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/1"
}
}
},
{
"id": 2,
"customers": [2,4],
"type": "demo",
"createdDate": 1596111304535,
"lastModifiedDate": 1596111304535,
"flatrate": true,
"_links": {
"self": {
"href": "http://localhost:2502/quotas/2"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:2502/quotas?page=0&size=10&sort=id,asc"
}
},
"page": {
"size": 10,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
For json serialization i use 'com.fasterxml.jackson.annotation.*'
I already tried it with using the #JsonBackRefference, #JsonIgnore, #JsonIngoreProperties and #JsonIdentityReference annotations but never got the wanted result.
I would recommend you introduce DTOs to model this rather than trying to annotate the entity classes with JSON annotations. At some point you will probably have conflicting needs i.e. one use case needs a field and another doesn't and then you will have to think about abstracting this anyway.
Having said that, this is a perfect use case for Blaze-Persistence Entity Views.
Blaze-Persistence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A DTO mapping for your model could look as simple as the following
#EntityView(Quota.class)
public abstract class QuotaDto extends RepresentationModel<QuotaDto> {
public abstract Integer getId();
#Mapping("customers.id")
public abstract List<Integer> getCustomers();
public abstract String getType();
public abstract boolean isFlatrate();
public abstract long getCreatedDate();
public abstract long getLastModifiedDate();
}
#EntityView(Customer.class)
public abstract class CustomerDto extends RepresentationModel<CustomerDto> {
public abstract Integer getId();
public abstract String getName();
public abstract String getCustomerNumber();
public abstract long getCreatedDate();
public abstract long getLastModifiedDate();
#Mapping("quota.id")
public abstract Integer getQuotaId();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
CustomerDto dto = entityViewManager.find(entityManager, CustomerDto.class, id);
But the Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
You are in control of the structure and can map anything to the properties. It will only fetch the mappings that you tell it to fetch.

How can i customize the dafault behavior of jackson when returning a ResponseEntity<> with PagedResources<>?

Im using spring boot on my proyect. Basically, all i want is to include inline entities in a response of a GET method while still using the advantages of spring-hateoas (_embedded and links).
Having this class:
#Entity
#Table(name = "sub_specialty", schema = "public", catalog = "icorelab")
public class SubSpecialty {
private Integer id;
private String name;
private Boolean active;
private Date createdAt;
private Date deletedAt;
private Specialty specialty;
private String description;
#ManyToOne
#JoinColumn(name = "specialty_id", referencedColumnName = "id", nullable = false)
public Specialty getSpecialtyBySpecialtyId() {
return specialty;
}
I set up repository with PagingAndSorting capabilities, also it extends CustomRepository to provide custom methods. This repository uses a projection so an iternal entity gets serialized as mentioned here
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts.excerpts
#RepositoryRestResource(collectionResourceRel = "sub_specialties", path = "sub_specialties",
excerptProjection = InlineSpecialty.class)
public interface SubSpecialtyRepository extends PagingAndSortingRepository<SubSpecialty, Integer>,SubSpecialtyRepositoryCustom {
The in a controller, i return the response by using Page and PageResourcesAssembler
Page<SubSpecialty> page = subSpecialtyRepository.filter(name,description,active,specialty,limit,offset,pageable.getSort());
PagedResources<SubSpecialty> pagedResources = pagedResourcesAssembler.toResource(page,subSpecialtyResourceAssembler);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/sub_specialties/filter", offset, limit);
ResponseEntity<?> response = new ResponseEntity<>(pagedResources, headers, HttpStatus.OK);
return response;
The problem is that the message does not include Specialty info, just the link
{
"_embedded": {
"sub_specialties": [
{
"id": 107,
"name": "PHARMACOLOGY",
"active": false,
"createdAt": "2017-09-30",
"deletedAt": null,
"description": null,
"_links": {
"self": {
"href": "http://localhost:8080/sub_specialties/107{?projection}",
"templated": true
},
"subSpecialty": {
"href": "http://localhost:8080/sub_specialties/107{?projection}",
"templated": true
},
"specialty": {
"href": "http://localhost:8080/specialties/16"
}
}
},
{
"id": 104,
"name": "PHARMACOLOGY",
"active": false,
"createdAt": "2017-09-30",
"deletedAt": null,
"description": null,
"_links": {
"self": {
"href": "http://localhost:8080/sub_specialties/104{?projection}",
"templated": true
},
"subSpecialty": {
"href": "http://localhost:8080/sub_specialties/104{?projection}",
"templated": true
},
"specialty": {
"href": "http://localhost:8080/specialties/16"
}
}
}
]
},
"_links": {
"self": {
"href": "http://localhost:8080/sub_specialties/filter?page=0&size=20"
}
},
"page": {
"size": 20,
"totalElements": 2,
"totalPages": 1,
"number": 0
}
}
Is there anyway to achieve this? I thought i could use an annotation on the entity domain class or a custom jackson serializer.The Specialty entity is included in the response, however is not included in the message that receives the client. I suppose there is some default configuration for jackson that detects a repository of type Specialty, and the just includes the link in the response.

Spring pagination - request parameters

My REST contorller:
#GetMapping("/test")
public Page<MyObject> pathParamTest(Pageable pageable) {
return myService.getPage(pageable);
}
I send a request like following:
localhost:8091/endpoint/test?page=0&size=3&sort=id&direction=DESC
It's my response from server:
{
"content": [
{
"id": 1
},
{
"id": 2
},
{
"id": 3
}
],
"last": true,
"totalPages": 1,
"totalElements": 3,
"first": true,
"sort": [
{
"direction": "ASC",
"property": "id",
"ignoreCase": false,
"nullHandling": "NATIVE",
"descending": false,
"ascending": true
}
],
"numberOfElements": 3,
"size": 3,
"number": 0
}
but the request has still direction = ASC.
How can I send to server direction = DESC?
And why response has a field "last" = true, because next page has one element more?
try
localhost:8091/endpoint/test?page=0&size=3&sort=id,DESC
from spring data rest 6.2. Sorting
curl -v
"http://localhost:8080/people/search/nameStartsWith?name=K&sort=name,desc"
sort Properties that should be sorted by in the format
property,property(,ASC|DESC). Default sort direction is ascending. Use
multiple sort parameters if you want to switch directions, e.g.
?sort=firstname&sort=lastname,asc.

Resources