Duplicate data for OneToMany mapping in hibernate - java

While doing insert in Hibernate for OneToMany mapping. Same data is getting inserted twice.
Employee:
#Entity
public class Employee {
#Id
#GeneratedValue
private int empId;
private String name;
private String designation;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "employee", fetch = FetchType.EAGER, orphanRemoval = true)
private Set<Address> addresses = new HashSet<>();
}
Address:
#Entity
public class Address {
#Id
#GeneratedValue
private int addressID;
private String city;
private String country;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "empId")
#JsonIgnore
private Employee employee;
}
When I use POST request to save the data:
Save request for 1st employee:
{
"name": "Test1",
"designation": "SAL1",
"addresses": [
{
"city": "Delhi",
"country": "India"
},
{
"city": "Noida",
"country": "India"
}
]
}
Save request for 2nd employee:
{
"name": "Test2",
"designation": "SAL2",
"addresses": [
{
"city": "Delhi",
"country": "India"
},
{
"city": "Noida",
"country": "India"
}
]
}
In this case in Address tables contains 4 entries 2 each for Noida:India and Delhi:India.
I wanted to understand what is the best way to have only 1 entry for each address data i.e 1 for Noida:India and 1 for Delhi:India.

Create a separate table which stores location entries. Have the key for this table be a composite key of "country" and "city". Then, create a separate address table which has a foreign key column for "user" and the table containing countries and cities.

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

Infinite Recursion with Jackson JSON and Hibernate JPA

