JPA Repository doesn't load full JSON - java

I am working with REST API, and I am stacked cause of some weird issue.
I want to return as JSON, my request object, with all details and relations.
There is my repository:
#Repository("requestRepository")
public interface RequestRepository extends JpaRepository<Request, Integer> {
Request findByTitle(String title);
List<Request> findAll();
}
My Controller method:
enter image description here
{
#Autowired
private RequestServiceImpl requestService;
#RequestMapping("/getall")
public List<Request> findAll() {
for (Request req:requestService.findAllRequests()) {
System.out.println("Profession:" + req.getProfession().getName());
}
return requestService.findAllRequests();
}
}
Mapping:
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "profession_id")
private Profession profession;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "profession")
private List<Request> requests;
I also use #JsonManagedReference and #JsonBackReference with getters.
When I call http GET, I receive JSON that looks like:
enter image description here
[
{
"id": 1,
"title": "Hydraulik na juz!!!",
"minPayment": 200,
"maxPayment": 300,
"description": "short description",
"active": 1,
"creationDate": "2017-11-10"
},
{
"id": 2,
"title": "Potrzebny kierowca do Warszawy",
"minPayment": 100,
"maxPayment": 700,
"description": "another desc...",
"active": 1,
"creationDate": "2017-11-10"
}
]
There is no relation between profession and request in that JSON. Even in my database, I see relation between them:
enter image description here
I tried changing fetch type from LAZY to EAGER, and it didn't help. I don't know what is the reason that this JSON has no relation between profession and request

Related

#JsonIgnore just for some specific endpoints

I have been struggling to solve this issue on my project: Is possible to use the annotation #JsonIgnore only when endpoint has an specific value?
For example, i want to use the annotation when endpoint.equals("xxxxxxxxx"), but not use when endpoint.equals("yyyyyy").
There are 3 classes with these relationship annotations:
Client
#OneToMany(mappedBy = "ownerOfTheProduct")
#JsonIgnore
private List<Product> ownProducts = new ArrayList<>();
Category
#JsonIgnore
#OneToMany(mappedBy = "category")
private List<Product> products;
Product
#ManyToOne
#JoinTable(name = "PRODUCT_CATEGORY", joinColumns = #JoinColumn(name = "product_id"), inverseJoinColumns = #JoinColumn(name = "category_id"))
private Category category;
#ManyToOne
#JoinTable(name = "CLIENT_PRODUCT", joinColumns = #JoinColumn(name = "product_id"), inverseJoinColumns = #JoinColumn(name = "client_id"))
private Client ownerOfTheProduct;
The point is:
If i dont put the #JsonIgnore, i get a StackOverflow error, the json gets into looping and wont stop.
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning",
"products": [
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
...
When i mapped in a different way, and put the #JsonIgnore into the both classes: Client and Product, it works, the loopings were not more hapenning. However, when i have to use other endpoint, which the fields products and ownerOfTheProduct need to show up through api, it doesnt work cuz the #JsonIgnore is annotated.
LOOPING SOLVED
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning"
},
"ownOfTheProduct": {
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
}
}
OTHER ENDPOINTS ARE NOT WORKING
{
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
}
I'd like the field that i have mapped with #JsonIgnore (ownProducts) shows up in this request exactly this way:
{
"id": 1,
"name": "Edited",
"cpf": "Edited",
"email": "test",
"password": "test"
"ownProducts" [
{
"id": 1,
"name": "Product name",
"price": 20.0,
"category": {
"id": 1,
"name": "Cleaning"
},
]
}
Is there a way to change this? Summing up, i just want to use #JsonIgnore with especific especific endpoints, not every single endpoint on my API.
I hope yall got my question, anyway here is the link of the repository on github: https://github.com/reness0/spring-restapi-ecommerce
You cant use only #JsonIgnore but you can use #JsonView and #JsonIdentityInfo annotations from com.fasterxml.jackson.core
How it works:
You need define class with interfaces. For example:
public class SomeView {
public interface id {}
public interface CoreData extends id {}
public interface FullData extends CoreData {}
}
Mark entity fields with #JsonView(<some interface.class>)
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#JsonView(SomeView.id.class)
private Long id;
#Column(nullable = false)
#JsonView(SomeView.CoreData.class)
private String username;
#Column(nullable = false)
#JsonView(SomeView.FullData.class)
private String email;
}
Annotate endpoint with #JsonView(<some interface.class>)
#GetMapping()
#JsonView(SomeView.FullData.class)
public User getUser() {
return <get user entity somwhere>
}
In case #JsonView(SomeView.id.class) you will get this JSON:
{
id: <some id>
}
In case #JsonView(SomeView.CoreData.class):
{
id: <some id>,
username: <some username>
}
In case #JsonView(SomeView.FullData.class):
{
id: <some id>,
username: <some username>,
email: <some email>
}
#JsonView also works with embeded objects and you can annotate one field with multiply views classes - #JsonView({SomeView.FullData.class, SomeOtherView.OtherData.class})
About Cycleing JSON. Annotate your entity class with
#JsonIdentityInfo(
property = "id",
generator = ObjectIdGenerators.PropertyGenerator.class
)
Every time when JSON serialization go in circles object data will be replaced with object id or orher field of entity for your choose.
Or as alternative you can just use DTO classes
While this is not possible to achieve using the annotation based approach (annotations make it static), you can achieve the same using any data mapper library. Create a filter based on the attribute from API. Orika library can be used: https://www.baeldung.com/orika-mapping

