#OneToMany column is not present in JSON - java

I have the following entities:
Book.java
#Entity #Data
public class Book {
#Id
private Long id;
#Column(unique = true, nullable = false)
private String title;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "book")
#JsonManagedReference
private List<Note> notes;
}
Note.java
#Entity #Data
public class Note {
#Id
private Long id;
#Column(nullable = false)
private String title;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "book_id", nullable = false)
#JsonBackReference
private Book book;
}
When I call my BookRestController, it returns a JSON containing all the properties I need:
{
"id": 15,
"title": "A fé explicada",
"author": "Leo J. Trese",
"notes": [{
"id": 10,
"title": "Sobre o pecado mortal"
}]
}
But when I call NoteRestController, the Book attribute is missing:
{
"id": 10,
"title": "Sobre o pecado mortal"
// missing "book" property here...
}
What am I doing wrong?
I'm using #OneToMany and #ManyToOne annotations to declare that it's a 1-N relationship; #JsonBackReference and #JsonManagedReference have the simple purpose to avoid infinite recursion.

Of course Book is omitted, that is what literally #JsonBackReference is for
#JsonBackReference is the back part of reference – it will be omitted from serialization.
(https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion)
The solution is as simple as it sounds: Instead of returning a Note entity in your REST controller (which I try to avoid as far as possible anyway to keep entities as entities that don't have any use in the REST context anyway) you can create a explicit transport object called e. g. NoteDTO which contains a reference to a book (that omits the notes of the book to not have infinite recursion):
public class NoteDTO {
private Long id;
private String title;
private BookReferenceDTO book;
// getters and setters
}
public class BookReferenceDTO {
private Long id;
private String title;
// getters and setters
}

Related

Hibernate Entity Mapping

Good night everyone,
I want to model a database that has the following entities and their perspective relationships:
But everytime I run the Java project to create the model at database, what I create is something like this:
There is another way to map this relationship? I'm mapping like that:
Article entity:
#Entity
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private Boolean featured;
#Column(nullable = false)
private String title;
#Column(nullable = false)
private String url;
#Column(name = "image_url", nullable = false)
private String imageUrl;
#Column(name = "news_site", nullable = false)
private String newsSite;
#Column(nullable = false)
private String summary;
#Column(name = "published_at", nullable = false)
private String publishedAt;
#OneToMany
#JoinColumn(name = "launches_id")
private List<Launches> launches;
#OneToMany
#JoinColumn(name = "events_id")
private List<Events> events;
}
Launches entity
#Entity
public class Launches {
#Id
private String id;
private String provider;
}
Events entity:
#Entity
public class Events {
#Id
private Long id;
private String provider;
}
And I want to map this JSON, with this same launcher and events appearing in other articles:
{
"id": 4278,
"title": "GAO warns of more JWST delays",
"url": "https://spacenews.com/gao-warns-of-more-jwst-delays/",
"imageUrl": "https://spacenews.com/wp-content/uploads/2019/08/jwst-assembled.jpg",
"newsSite": "SpaceNews",
"summary": "",
"publishedAt": "2020-01-28T23:25:02.000Z",
"updatedAt": "2021-05-18T13:46:00.284Z",
"featured": false,
"launches": [
{
"id": "d0fa4bb2-80ea-4808-af08-7785dde53bf6",
"provider": "Launch Library 2"
}
],
"events": []
},
{
"id": 4304,
"title": "Government watchdog warns of another JWST launch delay",
"url": "https://spaceflightnow.com/2020/01/30/government-watchdog-warns-of-another-jwst-launch-delay/",
"imageUrl": "https://mk0spaceflightnoa02a.kinstacdn.com/wp-content/uploads/2020/01/48936479373_2d8a120c8e_k.jpg",
"newsSite": "Spaceflight Now",
"summary": "",
"publishedAt": "2020-01-30T04:08:00.000Z",
"updatedAt": "2021-05-18T13:46:01.640Z",
"featured": false,
"launches": [
{
"id": "d0fa4bb2-80ea-4808-af08-7785dde53bf6",
"provider": "Launch Library 2"
}
],
"events": []
}
According to your diagram, it should be:
#ManyToOne
#JoinColumn(name = "launches_id")
private Launches launches;
#ManyToOne
#JoinColumn(name = "events_id")
private Events events;
...and not #OneToMany ;) (Can there be an "Article" (with id=x) having launchers_id=y AND launchers_id=z? No, vice versa!:)
...for the #OneToMany, you should find the join columns "on the other side" (of relationship(s)).
According to your JSON, it is OneToMany. But then, we have to draw/expect:
#Entity
class Article {
//... id, columns, blah
#OneToMany
#JoinColumn(name = "article_id") // Launches "owns the relationship"/column
private List<Launches> launches;
#OneToMany
#JoinColumn(name = "article_id") // Events...!
private List<Events> events;
}
Generally, (when you expose your db model via json,) ensure:
no "circles" (in bi-directional associations). (#JsonManagedReference, #JsonBackReference, #JsonIgnoreProperties, ... )
not to expose data, that you don't want to expose. (#JsonIgnoreProperties, ...)
Regarding Hibernate-ManyToOne, please refer to https://vladmihalcea.com/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/
Regarding spring-data-jpa, best to:
gs-data-jpa
gs-data-rest
spring-boot-ref, data-jpa
reference-doc, data-jpa
reference-doc, data-rest

Spring JPA ManyToMany with additional table persists with null id

I have these entities:
#Entity
#Data
#NoArgsConstructor
public class Hero {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotNull
private String name;
#OneToMany(mappedBy = "character", cascade = CascadeType.ALL, orphanRemoval = true)
#NotEmpty
private List<HeroSkill> heroSkills;
}
#Entity
#Data
#NoArgsConstructor
public class Skill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false)
private String name;
}
#Entity
#Data
#NoArgsConstructor
public class HeroSkill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name = "hero_id", referencedColumnName = "id")
#JsonIgnore
private Hero hero;
#ManyToOne
#JoinColumn(name = "skill_id", nullable = false)
private Skill skill;
private int ranks;
}
My HeroService is like this:
#Transactional
public void create(Hero hero) {
heroRepository.save(hero);
}
I am using postman to create a new Hero and this is a sample POST request:
{
"name": "Test",
"heroSkills": [
{
"skill": {
"id": 1
},
"ranks": 4
},
{
"skill": {
"id": 2
},
"ranks": 4
}
]
}
Although a hero is created and two entities on HeroSkill table are also created, the HeroSkill hero_id column is null, so my new heroes are not associated with their skills as they should be.
It works if I modify my service like this:
#Transactional
public void save(Hero hero) {
Hero result = heroRepository.save(hero);
hero.getHeroSkills().stream()
.forEach(it -> {
it.setHero(result);
heroSkillRepository.save(it);
});
}
But I think that I shouldn't have to pass the Hero manually to each HeroSkill. Isn't that the point of adding CascateType.ALL (which includes CascadeType.PERSIST)?
What is the correct approach to that?
Thanks in advance.
There is no reference of Hero(Parent) in HeroSkill(child) that's why JPA can't resolve hero_id and set as null. Use this heroSkill.setHero(hero) to set parent in child.
To save child with parent in bidirectional relation you have to make sync both side.
hero.getHeroSkills().stream()
.forEach(it -> {
it.setHero(hero);
});
Hero result = heroRepository.save(hero);

