How to do not send #IdClass object in Spring JSON queries - spring

I'm setting a server to get a CRUD api from a postgresql Database using JPA. Everytime I want to expose an object from the DB it duplicate the idObject.
When I get an object from the database using springframework and send it after that, it duplicate the idObject like this:
{
"siteId": 3,
"contractId": "1",
"name": "sitenumber1",
"siteIdObject": {
"siteId": 3,
"contractId": "1"
}
}
SiteId and contractId are repeating...
but I want something like that:
{
"siteId": 3,
"contractId": "1",
"name": "sitenumber1"
}
I want to avoid using DTO because I think there is a better way but I don't find it. Since I'm using springFramework for just one or two month I'm maybe forgeting something...
there is the code:
Site code:
#Entity
#IdClass(SiteId.class)
#Table(name = "site", schema="public")
public class Site {
#Id
#Column(name="siteid")
private Integer siteId;
#Id
#Column(name="clientid")
private Integer contractId;
private String name;
#JsonIgnore
#OneToMany(cascade=CascadeType.ALL, mappedBy = "site")
public Set<Device> devices;
//setter, getter, hash, equals, tostring, constructor empty one and full one
SiteId code:
public class SiteId implements Serializable {
private Integer siteId;
private Integer contractId;
// setter, getter, constructor empty and full, hash and equals
Thanks to help :)
Bessaix Daniel

If you are using Spring you might also be using Jackson so if you annotate your SiteIdclass with #JsonIgnoreType it shouldn't be serialized at all when the Site object is serialized.
I am however unsure if this will break your application logic now that the id object is not serialized anymore.

Related

How can I generalize the Outer Class in Json Response mapping using REST template?

I'm developing a series of clients to webservices and I'm observing that they follow a simple json structure, like this:
{
"typex": { "property1" : "value"},
"page": 1,
"count": 200,
"next_page": 2
}
I have near 15 webservices each returning the same structure, where the main information is within the object "typex" and their properties.
Today, these returns needs to have, at least, 2 classes, one for outer data and one for inner information.
public class TypeXWrapper {
#JsonProperty
TypeX typex;
#JsonProperty
Integer page;
#JsonProperty
Integer count;
#JsonProperty
Integer next_page;
}
public class TypeX {
#JsonProperty
String property1;
}
In this way, it would be necessary to create 30 classes.
Is there any way to implement some kind of generalization for situations like this?
I was thinking something like:
public class GenericWrapper<InnerClass> {
#JsonProperty
InnerClass data;
#JsonProperty
Integer page;
#JsonProperty
Integer count;
#JsonProperty
Integer next_page;
}
public class TypeX {
#JsonProperty
String property1;
}
But the problem is that the root property of the inner class data, changes for each webservice endpoint. That means that the next answer, for example TypeY, the property will have the name "typey", like:
{
"typey": { "property2" : "value"},
"page": 1,
"count": 200,
"next_page": 2
}
Is there anything that I could use to achieve this generalization? The environment and frameworks are Spring Boot 2.1.18 (can change if needed), using resttemplate with the return object encapsulated in ParameterizedTypeReference.
Thanks!

Spring REST api - foreign key ID instead of entire object

I have a problem with Json returned by my REST api GET method.
That's how my entities looks like:
#Entity
public class Employee {
...
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "department_id", nullable = true)
private Department department;
}
#Entity
public class Department {
...
#OneToMany(mappedBy = "department", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JsonBackReference
private Set<Employee> employees;
}
And this is the answer I get, while I'm trying to GET Employee by its id:
{
"id": 1,
"surname": "smith",
"department": {
"id": 1,
"name": "HR",
"room": "13"
}
}
Now, Instead entire Department object, I would like to get just simple id: "department_id": 1, and I don't know how to do that.
Second question: what's the good practise in this situation in REST api? Should I leave it like it is; expose only id (what I'm asking you how to do); or use DTO and not showing it at all?
Moreover, anyway I'm going to add _links to this user's department, and in this case i thought that leaving only id should be ok (tell me if I'm wrong).
Looking forward for your answers!
A good practice is to define a DTO that represents the data that is exposed by your API.
This should be decoupled from your domain (Employee) as it will offer you more flexibility, just like what you want to achieve.
class EmployeeDTO extends RepresentationModel {
private long id;
private String surname;
private long departmentId;
// getters and setters
}
This should work. Of course you need to map your Employee entity to the EmployeeDTO. RepresentationModel contains the _links property that you want for the HATEOAS (for example, have a look at
https://www.baeldung.com/spring-hateoas-tutorial )
About exposing the id from your database, I think that a good reason for not doing it is that you are giving information about your database size for free and it's something that you might not want to. More information could even be derived from that.
Here you can find a good discussion on the topic:
Exposing database IDs - security risk?
I would suggest to have a look at UUID which is a universally unique alphanumeric identifier that doesn't expose this information about your data.
More about UUID: https://www.baeldung.com/java-uuid
#JsonIgnoreProperties
To just get department id without changing any implementation you may use #JsonIgnoreProperties({"name", "room"}) as following
#ManyToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "department_id", nullable = true)
#JsonIgnoreProperties({"name", "room"})
private Department department;
which will respond with the following
[
{
"id": 1,
"surname": "smith",
"department": {
"id": 1
}
}
]
You may also like to explore other ways to achieve the same here
Best Practices
We should never expose and return our modal and entities as a response to APIs. We may create the DTOs/DAOs to receive and transfer the objects and data. You may also convert the entity to DTO and DTO to entity using mappers.
In the case of DTO, you may just include the department id and may fetch the object if required using the repository.

spring-data-redis, empty list attribute value becomes null

I'm in the process of porting some microservices from SpringBoot1.5 to 2.1.
We are using spring-data-redis. it seems the default internal moves from jedis to lettuce.
The thing is we now observe some weird behaviours, when we save an object and then retrieve it, there is a tiny difference:
empty list attributes are replaced with null.
Here is an example:
//repo
public interface TestRepository extends CrudRepository<Test, String> {}
...
//object
#RedisHash(timeToLive = 60)
public static class Test{
#Id private String id;
int age;
List<String> friends;
}
...
//saving then retreiving
Test test = new Test("1", 15, Collections.emptyList());
System.out.println(test);
testRepository.save(test);
Test testGet = testRepository.findById("1").get();
System.out.println(testGet);
and here is what happens:
//before
{
"id": "1",
"age": 15,
"friends": []
}
//after
{
"id": "1",
"age": 15
}
the friends empty list has disappeared. This new behaviour affects our code in many places leading to NullPointerExceptions etc.
Apparently, there are multiple serializers available but this doesn't seem to have any effect. Any idea?
https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#redis:serializer
for reference:
springBootVersion = '2.1.5.RELEASE'
springCloudVersion = 'Greenwich.SR1'
I met this problem too. I solved it like this:
#RedisHash(timeToLive = 60)
public class MyData implements Serializable {
#Id
private String id;
private List<Object> objects = new ArrayList<>();
}
If i will save MyData with empty list objects, when i pull it from Redis, objects in it will not be null and will be empty list. If i will save 'MyData' with not empty objects, objects not will be lost after deserialization.

Sending POST request with Postman with #DBref

I want to send a POST request with Postman that creates a Purchase object and save it in the database.
My class Purchase:
#Document(collection = "purchases")
public class Purchase {
#Id
private String id;
#DBRef
private User buyer;
#DBRef
private List<File> FilesToPurchase;
private Long timestamp;
public Purchase() { }
public Purchase(User buyer, List<File> filesToPurchase) {
this.buyer = buyer;
FilesToPurchase = filesToPurchase;
}
// Getters and setters not posted here.
I want to insert in the database a new purchase done by an already existing User "buyer" who wants to purchase a list of already exsting Files "FilesToPurchase".
I have in my controller a POST function that receives a Purchase object using the annotation #RequestBody, but so far I've got NullPointerExceptions because of the empty Purchase object received.
I don't know how to handle #DBRef annotation. In Postman I try sending a JSON like this:
{
"buyer": {
"$ref":"users",
"$id" : "ObjectId('5bb5d6634e5a7b2bea75d4a2')"
},
"FilesToPurchase": [
{ "$ref":"files",
"$id" : "ObjectId('5bb5d6634e5a7b2bea75d4a5')"
}
]
}
Rename field "FilesToPurchase" and setter to "filesToPurchase" to match java conventions and try this
{ "buyer": { "id" : "5bb5d6634e5a7b2bea75d4a2" }, "filesToPurchase": [ { "id" : "5bb5d6634e5a7b2bea75d4a5" } ] }
By marking controller parameter with #RequestBody you ask Spring to deserialize input json to java object(Jackson ObjectMapper is used by default). It will not automaticly populate the whole #Dbref field, you should do it yourself by querying mongo if you want, however the only field you need in referred object to save object that reffers it is 'id'.

Sort feature in association endpoint in Spring Data Rest

I have the following two resources, and their association;
#Table(name = "Item")
#Data
#Entity
public class Item {
#ManyToOne
#JoinColumn(name = "fk_wrapper")
private Wrapper wrapper;
#Id
#GeneratedValue
private String id;
private Integer someValue;
}
and;
#Table(name = "Wrapper")
#Data
#Entity
public class Wrapper {
#Id
#GeneratedValue
private String id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_wrapper")
private List<Item> items;
private String someField;
}
Then, first, I create a Wrapper;
POST http://localhost:8080/wrappers/
{
"someField": "asd"
}
http://localhost:8080/wrappers/1 created, then I create two Item's, linked to this Wrapper;
POST http://localhost:8080/items/
{
"someValue": "5",
"wrapper": "http://localhost:8080/wrappers/1"
}
&
POST http://localhost:8080/items/
{
"someValue": "7",
"wrapper": "http://localhost:8080/wrappers/1"
}
After all this, when I call the endpoint http://localhost:8080/wrappers/1/items, I get the list of these two items, as expected, but what the trouble is that, I cannot seem to have a sorting feature on this endpoint. I seem to be able to sort in http://localhost:8080/items endpoint, but while fetching with association, there doesn't seem to be a sorting feature. Is this lack of sorting is intended, or am I lacking some configuration?
P.S. when I create a custom search method, for example;
#RepositoryRestResource
public interface ItemRepository extends JpaRepository<Item, String> {
List<Item> findByWrapper_Id(#Param("id") String id, Sort sort);
}
Then I can use the sorting with http://localhost:8080/items/search/findByWrapper_Id endpoint, but too ugly imo, considering there is already an auto-generated endpoint.
Spring Data Rest doesn't support sorting on the associations.
You seem to have already found the best way to do what you need, according to the Spring Data Rest team - create a query for fetching the data you need. That will indeed support both pagination and sorting.
The reason why it's not supported has to do with the time when the queries are made to fetch the main resource (before the association endpoints are built) and the facts that the association endpoint makes use of the the loaded entity associations directly and that for supporting sort, a new query would need to be made anyway.
More detailed information can be found here:
https://jira.spring.io/browse/DATAREST-725?focusedCommentId=122244&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-122244
Cheers!

Resources