I want to store some data into database through OnetoMany and ManytoOne Bidirectional relationship mapping. While request for persist data in postman get infinite row in response.
Here down is my code:
Entity
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer author_id;
private String name;
private String language;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "author")
private Set<Book> book;
// getter setter
}
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String title;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author_id")
private Author author;
// getter setter
}
Service
#Override
public Author insertAuthor(Author author) {
Set<Book> bookList = new HashSet<>();
Book book = new Book();
book.setTitle(book.getTitle());
bookList.add(book);
book.setAuthor(author);
author.setBook(bookList);
return authorRepo.save(author);
}
Controller
#RestController
public class PojoController {
#Autowired
private PojoService pojoService;
#RequestMapping(value="/book", method = RequestMethod.POST)
public Author addBookCourse(#RequestBody Author author) {
return this.pojoService.insertAuthor(author);
}
}
Request
{
"language": "english",
"name": "Banjamin franklin",
"book": [{
"title": "Theory Of Everything"
},
{
"title": "A Brief Story Of Time"
}]
}
Output
{
"author_id": 1,
"name": "Banjamin franklin",
"language": "english",
"book": [
{
"id": 1,
"title": null,
"author": {
"author_id": 1,
"name": "Banjamin franklin",
"language": "english",
"book": [
{
"id": 1,
"title": null,
"author": {
"author_id": 1,
"name": "Banjamin franklin",
"language": "english",
"book": [
{
"id": 1,
"title": null,
"author": {
"author_id": 1,
"name": "Banjamin franklin",
"language": "english",
"book": [
{
"id": 1,
"title": null,
more 7460 line
.......
.......
.......
{
"timestamp": "2021-11-30T10:25:03.957+00:00",
"status": 200,
"error": "OK",
"trace": "org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.rest.RestApiPojo.Entity.Author[\"book\"]->org.hibernate.collection.internal.PersistentSet[0]->com.rest.RestApiPojo.Entity.Book[\"author\"]->com.rest.RestApiPojo.Entity.Author[\"book\"]->org.hibernate.collection.internal.PersistentSet[0]->com.rest.RestApiPojo.Entity.Book[\"author\"]
}
You need to use #JsonManagedReference and #JsonBackReference to allow Jackson to better handle the relationship between Author and Book:
public class Author {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer author_id;
private String name;
private String language;
#JsonManagedReference
#OneToMany(cascade = CascadeType.ALL, mappedBy = "author")
private Set<Book> book;
// getter setter
}
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String title;
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "author_id")
private Author author;
// getter setter
}
You have other options (e.g. using #JsonIdentityInfo) to handle this infinite recursion issue, but this is the most common solution. You can check all other possible approaches at the following online resource https://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion.
Additionally, in your Service you are creating a brand new Book and setting its title with book.setTitle(book.getTitle());, which basically does nothing. In fact you don't even need to do most of the stuff you are doing there because Book instances are already in Author, you just need to set Author on each Book instance as follows:
#Override
public Author insertAuthor(Author author) {
for (Book book : author.getBook()) {
book.setAuthor(author);
}
return authorRepo.save(author);
}
Finally, consider changing the book property in Author to books since it contains multiple books (you will need to adjust your code afterwards).

In springboot, I can't get the JSON for manytomany with attribute on my REST Controller

I have a SpringBoot REST API where one or multiple User are part of one or multiple Team (Equipe), but they have a Role for each Team.
When I request my UserRepository.findAll(), I would like to get all my users and their teams and roles.
But when I request my TeamRepository.findAll(), I would like to get all my teams and their users and their roles.
But I don't know how to get a bidirectionnal request. I tried with #jsonmanagedreference and #jsonbackreference but it only allows me to get the teams from the user, and not the opposite.
Currently I have
[
{
"id": 1,
"prenom": "TestPrenom1",
"nom": "TestNom1",
"roleUserEquipe": []
}
{
"id": 2,
"prenom": "TestPrenom2",
"nom": "TestNom2",
"roleUserEquipe": []
}
]
But what I want is
[
{
"id": 1,
"prenom": "TestPrenom1",
"nom": "TestNom1",
"roleUserEquipe": [
{
"role": "Role1",
"team": {
"id": 1,
"nom": "Team1"
}
}
]
}
{
"id": 2,
"prenom": "TestPrenom2",
"nom": "TestNom2",
"roleUserEquipe": [
{
"role": "Role2",
"team": {
"nom": "Team1"
}
}
]
}
]
And when I request my teams I have
[
{
"id": 1,
"nom": "Team1",
"roleUserEquipe": [
{}
]
},
{
"id": 2,
"nom": "Team2",
"roleUserEquipe": [
{}
]
}
]
But I would like
[
{
"id": 1,
"nom": "Team1",
"roleUserEquipe": [
{
"role": "Role1",
"user": {
"id": 1,
"prenom": "TestPrenom1",
"nom": "TestNom1",
}
},
{
"role": "Role2",
"user": {
"id": 2,
"prenom": "TestPrenom2",
"nom": "TestNom2",
}
}
]
},
{
"id": 2,
"nom": "Team2",
"roleUserEquipe": []
}
]
My User.java
#Entity
#JsonIdentityInfo(generator= ObjectIdGenerators.PropertyGenerator.class, property="id")
public class User implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String prenom;
private String nom;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<RoleUserEquipe> roleUserEquipe = new HashSet<>();
//GETTERS AND SETTERS AND CONSTRUCTORS
}
My Equipe.java (Team)
#Entity
#JsonIdentityInfo(generator= ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Equipe implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String nom;
#OneToMany(mappedBy = "equipe", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<RoleUserEquipe> roleUserEquipe = new HashSet<>();
//GETTERS AND SETTERS AND CONSTRUCTORS
}
My RoleUserEquipe.java
#Entity
public class RoleUserEquipe implements Serializable {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "equipe_id")
private Equipe equipe;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
private String role;
//GETTERS AND SETTERS AND CONSTRUCTORS
}
EDIT :
When debugging, the subobjects are OK: they contain the values that I need.
Only when returning the JSON Response, they lose their values
I am not sure if this would work, but try removing the new keywords in:
#OneToMany(mappedBy = "equipe", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<RoleUserEquipe> roleUserEquipe = new HashSet<>();
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<RoleUserEquipe> roleUserEquipe = new HashSet<>();
to
#OneToMany(mappedBy = "equipe", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<RoleUserEquipe> roleUserEquipe;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<RoleUserEquipe> roleUserEquipe;
You didn't show us, how you convert your domain objects into resource/DTO objects.
Are you using the Spring ConversionService framework?
If so, show us the converter you wrote. Maybe your converter doesn't take the roles into account?

Return array of object(s) in join

I have this Product entity :
#Entity
public class Product implements Serializable{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id")
#JsonBackReference
private ProductCategory productCategory;
}
And Category Entity :
#Entity
public class ProductCategory implements Serializable{
#OneToMany(fetch = FetchType.LAZY, mappedBy="productCategory" ,cascade = CascadeType.ALL)
#JsonManagedReference
#OrderBy("id desc")
private Set<Product> products = new HashSet<Product>();
}
Rest API :
#Query("select p,productCategory.id,productCategory.name from Product p inner join p.productCategory productCategory")
public Page<Product> getProductsWithCategory(Pageable pageable);
This returns the following JSON :
"content": [
[
{
"id": 2,
"sku": "REF_25471",
"name": "Serrure Washington"
},
1,
"Hello 1"
],
I want to add the category object in the JSON like following :
"content": [
{
"id": 1,
"name": "Hello 1",
"products": [
{
"id": 4,
"sku": "REF_25472",
"name": "Serrure Washington",
"productCategory": [
{
"id": 1,
"name": "Hello 1"
}
]
}
]
}
]

Fasterxml - How to exclude an object from json file?

I have mapped entities which I send in JSON format to the service. Here is my entities
#Entity
#Table(name = "company")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Company implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column
private String name;
#OneToMany(mappedBy = "company")
#Cascade(value = CascadeType.ALL)
private Collection<Employee> employees;
My Employee class
#Entity
#Table(name = "employee")
#JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Integer id;
#Column
String name;
#ManyToOne()
#Cascade(value = org.hibernate.annotations.CascadeType.ALL)
#JoinColumn(name = "company_id", referencedColumnName = "id")
private Company company;
But I'm getting not appropriate json format.
{
"id": 1,
"name": "Tim",
"company": {
"id": 1,
"name": "Microsoft",
"employees": [1, {
"id": 5,
"name": "Jack",
"company": 1
}, {
"id": 6,
"name": "Jack",
"company": 1
}, {
"id": 7,
"name": "Jack",
"company": 1
}, {
"id": 8,
"name": "Tommy",
"company": 1
}]
}
}
But like I said I don't need "employees" object in "company". How to exclude it in my JSON file?
You can use the Jackson's bi-directional references to exclude "employees" object from "company". Here is an example based on your code that demonstrate the approach:
public class JacksonReferences {
#JsonIdentityInfo(generator = ObjectIdGenerators.None.class, property = "id")
static public class Company {
public Integer id;
public String name;
#JsonManagedReference
public Collection<Employee> employees;
public Company(Integer id, String name, Collection<Employee> employees) {
this.id = id;
this.name = name;
this.employees = employees;
}
}
#JsonIdentityInfo(generator = ObjectIdGenerators.None.class, property = "id")
static public class Employee {
public Integer id;
public String name;
#JsonBackReference
public Company company;
public Employee(Integer id, String name, Company company) {
this.id = id;
this.name = name;
this.company = company;
}
}
public static void main(String[] args) throws IOException {
Company company1 = new Company(1, "Microsoft", new ArrayList<Employee>());
Company company2 = new Company(2, "Google", new ArrayList<Employee>());
Employee employee1 = new Employee(1, "John", company1);
company1.employees.add(employee1);
Employee employee2 = new Employee(2, "Tim", company1);
company1.employees.add(employee2);
Employee employee3 = new Employee(3, "Bob", company2);
company2.employees.add(employee3);
ObjectMapper mapper = new ObjectMapper();
System.out.println("JSON for company #1:");
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(company1));
System.out.println("JSON for employee #1:");
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(employee1));
System.out.println("JSON for company #2:");
System.out.println(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(company2));
}
}
The output is:
JSON for company #1:
{
"id" : 1,
"name" : "Microsoft",
"employees" : [ {
"id" : 1,
"name" : "John"
}, {
"id" : 2,
"name" : "Tim"
} ]
}
JSON for employee #1:
{
"id" : 1,
"name" : "John"
}
JSON for company #2:
{
"id" : 2,
"name" : "Google",
"employees" : [ {
"id" : 3,
"name" : "Bob"
} ]
}

Categories

Resources