Working with Foreign keys in JpaRepository

I am developing simple API for practice project Online Shopping system. Since I am very new in working with APIs, I am having a trouble with my Entities and relationships. First, I give all my schema and classes before introduce the problem.
Here is a link for my database schema.
These are #Entity classes:
----
#Entity
#Table(name = "Customer")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "country")
private String country;
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
// constructor, getters, setters ....
#Entity
#Table(name = "Order")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "date")
private Date date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cust_id", nullable = false)
#JsonIgnore
private Customer customer;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Detail> details = new ArrayList<>();
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Invoice> invoices = new ArrayList<>();
//constructor, setters, getters ....
#Entity
#Table(name = "Product")
public class Product {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#Column(name = "price")
private Double price;
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Detail> orderDetails = new ArrayList<>();
//cons, setters, getters ...
#Entity
#Table(name = "Detail")
public class Detail {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ord_id", nullable = false)
#JsonIgnore
private Order order;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "pr_id", nullable = false)
#JsonIgnore
private Product product;
#Column(name = "quantity")
private int quantity;
//similar classes for Invoice and Payment (no problem with them)
Here is my Sample Repository class:
#Repository
public interface ProductRepository extends JpaRepository<Product, Integer> {
}
Also here is my controller class:
#RestController
public class OnlineShoppingApiController {
#Autowired
ProductRepository productRepository;
#Autowired
OrderRepository orderRepository;
#Autowired
CustomerRepository customerRepository;
#Autowired
DetailRepository detailRepository;
#Autowired
InvoiceRepository invoiceRepository;
#Autowired
PaymentRepository paymentRepository;
#GetMapping("/products")
public List<Product> getProductsList(){
return productRepository.findAll();
}
#GetMapping("/customers")
public List<Customer> getCustomersList(){
return customerRepository.findAll();
}
#GetMapping("/orders")
public List<Order> getOrdersList(){
return orderRepository.findAll();
}
#GetMapping("/invoices")
public List<Invoice> getInvoicesList(){
return invoiceRepository.findAll();
}
#GetMapping("/payments")
public List<Payment> getPaymentsList(){
return paymentRepository.findAll();
}
#GetMapping("/details")
public List<Detail> getDetailsList(){
return detailRepository.findAll();
}
I am doing the same approach for all APIs and relationships.
When I call for /products in postman, I am getting result JSON like this:
[{
"id": 3,
"name": "pname_816",
"description": "pdesc_871_871_871_87",
"price": 1.41,
"orderDetails": [
{
"id": 9,
"quantity": 831
},
{
"id": 51,
"quantity": 701
},
{
"id": 87,
"quantity": 310
}
]
},
{
"id": 4,
"name": "pname_395",
"description": "pdesc_495_495_495_49",
"price": 26.65,
"orderDetails": [
{
"id": 85,
"quantity": 853
}
]
}]
Same fine results for /details, /invoices, and /payments.
The problem is if I send GET request for /customers, the result:
{
"timestamp": "2018-04-05T11:53:39.558+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Could not write JSON: could not extract ResultSet; nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not extract ResultSet (through reference chain: java.util.ArrayList[0]->com.example.project.pojo.Customer[\"orders\"])",
"path": "/customers"
}
And if i send request for /orders, the result is:
{
"timestamp": "2018-04-05T11:54:37.316+0000",
"status": 500,
"error": "Internal Server Error",
"message": "could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet",
"path": "/orders"
}
The same approach is not working for all. I cannot understand where the problem is. Please help me to find it.
Thanks for the answer
I have finally found the answer by myself. Here the problem is not with Annotation or key referencing but with the naming the Entities.
Since order is reserved keyword for MySql, naming the entity and variables like this causes unexpected problems.
So I have just changed the Entity name to Orders in schema and code and working fine.
Hope this post will help for others too

How to get just an entity id instead of the entire entity in JSON response

I saw several similar questions but non with an answer that worked for me.
I am working on converting an existing project over to use Hibernate. I am using Mysql for the database. I have a class like this:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#ManyToOne
#JoinColumn(name = "group_id_user")
private Group group;
#Column(name = "name")
private String name;
...
// getters and setters....
}
and a class like this:
#Entity
#Table(name = "group")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "group_id")
private Long groupId;
#Column(name="group_name")
private String groupName;
...
// getters and setters....
}
When I fetch a User item from the database I convert it to JSON and end up with a result in this format:
{
"user": {
"userId": 1,
"group": {
"groupId": 3,
"groupName": "Group Three",
},
"name": "John Doe",
}
}
In my case, the Group object is actually quite a bit bigger than in this example and also contains references to another table as well (i.e. Group has a SomeOtherClass object). I want to end up with something that looks like the following:
{
"user": {
"userId": 1,
"groupId": 3,
"name": "John Doe",
}
}
Is there a way to just get the groupId in my JSON response instead of the entire Group object?
I have tried adding FetchType.Lazy to my User class
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "group_id_user")
private Group group;
but when I try to convert the User object to Json I get the following error:
error: Errorjava.lang.UnsupportedOperationException: Attempted to serialize java.lang.Class: org.hibernate.proxy.HibernateProxy. Forgot to register a type adapter?
I found this answer which gets rid of the error, but then the JSON object is exactly the same as it was before I added the FetchType.LAZY. It looks like somehow converting to JSON adds in the values that the FetchType.LAZY was ignoring.
I am using converting to JSON as follows:
GsonBuilder b = new GsonBuilder();
Gson gson = b.create();
//b.registerTypeAdapterFactory(HibernateProxyTypeAdapter.FACTORY);
com.google.gson.JsonArray jsonArray = gson.toJsonTree(out).getAsJsonArray();
Correct me if I wrong, as I'm new to hibernate. Based on my experience so far, Yes it is possible to send only object ID instead of sending the whole object details in response. By using #JsonIdentityReference and #JsonIdentityInfo find documentation here
Below is the sample code worked for me:
Group Class
#Entity
#Table(name = "group")
public class Group {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "group_id")
private Long groupId;
#Column(name="group_name")
private String groupName;
...
// getters and setters....
}
User Class
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#ManyToOne
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "group_id")
#JsonIdentityReference(alwaysAsId = true)
#JoinColumn(name = "group_id_user")
private Group group;
...
// getters and setters....
}
Result is
{
"userId": 1,
"group": 1,
"name": "Sample User" }
I used below code to convert JSON
ObjectMapper mapper = new ObjectMapper();
try {
json = mapper.writeValue(user);
} catch (JsonProcessingException e) {
e.printStackTrace();
}
I found the solution for my issue here
Option 1: (from #JBNizet's comment)
Create classes (DTOs) which represent the output of your API, transform your entities to instances of these DTO classes, and use Gson to serialize the DTOs to JSON. Good REST frameworks do the last part automatically for you.
For this example you could create a UserItem class that looked like this:
public class UserItem {
private Long userId;
private Long groupId;
private String name;
...
// getters and setters....
}
Then you would need a method that converts the User entity to a UserItem:
public UserItem UserToUserItem(User user) {
UserItem userItem = new UserItem();
userItem.setUserId(user.getUserId());
userItem.setGroupId(user.getGroup().getGroupId());
userItem.setName(user.getName());
return userItem;
}
The use the userItem in your response.
Option 2:
UPDATE: Updated option 2 to something that works
If you don't want to write DTOs for each entity class because (like this example) the only field that needs changed is the Group object to the groupId you can create an ExclusionStrategy and use the #Expose annotation as follows:
First create a class that implements ExclusionStrategy
public class SerializeExclusionStrat implements ExclusionStrategy {
#Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
final Expose expose = fieldAttributes.getAnnotation(Expose.class);
return expose != null && !expose.serialize();
}
#Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
}
#Entity
#Table(name = "user")
public class User {
Then add the #Expose annotation to User class. Adding the additional field groupId will allow the groupId to be returned in your queries and in your json responses. Just make sure to mark groupId with updatable=false and insertable=false.
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "user_id")
private Long userId;
#ManyToOne
#JoinColumn(name = "group_id_user")
#Expose(serialize = false)
private Group group;
#Column(name = "group_id_user", updatable = false, insertable = false)
private Long groupId;
#Column(name = "name")
private String name;
...
// getters and setters....
}
Then set the Exclusion strategy when creating the Gson:
GsonBuilder b = new GsonBuilder();
b.serializeNulls();
b.setExclusionStrategies(new SerializeExclusionStrat());
Gson gson = b.create();

