JAX-RS (Jersey) creates related object instead of null - java

problem is rather simple, but kinda hard to explain.
I have REST service like this:
#Path("categories")
#RequestScoped
public class CategoryResource {
#Inject
CategoriesFacade dao;
#PUT
#Consumes("application/json")
public void putJson(Categories content) {
System.err.println(content.toString()); // <-- ?????
dao.edit(content);
}
// ....
}
And entity object like this:
#XmlRootElement
public class Categories implements Serializable {
#Id
#Column(name = "CATEGORY_ID", unique = true, nullable = false, precision = 5)
private Integer categoryId;
#Column(name = "NAME")
private String name;
#JoinColumn(name = "PARENT_ID", referencedColumnName = "CATEGORY_ID")
#ManyToOne
private Categories parentId;
// ....
}
Here is http request payload:
PUT http://localhost:8080/api/categories
Data: {"categoryId":"7162","name":"test","parentId":null}
And here is Categories object i get in #PUT request (see line marked with ????):
Categories{
categoryId=7162,
name=test,
parentId=Categories{categoryId=null, name=null, parentId=null}
}
So as you can see here, parentId is assigned with empty Categories object, this way
i'm getting ValidationException, because name could not be null.
Any idea how to make Jersey to give me this kind of object, so null value for parentId will be fine:
Categories{
categoryId=7162,
name=test,
parentId=null
}

Stringify your object on client site (for example, if client on JavaScropt, using method JSON.stringify).
Rest method make
#PUT
#Consumes("application/json")
public void putJson(String content) {
And convert json string to object (for eample, using google json)
Categories cont = new com.google.gson.Gson().fromJson(content, Categories.class);

Related

How to map optional ID field to entity?

I have entity called Franchise and this entity looks like this:
#Data
#Entity
#Table(name = "franchises")
public class Franchise {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#Enumerated(EnumType.STRING)
private FranchiseType type;
#ToString.Exclude
#EqualsAndHashCode.Exclude
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "country_id")
private Country country;
}
I have following api endpoint for creating new Franchise entity: POST /api/franchise
And fields that I need to send are: name, type and countryId. countryId is optional field and can be null in the database (column country_id).
I am using mapstruct to map CreateFranchiseDTO to Franchise:
#Data
public class CreateFranchiseDTO {
private String name;
private String type;
private Long countryId;
}
#Mapper(componentModel = "spring")
public interface FranchiseDTOMapper {
#Mapping(source = "countryId", target = "country.id")
Franchise fromCreateFranchiseDTO(CreateFranchiseDTO dto);
}
When I call above mentioned api endpoint with countryId set to some integer (which exists in countries table) everythng works. But if I call endpoint without countryId field in request body and when the following code executes I get exception object references an unsaved transient instance:
Franchise franchise = franchiseDTOMapper.fromCreateFranchiseDTO(dto);
franchiseRepository.save(franchise);
When I look at the debugger I can see that franchise.getCountry() is equal to Country object with all fields set to NULL or zero/false (default values for primitives)
I have few options to solve this:
using #AfterMapping and check for franchise.getCountry().getId() == null
create following method in mapper: Country longToCountry(Long id)
But it seems to me that I am missing something or I am wrong. So how do I map an optional ID (relation) to field using mapstruct?
If I understand correctly, you have the same problem as this person.
What seems to have worked for that person was to use optional = true:
#ManyToOne(optional = true, fetch = FetchType.LAZY)

how get list of Object foreign key in Spring when use ManyToMany relation?

i'm have two model class.
one of them is user and other is degree.
each user can get more than one degree.
I want to send list of degrees along with Json when I create a new user in postbody.
my user class like this:
#Entity
#Table(name = "USERS")
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name = "";
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(
name = "USER_DEGREE",
joinColumns = #JoinColumn(name = "USER_FK"),
inverseJoinColumns = #JoinColumn(name = "DEGREE_FK")
)
private List<Degree> degreeList = new ArrayList<>();
public User setDegreeList(List<Degree> degreeList) {
this.degreeList = traitList;
return this;
}
public User setId(long id) {
this.id = id;
return this;
}
public User setName(String name) {
this.name = name;
return this;
}
}
and degree class have 3 attribute id, title, point.
in my controller i want when use #RequestBody for get user json in body, get all user degrees.
for example my controller :
#RequestMapping(value = "/user/add",method = RequestMethod.POST)
#ResponseBody
public Object sendTechnicalMessage(
HttpServletRequest request,
#RequestBody User user
){
return userService.createNewUser(request,user);
}
and my json body like this :
{
name:"abc",
degreeList:[1,2,4,6] // or [{id:1},{id:2},{id:4}]
}
how can do this ?
2 ways:
You can create a DTO class with field Set<Long> instead List<Degree>, convert User object to this UserDTO object and return it.
You can use this User class but with a specific Serializator. For this annotate the field with #JsonSerialize(using = SomeSerializer.class) and implement this serializer implementing JsonSerializer<Long> (or Set<Long> - I cannot say now, this is just idea).
Note: remember, that #ManyToMany fields by default are lazy (and almost always must be lazy) so use #Transactional to get a collection without exception.

