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.
Related
Issue: Don't want to fetch the full referenced child objects just the Id.
Partner.java
#Data
#Document
#Builder
public class Partner {
#Id
private String id;
#DBRef(lazy = true) // working as expected
private User user;
#DBRef(lazy = true) // not working, still loads full objects
#Builder.Default
private HashSet<Product> products = new HashSet<>();
}
Product.java
#Data
#Document
#Builder
public class Product {
#Id
private String id;
private Name name;
}
I know that #DBRef is not recommended for such cases. But the project is already built that way and is expensive to change at the moment.
So far tried:
excluding the child array field from the query but then I do not get the Ids as well.
excluding the products.$ref from the query.
#Query(value="{ '_id' : ?0 }", fields="{ products.$ref: 0 }")
Optional<Partner> findByIdWithoutProducts(String id);
Expected:
{
"_id": {
"$oid": "634692559f31e246984436c8"
},
"user": {
"$ref": "user",
"$id": "f689bae8-7442-4355-ae9c-65d22a96ear4"
},
"products": [
{
"$id": {
"$oid": "633d92d2310687496fb852e6"
}
},
{
"$id": {
"$oid": "633d92d2310687496fb852d5"
}
}
}
}
Actual:
{
"_id": {
"$oid": "634692559f31e246984436c8"
},
"user": {
"$ref": "user",
"$id": "f689bae8-7442-4355-ae9c-65d22a96ear4"
},
"products": [
{
"$id": {
"$oid": "633d92d2310687496fb852e6"
},
"name": "abcd" // not wanted
},
{
"$id": {
"$oid": "633d92d2310687496fb852d5"
},
"name": "xyz" // not wanted
}
}
}
My goal is to fetch the parent and just list of Ids of the child objects. Any other way is also appreciated.
I have Apartment entity:
#Entity
public class Apartment extends AbstractEntity {
private String name;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(nullable = false)
#RestResource(exported = false)
private Address address;
private String website;
#OneToMany(mappedBy = "apartment")
#RestResource(exported = false)
private Set<FloorPlan> floorPlans;
...
FloorPlan entity:
#Entity
public class FloorPlan extends AbstractEntity {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "apt_id", nullable = false)
private Apartment apartment;
private float bed;
private float bath;
private int priceFrom;
...
I applied excerptProjection to Floorplan to only show bed, bath and priceFrom. When I query apartments collection, the json output looks ok:
{
"_embedded": {
"apartments": [
{
"name": "Avalon Silicon Valley",
"website": "https://www.avaloncommunities.com/california/sunnyvale-apartments/avalon-silicon-valley",
"address": {
"streetNumber": "1257",
"street": "Lakeside Drive",
"city": "Sunnyvale",
"state": "CA",
"zipCode": "94085",
"fullAddress": "1257 Lakeside Drive, Sunnyvale, CA 94085"
},
"floorPlans": [
{
"bed": 3,
"bath": 3,
"priceFrom": 4495
},
{
"bed": 3,
"bath": 2,
"priceFrom": 4760
},
However if I do a single resource like http://localhost:8080/ag-api/apartments/1
floorplans will output Apartment Object as one of its field:
{
"name": "Avalon Silicon Valley",
"address": {
"streetNumber": "1257",
"street": "Lakeside Drive",
"city": "Sunnyvale",
"state": "CA",
"zipCode": "94085",
"fullAddress": "1257 Lakeside Drive, Sunnyvale, CA 94085"
},
"website": "https://www.avaloncommunities.com/california/sunnyvale-apartments/avalon-silicon-valley",
"floorPlans": [
{
"bed": 3,
"bath": 3,
"priceFrom": 4495,
"_embedded": {
"apartment": {
"name": "Avalon Silicon Valley",
"website": "https://www.avaloncommunities.com/california/sunnyvale-apartments/avalon-silicon-valley",
"address": {
"streetNumber": "1257",
"street": "Lakeside Drive",
"city": "Sunnyvale",
"state": "CA",
"zipCode": "94085",
"fullAddress": "1257 Lakeside Drive, Sunnyvale, CA 94085"
},
"floorPlans": [
Anyone got an idea what might be going on? really appreciate it. Thanks
Not sure if it will help, but in documentation I found in order to omit "_embedded" in you response you'll need to add:
"spring.hateoas.use-hal-as-default-json-media-type=false" to application.properties.
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.
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.
is it possible to output a return Value totalAmount of an Entity ShoppingCart that is not a Value in the Class but a Method? So for example I have a Class Shoppingcart with a List of Items. and a Method totalAmount. Now when I make a request to the API with the URL http://localhost:8082/carts/1 I want to get a response like the following:
{
"creationDate": "2016-12-07T09:45:38.000+0000",
"items": [
{
"itemName": "Nintendo 2DS",
"description": "Konsole from Nintendo",
"price": 300.5,
"quantity": 3
},
{
"itemName": "Nintendo Classic",
"description": "Classic nintendo Console from the 80th...",
"price": 75,
"quantity": 2
}
],
"totalAmount": "1051,50",
"_links": {
"self": {
"href": "http://localhost:8082/carts/2"
},
"cart": {
"href": "http://localhost:8082/carts/2"
},
"checkout": {
"href": "http://localhost:8083/order"
}
}
}
Currently the response of an API request looks like the following:
{
"creationDate": "2016-12-07T09:45:38.000+0000",
"items": [
{
"itemName": "Nintendo 2DS",
"description": "Konsole from Nintendo",
"price": 300.5,
"quantity": 3
},
{
"itemName": "Nintendo Classic",
"description": "Classic nintendo Console from the 80th...",
"price": 75,
"quantity": 2
}
],
"_links": {
"self": {
"href": "http://localhost:8082/carts/2"
},
"cart": {
"href": "http://localhost:8082/carts/2"
},
"checkout": {
"href": "http://localhost:8083/order"
}
}
}
Is there an Annotation that do this job or something other. I tried to add it in the CartResourceProcessor (org.springframework.hateoas.ResourceProcessor) but there is only the possibility to add additional links. Or do I need to add a Class value totalAmount?
Yes you can achieve that by annotating your method with Jackson #JsonProperty annotation
Code sample
#JsonProperty("totalAmount")
public double computeTotalAmount()
{
// compute totalAmout and return it
}
And to answer the possible next question you get after reading this. How the totalAmount is calculated. Here the snippet
public Class Cart{
// some Class values
#JsonProperty("totalAmount")
public BigDecimal total(){
return items.stream()
.map(Item::total)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
public class Item{
// some Item values
#JsonProperty("totalAmount")
public BigDecimal total(){
return price.multiply(new BigDecimal(this.quantity));
}
}
Outputs something similar to this:
{
"creationDate": "2016-12-07T09:45:38.000+0000",
"items": [
{
"itemName": "Nintendo 2DS",
"description": "Konsole from Nintendo",
"price": 300.5,
"quantity": 3,
"totalAmount": 901.5
},
{
"itemName": "Nintendo Classic",
"description": "Classic nintendo Console from the 80th...",
"price": 75,
"quantity": 2,
"totalAmount": 150
}
],
"totalAmount": 1051.5,
"_links": {
"self": {
"href": "http://localhost:8082/carts/2"
},
"cart": {
"href": "http://localhost:8082/carts/2"
},
"checkout": {
"href": "http://localhost:8083/order"
}
}
}
Hope it helps you :)