Hibernate ORM: Saving Parent Entity Saves the Children too?

I have the below JSON as input:
{
"type": "Student",
"numOfPeople": "1",
"tenantMembers": [
{
"firstName": "Chris",
"lastName": "C"
}
],
"tenantDetails": {
"firstName": "John",
"lastName": "J",
"email" "xyz#gmail.com"
}
}
I want to use this to do a save:
tenantRepo.save(tenant);
This should save the parent "Tenant" and the children "TenantMembers" and "TenantDetails".
But when I do it does with NULL 'tenant_id's in the children. (If I have foreign keys in the DB gives 'tenant_id' can't be null constraint exception)
My question is: Is this possible in Hibernate?
My models:
Parent class:
#Entity
#Table(name = "tenant")
public class Tenant {
#GeneratedValue
#Id
private Long id;
private String type;
#Column(name = "num_of_people")
private String numOfPeople;
#OneToMany(mappedBy = "tenant", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<TenantMember> tenantMembers;
#OneToOne(mappedBy = "tenant", cascade = CascadeType.ALL)
private TenantDetails tenantDetails;
TenantMember child class:
#Entity
#Table(name = "tenant_member")
public class TenantMember {
#GeneratedValue
#Id
private Long id;
#ManyToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
TenanatDetails child class:
#Entity
#Table(name="tenant_details")
public class TenantDetails {
#GeneratedValue
#Id
private Long id;
#OneToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String email;
EDIT:
Following up Dragan Bozanovic's suggestion, tried using #JsonIdentityInfo
for the three tables:
#Entity
#Table(name = "tenant")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Tenant {
#Entity
#Table(name="tenant_details")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantDetails {
#Entity
#Table(name = "tenant_member")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantMember {
and did the following to save:
#RequestMapping(value = "/set", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Tenant test(#RequestBody Tenant tenant) {
Tenant t = new Tenant();
t.setType(tenant.getType());
t.setNumOfPeople(tenant.getNumOfPeople());
tenantRepo.save(t);
tenant.setId(t.getId());
tenant.getTenantDetails().setTenant(tenant);
for(TenantMember member: tenant.getTenantMembers()) {
member.setTenant(tenant);
}
return tenantRepo.save(tenant);
}
Would this be the best approach that is possible?
Hibernate does save the children (hence the constraint violation) because of the cascading options you specified, but it does not save the relationship information (join column value) in your case.
TenantMember and TenantDetails are the owners of the association with Tenant (mappedBy attributes in the association annotations in Tenant).
That means that you have to properly update the tenant field in the TenantMember and TenantDetails instances, because Hibernate ignores inverse side of the association when maintaining the relationship.

Categories

Resources