ManyToOne referenced entities not exist in JSON Response when returning PagedResource

When I return a Page<Entity> from a method inside my #RestController class, all fields of Entity both referenced via #OneToXXX and #ManyToXXX take place in the returned JSON object. But when I switched the return type to PagedResource (to be able to add links to the response), #ManyToXXX fields are not included at all.
Here is the method in question:
#GetMapping("/fetch")
public PagedResources getResults(Pageable pageable, PagedResourcesAssembler assembler) {
Page<ParentClass> page = myRepository.findAll(pageable);
PagedResources pagedResources = assembler.toResource(page, myResourceAssembler);
return pagedResources;
}
Here is the resource assembler: it's #Autowired in the MyController's body.
MyResourceAssembler
#Component
public class MyResourceAssembler extends ResourceAssemblerSupport<ParentClass, Resource> {
public MyResourceAssembler() { super(MyController.class, Resource.class); }
#Override
public Resource toResource(ParentClass obj) {
return new Resource<>(obj,
linkTo(methodOn(MyController.class).getResults(obj.getId())).withRel("edit"),
}
}
Here are the basic class definitions:
ParentClass
#Entity
#Table(name = "parent_table", catalog = "myDB")
public class ParentClass implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#ManyToOne(optional = false)
#JoinColumn(name = "other_class", referencedColumnName = "id")
private OtherClass otherClass;
#OneToOne(mappedBy = "parent")
private SampleField1 sampleField1;
#OneToMany(mappedBy = "parent")
private List<SampleField2> sampleField2;
}
SampleField1 OneToXXX
#Entity
#Table(name = "sample_table_1", catalog = "myDB")
public class SampleField1 implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name="some_field")
String someField;
#OneToOne
#JoinColumn(name = "sample_field_1", referencedColumnName = "id")
#JsonBackReference //to avoid infinite recursion
private ParentClass parent;
}
OtherClass ManyToOne
#Entity
#Table(name = "other_table", catalog = "myDB")
public class OtherClass implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Integer id;
#Column(name="some_other_field")
String someOtherField;
// I don't need any reference to ParentClass here.
}
To add further detail to the issue here is the logging output of changeProperties() method inside PersistentEntityJackson2Module class:
s.d.r.w.j.PersistentEntityJackson2Module : Assigning nested entity serializer for #javax.persistence.OneToOne(..) com.project.SampleField1 com.project.model.ParentClass.sampleField1
s.d.r.w.j.PersistentEntityJackson2Module : Assigning nested entity serializer for #javax.persistence.OneToMany(..) com.project.SampleField2 com.project.model.ParentClass.sampleField2
// .... omitted other lines for brevity
the resulting JSON is :
{
"_embedded":{
"parentClasses":[
{
"id":1,
// <-- There is no field for otherClass !
"sampleField1":{
"id":1,
"sampleField":"blabla"
},
"sampleField2":[ ]
}
]
},
"links":[
]
}
As it can be seen above, OneToXXX fields are being taken to be serialized but no output for the ManyToOne fields like
Assigning nested entity serializer for #javax.persistence.ManyToOne ... com.my.OtherClass ... and therefore those aren't existed in the response JSON.
According to this SO answer, #ManyToXXX referenced entities are appended as links to the JSON response. But that's not an acceptable solution for me since I have a different planning of consumption in my mind for the rest client.
Bottomline, I'd like to have my ManyToOne referenced entities in my JSON Response returned from getResults() method.
Anything I can provide just ask in the comments.
Return Entity in responses is not the best way, because usually clients dont need whole set of data. Also, if Entities has links for each other, it will cause StackoverflowException on serialization tries. Use DTO for responses. At least it will help you to determine where is the problem - serialization, or fetching from database. Anyway it is more proper way for serving data to clients.
By the way, check getter and setter for otherClass in your ParentClass :) If threre is no getter and setter, thats will be reason of your issue.
Also, take a look into OtherClass for default empty constructor. If it hasn't present in there, you should add it.

API Rest with Spring Boot