Parent Entity is not populated in #OneToMany. Hibernate Bidirectional

hi newbie to Hibernate,
My entity classes
#Entity
public class User {
#Id
#GeneratedValue//How to restrcit by passing id from response
#JsonIgnore
private Integer userId;
#OneToMany(mappedBy = "user")
private List<PostEntity> postEntity;
}
#Entity
#Table(name="post")
public class PostEntity {
#Id
#GeneratedValue
#JsonIgnore
#ApiModelProperty(required=false)
private Integer id;
#ManyToOne(fetch=FetchType.LAZY)
private User user;
}
When I fetch User its populating Post entity as well.
URI: http://localhost:8080/jpa/users
[
{
"name": "Abdul",
"birthDate": "2018-07-25T01:29:51.895+0000",
"postEntity": [
{
"description": "My Fourth Post"
},
{
"description": "My First Post"
},
{
"description": "My Second Post"
}
]
},
{
"name": "Anji",
"birthDate": "2018-07-25T01:29:51.903+0000",
"postEntity": []
},
{
"name": "Naren",
"birthDate": "2018-07-25T01:29:51.903+0000",
"postEntity": []
}
]
but this is not the case in reverse. When I fetch post its skipping User entity.
URI: localhost:8080/jpa/users/101/posts/11001
Response:
{
"description": "My First Post"
}
Why its not populating user information in the above JSON response.
Fetching Methods:
User:
#GetMapping("/jpa/users")
public List<User> retAll(){
return userRepository.findAll();
}
Post:
#GetMapping("/jpa/users/{uid}/posts/{pid}")
public Resource<PostEntity> postE(#PathVariable int uid,#PathVariable int pid) {
Optional<PostEntity> post = postRepository.findById(pid);
if (!post.isPresent()) {
throw new UserNotFoundException("POst");
}
PostEntity ePost = post.get();
Resource<PostEntity> resource = new Resource<PostEntity>(ePost);
return resource;
}
Please help.
That is actually the intended way REST is supposed to work.
GET at /users : all users
GET at /users/1 : information of user 1 and all its children
GET at /users/1/posts : all posts of user 1
GET at /users/1/posts/10 : information of post 10 and all its children from user 1
As you're calling /users/101/posts/11001, the endpoint will give you the information of one post (id 11001) from one user (id 101).
There are two common ways to get the parent information:
The fastest way would be to just call /users and filter for your desired post in the frontend.
The right way would be changing the model of your post (PostEntity.java) to contain its "parent" User object, so when you make a REST call for a post, the user object gets populated.
Further reading:
https://softwareengineering.stackexchange.com/questions/274998/nested-rest-urls-and-parent-id-which-is-better-design
Maybe it's a good idea to read some REST best practices:
https://hackernoon.com/restful-api-designing-guidelines-the-best-practices-60e1d954e7c9
Try to use FetchType
#Entity
public class User {
#Id
#GeneratedValue//How to restrcit by passing id from response
#JsonIgnore
private Integer userId;
#OneToMany(mappedBy = "user", fetch=FetchType.EAGER)
private List<PostEntity> postEntity;
}
Beware of the performance.

Spring Entity OneToMany Ignore Self

