Object modelling - REST vs Hibernate - json

I am facing this in a project of my own, but i am sure this should be a generic issue.
I am trying to build rest services using java, spring and hibernate.
My typical entity object is modeled like below
#Entity
public class Company implements Serializable{
private Long companyId;
private String name;
private String shortDescription;
private Long logoId;
private Set<ActivityType> activityTypeList;
private String address;
private RegistrationInfo regInfo;
}
Where one object has associations with other objects, so my model object contains references to the associated objects.
I use jackson configured with spring for json-object and object-json conversion, it works perfectly, so I am good till this point.
Now I want my POST calls(which I am using for object creation) to create Company objects, only catch here is that I do not want to pass complete json for contained objects(like ActivityType and RegistrationInfo in above example), rather an id for each where the object with provided id already exists in the database. For example see this JSON snippet:
{
"companyId": 5,
"name": "test company created by rest call",
"shortDescription": "dummy description",
"logoId": 0,
"activityTypeList": [
{"activityTypeId": 1}
],
"address": "202, kardoga lane, lonavala, Maharashtra",
"regInfo": {
"registrationInfoId": 1
}
}
My rest service just needs to associate the newly created company object with the existing contained object with the provided id(inside POST call's body).
Jackson serialization creates unpopulated objects in this case, which hibernate fails to persist (for obvious reasons, there is no data just an id)
Sure i can use a DTO over here, i can get REST -> DTO and then DTO -> entity, but that seems an overhead and moreover in order to populate the Company object defined above, I will need to fetch the associated objects first from DB, attach them to my company object and then save it back. That will be an unnecessary DB trip.
Anyone has better idea on this one? Any design pattern, framework or simple tips/tricks in which i dont require a dto as well as save DB calls.

Related

Error thrown: "No serializer found for class java.lang.Long..." from controller while serializing JPA entity containing lazy "many-to-one" property

I am on Spring Boot 2.0.6, where an entity pet do have a Lazy many-to-one relationship to another entity owner
Pet entity
#Entity
#Table(name = "pets")
public class Pet extends AbstractPersistable<Long> {
#NonNull
private String name;
private String birthday;
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
#JsonIdentityReference(alwaysAsId=true)
#JsonProperty("ownerId")
#ManyToOne(fetch=FetchType.LAZY)
private Owner owner;
But while submitting a request like /pets through a client(eg: PostMan), the controller.get() method run into an exception as is given below:-
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class java.lang.Long and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: java.util.ArrayList[0]->com.petowner.entity.Pet["ownerId"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.9.7.jar:2.9.7]
at com.fasterxml.jackson.databind.SerializerProvider.reportBadDefinition(SerializerProvider.java:1191) ~[jackson-databind-2.9.7.jar:2.9.7]
Controller.get implementation
#GetMapping("/pets")
public #ResponseBody List<Pet> get() {
List<Pet> pets = petRepository.findAll();
return pets;
}
My observations
Tried to invoke explicitly the getters within owner through pet to force the lazy-loading from the javaassist proxy object of owner within the pet. But did not work.
#GetMapping("/pets")
public #ResponseBody List<Pet> get() {
List<Pet> pets = petRepository.findAll();
pets.forEach( pet -> pet.getOwner().getId());
return pets;
}
Tried as suggested by this stackoverflow answer at https://stackoverflow.com/a/51129212/5107365 to have controller call to delegate to a service bean within the transaction scope to force lazy-loading. But that did not work too.
#Service
#Transactional(readOnly = true)
public class PetServiceImpl implements PetService {
#Autowired
private PetRepository petRepository;
#Override
public List<Pet> loadPets() {
List<Pet> pets = petRepository.findAll();
pets.forEach(pet -> pet.getOwner().getId());
return pets;
}
}
It works when Service/Controller returning a DTO created out from the entity. Obviously, the reason is JSON serializer get to work with a POJO instead of an ORM entity without any mock objects in it.
Changing the entity fetch mode to FetchType.EAGER would solve the problem, but I did not want to change it.
I am curious to know why it is thrown the exception in case of (1) and (2). Those should have forced the explicit loading of lazy objects.
Probably the answer might be connected to the life and scope of that javassist objects got created to maintain the lazy objects. Yet, wondering how would Jackson serializer not find a serializer for a java wrapper type like java.lang.Long. Please do rememeber here that the exception thrown did indicate that Jackson serializer got access to owner.getId as it recognised the type of the property ownerId as java.lang.Long.
Any clues would be highly appreciated.
Edit
The edited part from the accepted answer explains the causes. Suggestion to use a custom serializer is very useful one in case if I don't need to go in DTO's path.
I did a bit of scanning through the Jackson sources to dig down to the root causes. Thought to share that too.
Jackson caches most of the serialization metadata on first use. Logic related to the use case in discussion starts at this method com.fasterxml.jackson.databind.ser.std.CollectionSerializer.serializeContents(Collection<?> value, JsonGenerator g, SerializerProvider provider). And, the respective code snippet is:-
The statement serializer = _findAndAddDynamic(serializers, cc, provider) at Line #140 trigger the flow to assign serializers for pet-level properties while skipping ownerId to be later processed through serializer.serializeWithType at line #147.
Assigning of serializers is done at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.resolve(SerializerProvider provider) method. The respective snippet is shown below:-
Serializers are assigned at line #340 only for those properties which are confirmed as final through the check at line #333.
When owner comes here, its proxied properties are found to be of type com.fasterxml.jackson.databind.type.SimpleType. Had this associated entity been loaded eagerly, the proxied properties obviously won't be there. Instead, original properties would be found with the values that are typed with final classes like Long, String, etc. (just like the pet properties).
Wondering why can't Jackson address this from their end by using the getter's type instead of using that of the proxied property. Anyway, that could be a different topic to discuss :-)
This has to do with the way that Hibernate (internally what spring boot uses for JPA by default) hydrates objects. A lazy object is not loaded until some parameter of the object is requested. Hibernate returns a proxy which delegates to the dto after firing queries to hydrate the objects.
In your scenario, loading OwnerId does not help because it is the key via which you are referencing the owner object i.e. the OwnerId is already present in the Pet object, so the hydration will not take place.
In both 1 and 2, you have not actually loaded the owner object, so when Jackson tries to serialize it at the controller level it fails. In 3 and 4, the owner object has been loaded explicitly, which is why Jackson does not run into any issues.
If you want 2 to work then load some parameter of owner, other than id, and hibernate will hydrate the object, and then jackson will be able to serialize it.
Edited Answer
The problem here is with the default Jackson serializer. This inspects the class returned and fetches the value of each attribute via reflection. In the case of hibernate entities, the object returned is a delegator proxy class in which all parameters are null, but all getters are redirected to the contained instance. When the object is inspected, the values of each attribute are still null, which is defaulted to an error as explained here
So basically, you need to tell jackson how to serialize this object. You can do so by creating a serializer class
public class OwnerSerializer extends StdSerializer<Owner> {
public OwnerSerializer() {
this(null);
}
public OwnerSerializer(Class<Owner> t) {
super(t);
}
#Override
public void serialize(Owner value, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeStartObject();
jgen.writeNumberField("id", value.getId());
jgen.writeStringField("firstName", value.getFirstName());
jgen.writeStringField("lastName", value.getLastName());
jgen.writeEndObject();
}
}
And setting it as the default serializer for the object
#JsonSerialize(using = OwnerSerializer.class)
public class Owner extends AbstractPersistable<Long> {
Alternatively, you can create a new Object of type Owner from the proxy class, manually populate it and set it in the response.
It is a little roundabout, but as a general practice you should not expose your DTO's externally anyway. The controller/domain should be decoupled from the storage layer.

Reusing the same Domain Object in For Get and POST

Assuming I have a student Object
public class Student{
private long id;
private string name;
private List<string> courses
}
In a typical Get request, to get a student I send the Student object to the client.
In the case of a PUT request to modify the Student object by adding or removing a course or a POST request to create a Student record, I only need to receive from the client the student.id and the list of courses.
My question is, Can I send back the same Student object from the client in the PUT or POST request without including the name or maybe have name=null?
Or should I create a separate domain object that will be sent by the client for example:
public class StudentReponse
{
private long id;
private List<string> courses;
}
I guess my generic question is, should we separate the Request and response objects in Rest API? or for code re usability try to use the same domain objects for both directions?
should we separate the Request and response objects in Rest API?
Yes - it allows to evolve both the request and response independently.
If you follow REST practices when a create is issued, you should return 201 - created and the ID of the newly created object.
If the client requires details about it, the client can use the ID + GET to get the full resource representation.
Also consider not exposing the domain objects directly through REST.
For example having a domain entity object - it will probably have some fields related to persistence layer like database Id, createdOn, createdBy - etc. Those fields should not be sent to the client. Use a simple StudentDto (StudentResponse, StudentResponseDto whatever you want to call it) representation, which holds only those fields, which are of interest to the client.
Keeping domain and response objects separate also gives you the ability to evolve them separately or change the data representation.
Imagine you have a JPA Entity and you use both JPA and Jackson annotations in the same class - it's very messy and hard to read and maintain.
Update:
If you're using the same object to be modified by the client and sent back to the server, I guess you could reuse it and model it like this:
get
#GetMapping("/students/{id}")
public StudentDto getStudent(#PathVariable long id) {
return studentService.get(id);
}
update (replace)
#PutMapping("/students/{id}/")
public ResponseEntity updateStudent(#PathVariable long id, #RequestBody StudentDto student) {
return new ResponseEntity(studentService.replaceStudent(id, student), HttpStatus.OK);
}
OR
update (partial update)
#PostMapping("/students/{id}/")
public ResponseEntity updateStudent(#PathVariable long id, #RequestBody StudentDto student) {
return new ResponseEntity(studentService.updateStudent(id, student), HttpStatus.OK);
}

JPA + JSON deserialization

I'm quite new to JPA and using Java as backend for REST services, and I'd like to store a JSON into the database, and wanted to check what is the best way to do so. Please let me know in case I'm taking the "long path".
(I'm using Spring)
My data:
{
frequency: "Week"
isGoalOrMore: "true"
name: "Develop"
targetValue: "5"
type: "Average"
}
Habit.java
#Entity
#Table(name = "habits")
public class Habit {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_habit_type")
private HabitType type;
private boolean isGoalOrMore; //basically means, achieve goal or do more
private double targetValue;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "id_frequency")
private Frequency frequency;
//getters and setters
}
HabitType.java
#Entity
#Table(name = "habits_type")
public class HabitType {
#Id
private Long id;
private String description;
}
DB Model (mysql)
habits
--id
--name
--id_frequency
--id_habit_type
habits_type
--id
--description
Problem
When I try to save this data I receive an error as below:
com.fasterxml.jackson.databind.JsonMappingException: Can not instantiate value of type [simple type, class com.tiperapp.model.HabitType] from String value ('Average'); no single-String constructor/factory method
How can I solve it?
By reading some topics, one option would be to write a JSON deserializer. Other cases they solved it by fixing their JPA.
I'd like to know which would be recommend.. can you guys please help me how to deserialize this, the best way ?
The best solution, IMHO, is to think of the data that you send and receive to/from the browser (which I'll call the DTOs), and the data that you store in the database, as two distinct things.
Sure, they often look the same. But they're different. You can't have exactly the same model for both. For example:
the entities constitute a huge graph, often containing bidirectional associations, and which has no limit, since everything can be loaded lazily. For example, an order line has an order, which has a buyer, which has an address, which has a city, which has a country, which has... You can't reasonably serialize all this graph when the browser asks for an order line, otherwise you'll serialize half of the database to JSON.
the JSON sometimes has a different representation of the same thing than the database. For example, you store all the habit types in the database as a row with an ID and a description, but it seems the JSON only really cares about the description. The ID seems to be a way to avoid duplicating the same description everywhere.
many attributes of the entities can not be seen (for security reasons) by the end user, or are only relevant for some use cases, etc.
So I would thus use different classes for both. When you receive a HabitDTO as JSON, you deserialize it with Jackson to a HabitDTO instance, and then find the HabitType entity correspondint to the description in the DTO, and create/update the Habit entity instance based on the corresponding information in the HabitDTO.
To recap: the entities contain the complete business model of your application, used to implement all the functional use cases. The DTOs contain serialized information and are used to transfer a small part of the information to/from the client, often for a specific use case. Having a clear distinction between the two allows much more flexibility: you can change the underlying persistence model without changing the interface of your services, or vice versa.
Your json is wrong.
type is not mapped to a string but to an object. You can do that by using this :
{
frequency: "Week"
isGoalOrMore: "true"
name: "Develop"
targetValue: "5"
type: {
description: "A DESCRIPTION"
id: "Average"
}
}

Spring Boot REST display id of parent only in a JSON response

Assume I have the following class:
public class ChildEntity {
...
#ManyToOne
private ParentEntity parent;
...
}
Now, I have a REST endpoint that retrieves a child entity object from the database, thus my JSON is the following:
{"id": "123", "name":"someName", "parent": { //parent fields here } ... }
I want to format my JSON responses in another way. I want parent display only the id from the database, instead of the whole object:
{"id": "123", "name":"someName", "parentId": "1" ... }
Basically returning entities directly from endpoints isn't a good idea. You make very tight coupling between DB model and responses. Instead, implement a POJO class that will be equivalent of the HTTP response you sent.
This POJO will have all ChildEntity fields and parentId only and will be constructed in HTTP layer.
Please, see the discussion in comments, basically such an object returned from web layer is not a DTO according to me.
I am annotating #JsonIgnore which ever field I do not want to be part of JSON response. Creating parallel POJO for each entity is costly affair.
#JsonIgnore
#NotNull
#Column(name="DELETED")
private boolean deleted = false;

Hibernate retrieved objects to Json using GSON

I have developed this webapp using Spring MVC + Hibernate.
I retrieve all my objects in a Service then return them directly to the controller. These objects generally are lazily initialized so collections are empty.
So for object User:
User
{
int idUser;
City city;
String name;
List<User> friends;
}
I return an object with just idUser and name, City and Friends are not initialized.
I want to take advantage of all my services methods (without modifying them) to provide a REST api, so if from my ApiController I request to get user with id 1, I retrieve all useful information about this user in JSON.
I tried using GSON but as soon as it tries to jsonize the city object it crashes because it has been lazily initialized. Same goes for the friends collection.
For collections it's not much of a big deal since in my api I would have another request url where you can get all friends given a user Id, but in the case of relationships with a single object (like the city in this example), I would like to return the id of the City which by definition of Lazy loading is indeed set.
How can I tell GSON to jsonize just the cityId attribute of City instead of the whole object?
Will nulling the rest of collections be a good solution so they're not converted into JSON? Is there any other way to explicitly tell GSON to ignore these attributes?
I believe you need to put your gson.toJson(...) within a transaction, in springMVC typically #Transactional at controller method where you are doing the actual serialization will do.
If you really want to skip fields or selectively serialize fields of using Gson, you can check https://stackoverflow.com/a/3341439 for gson exclusion strategy. You can skip based on Field Annotation or field name or the entire referenced class.
Gson gson = new GsonBuilder().setExclusionStrategies(new ExclusionStrategy() {
public boolean shouldSkipClass(Class<?> clazz) {
return <class exclusion logic, return true for exclusion else false>;
}
public boolean shouldSkipField(FieldAttributes f) {
return <field exclusion logic>;
}
}).create();