I'm seeing some videos about API Rest with Spring Boot and so far I've done some basics and when I tried to increase the complexity I'm getting caught.
My idea is in the Post / class, create a new class with students getting the following json:
{
"nome": "Primeira Serie - A".
"alunos": [
"João",
"José",
"Maria"
]
}
And return:
{
"id_classe": 101
}
It happens that it saves the class, but it does not save the students and I have no idea how to show only the id of the class.
I have created the following classes in Java:
Model
Classe.java
package com.example.classe.model;
//Import's suppressed
#Entity
#Table(name = "classe")
public class Classe {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String nome;
#OneToMany(mappedBy = "classe")
private Set<Aluno> alunos = new HashSet<Aluno>();
//Get's e Set's suppressed
}
Aluno.java
package com.example.classe.model;
//Import's suppressed
#Entity
#Table(name = "aluno")
public class Aluno {
private static int tempID = 0;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
private String nome;
#ManyToOne
#JoinColumn(name = "id_classe")
#JsonBackReference
private Classe classe;
public Aluno(String nome) {
tempID++;
this.id = tempID;
this.nome = nome;
}
public Aluno() {
}
//Get's e Set's suppressed
}
Repository
ClasseRepository.java
package com.example.classe.repository;
//Import's suppressed
#Repository
public interface ClasseRepository extends JpaRepository<Classe, Integer> {
public List<Classe> findAll();
}
Controller
ClasseController.java
package com.example.classe.controller;
//Import's suppressed
#RestController
#RequestMapping("/classe")
public class ClasseController {
#Autowired
private ClasseRepository classeRepo;
#RequestMapping(method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public ResponseEntity<Collection<Classe>> getClasse() {
return new ResponseEntity<>(classeRepo.findAll(), HttpStatus.OK);
}
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> salvarClasse(#RequestBody Classe classe) {
return new ResponseEntity<>(classeRepo.saveAndFlush(classe), HttpStatus.CREATED);
}
}
Am I doing everything wrong or did I not understand the concept? But I wanted to understand how to do it that way.
Thanks in advance.
Cesar Sturion
What you want to achieve is totally doable, but requires several changes.
I split my answer into 2 parts:
Save the students
There are several problems with saving:
On POST your incoming json deserialized into objects in which Classe has a reference to Anuli, but Anuli doesn't have a reference toClasse. To check it you can add a break point at the line: return new ResponseEntity<>(... , run in debug mode and check fields of Anuli in Classe. To fix it you can add #JsonManagedReference on aluni field in Classe. Related question
Hibernate can't save referenced objects by default. You have to save them one by one after saving your Classe object or just turn on Cascade persisting. Related question
So, to fix 1 and 2 Classe should have:
#OneToMany(mappedBy = "classe", cascade = CascadeType.PERSIST)
#JsonManagedReference
private Set<Aluno> alunos = new HashSet<Aluno>();
You have to remove custom id generation in Alumi (I am talking about static int tempID). Annotation #GeneratedValue will perfectly generate id for you as soon as you persist an object. This custom generation breaks Hibernate support. I even not talking about that it also breaks the app after restart, not threadsafe etc.
Return id only
On POST returned json represent what was returned in classeRepo.saveAndFlush(classe) so it's an object of Classe.
If you want to return exactly this:
{
"id_classe": 101
}
Then create new class like this:
public class ClasseIdVO {
#JsonProperty("id_casse")
private Integer id;
// Constructors, getter, setter
VO - means View Object, so this object only for representation, not for persisting, etc.
You can use field name id_casse, but it's against Java code convention, so better add #JsonProperty.
Also change your saving code to new ClasseIdVO(classeRepo.saveAndFlush(classe).getId())
Or you can just return id as a number: classeRepo.saveAndFlush(classe).getId()

Response entity and response in client differ

I've got a Spring REST web application. I've got a method which returns a response entity.
#RequestMapping(value = "/shoes", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> getShoes() {
Collection<Shoes> shoes = shoesService.findAll();
ResponseEntity responseEntity = new ResponseEntity<>(shoes, HttpStatus.OK);
return responseEntity;
}
When I set a breakpoint on the last line, I can see that the responseEntity contains a list of the following objects:
Shoes{id=1, localization=Localization{id=1, city='Denver'}, category=Category{id=1, name='wellingtons', group='male'}, size=9}
But when I send the request in client app, I get a JSON, which contains only id and size:
{
"id": 1,
"size": 9
}
I wonder why I don't receive localization and category.
Here is the Shoes class:
#Table(name = "shoes")
public class Shoes{
#Column(name = "id")
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonBackReference
#ManyToOne
private Localization localization;
#JsonBackReference
#ManyToOne
private Category category;
#Column(name = "size")
private int size;
...
}
If you have bi-directional associations, you have to declare which object has the role of parent (annotated with JsonManagedReference) and which has the role of child (annotated with JsonBackReference) to break cycles.
You annotated both properties (localization, category) to not be serialized, see JsonBackReference:
Annotation used to indicate that associated property is part of two-way linkage between fields; and that its role is "child" (or "back") link. Value type of the property must be a bean: it can not be a Collection, Map, Array or enumeration. Linkage is handled such that the property annotated with this annotation is not serialized; and during deserialization, its value is set to instance that has the "managed" (forward) link.

Categories

Resources