Currently, I have the following relationship between two entities:
#Entity
public class Pokemon {
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "trainer_id")
#JsonIgnoreProperties("pokemons")
private Trainer trainer;
}
and also:
#Entity
public class Trainer {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,
mappedBy = "trainer")
private Set<Pokemon> pokemons = new HashSet<>();
}
Everything is fine when updating the entities. However, when I attempt to retrieve all trainers, it includes the trainer within the pokemon, which I don't want. Observe:
GET ALL POKEMON (all is fine):
{
"id": 1,
"name": "squirtle",
"type": "water",
"trainer": {
"id": 1,
"name": "Ash Ketchum",
"level": 1
}
}
GET ALL TRAINERS:
{
"id": 1,
"name": "Ash Ketchum",
"level": 1,
"pokemons": [
{
"id": 1,
"name": "squirtle",
"type": "water",
"trainer": {
"id": 1,
"name": "Ash Ketchum",
"level": 1
}
}
]
}
Notice how the trainer class is returned within each pokemon inside of the the pokemons set? I'd prefer not to return that, since I already have access to that information. Is there anyway I can tell the entity not to return its own info from the Pokemon class? If it helps, my retrieval query looks like this:
public List<Trainer> getAllTrainers() {
em.getTransaction().begin();
List<Trainer> trainer = em.createNativeQuery("SELECT * FROM
Trainer", Trainer.class).getResultList();
em.getTransaction().commit();
return trainer;
}
Thank you so much.
For anyone interested, I was able to achieve my goal by telling my Trainer class to ignore the trainer property in the Pokemon object via #JsonIgnoreProperties
#Entity
public class Trainer implements Serializable {
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "trainer")
#JsonIgnoreProperties(value = "trainer")
private Set<Pokemon> pokemons = new HashSet<>();

Java - Jackson, De-Serializing subclasses

Lets assume I have the following JPA entities:
#MappedSuperclass
public abstract class BaseForumPersistable {
#Id
Long id;
String title;
Date creationDate;
#ManyToOne
User user;
//getters, setter
}
#Entity
public class ThematicArea() {
#OneToMany(mappedBy="thematicArea")
List<Topic> topics;
//getters, setters
}
public class Topic() {
String status;
boolean isSticky;
#ManyToOne
ThematicArea thematicArea;
#OneToMany
List<Post> posts
//getters, setters
}
I also use these entities for my REST Controllers that handle POST requests. So for instance for Topic I have an /api/topics endpoint. When I send something like this as a JSON object
{
"user": {
"id": 3,
"role": "Admin"
},
"thematicArea": {
"id": 1
},
"title": "asdf",
"status": "Active"
}
It fails to create the Thematic Area, although it perfectly creates the User entity. The controller signature is as follows:
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> create(#RequestBody final Topic entity)
So when I use the debugger with a breakpoint, the ThematicArea entity is not even de-serialized.
Moreover if I send an object like this:
{
"user": {
"id": 3,
"role": "Admin"
},
"thematicArea": {
"id": 1,
"title": "topic_title"
},
"title": "asdf",
"status": "Active"
}
Which now also includes a title field in the ThematicArea object the two title fields get mixed up. This leads me to believe that it's an issue of de-serializing. Any ideas how i can fix this.

Unable to persist Json object with Spring Data having #OneToMany relationship

I have an entity called Sale, which has list of objects SaleData, I have set up unidirectional #OneToMany relationship between these two. Now I ran into a problems when doing CRUD operations on my Sale object.
CascadeType set to ALL.
#Entity
#Table(name = "sale")
public class Sale extends BaseEntity {
#OneToOne(cascade = CascadeType.DETACH)
private Client client;
#OneToMany(cascade = {CascadeType.ALL}) // <--
#JoinColumn(name = "sale_id", nullable = false)
private List<SaleData> saleData = new ArrayList<>();
}
Save (POST) and Delete works. What's causing problem is when POST-ing for update, my JSON sent from Angular controller looks like that:
{"id": 1, "client": {
"id": 1,
"firstName": "Test",
"lastName": "Client"
}, "saleData": [
{
"id": 1,
"employee": {
"id": 1,
"firstName": "Herp",
"lastName": "Derp"
}
},
{
"id": 2,
"employee": {
"id": 1,
"firstName": "John",
"lastName": "Smith"
}
}
]}
Update call from Spring service throws:
org.hibernate.PersistentObjectException: detached entity passed to persist: app.sales.SaleData
Service implementation
#Override
#Transactional(rollbackFor = SaleNotFoundException.class)
public Sale update(#NotNull SaleDTO updated) {
Sale sale = repository.findOne(updated.getId());
if (sale == null)
throw new SaleNotFoundException("Sale not found");
return transformSaleObject(sale, updated);
}
As I understand, cascade ALL doesn't make the sale object Deattached, because in 2nd scenario where I replace the type with any combination of...
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.DETACH})
.. then Update and Delete works, but Saving a new object does not. I haven't used Hiberate that much, but maybe anyone knows what's causing this behaviour? Is it the matter of defining relationship wrong way?
Update:
The transform method is simply this, but anyway seems like it's all solved for me now :)
private Sale transformSaleObject(Sale sale, SaleDTO dto) {
sale.setClient(dto.getClient());
sale.setSaleData(dto.getSaleData());
sale.setService(dto.getService());
sale.setHours(dto.getHours());
sale.setPrice(dto.getPrice());
sale.setSaleStatus(dto.getSaleStatus());
sale.setSaleDate(dto.getSaleDate());
return sale;
}
I changed my update method as follows:
public Sale update(#NotNull SaleDTO updated) {
Sale sale = repository.findOne(updated.getId());
if (sale == null)
throw new SaleNotFoundException("Sale not found");
transformSaleObject(sale, updated);
return repository.saveAndFlush(sale);
}

Categories