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).
Related
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
I have 4 tables
Area: it is a catalog where there are all the departments of an organization (parent)
SubAreas: It is a catalog where there are all the subAreas that belong to before catalog (Area), that is (child)
personal: Catalog where all the employees of the organization exist
personal Area: It is a table where there are all the relationships between the tables previously described
The business logic is simple, an employee can belong to one of several areas and only one subarea for each area to which they belong.
Here my relationships
These are my entities
Personal
#Entity
#Table(name = "personal", catalog = "almacen", schema = "")
public class Personal {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id")
private Long id;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 30)
#Column(name = "curp")
private String curp;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 200)
#Column(name = "nombre")
private String nombre;
#JsonIgnore
#OneToMany(mappedBy = "personal")
private Set<PersonalArea> registrations = new HashSet<>();
}
PersonalArea
#Getter
#Setter
#Entity
#Table(name = "personal_area", catalog = "almacen", schema = "")
#XmlRootElement
public class PersonalArea {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NotNull
private Long id;
#ManyToOne
#JoinColumn(name = "personal_id")
private Personal personal;
#ManyToOne
#JoinColumn(name = "area_id")
private Area area;
#ManyToOne
#JoinColumn(name = "subarea_id")
private subArea subArea;
private Date fechaCreacion;
}
Area
#Getter
#Setter
#Entity
#Table(name = "area", catalog = "almacen", schema = "")
#XmlRootElement
public class Area {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NotNull
private Long id;
private String nombreArea;
#JsonIgnore
#OneToMany(mappedBy = "area")
private Set<PersonalArea> registrations = new HashSet<>();
}
SubArea
#Getter
#Setter
#Entity
#Table(name = "sub_area", catalog = "almacen", schema = "")
public class subArea {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NotNull
private Long id;
private Long idArea;
private String nombre;
#JsonIgnore
#OneToMany(mappedBy = "personal")
private Set<PersonalArea> registrations = new HashSet<>();
}
Actually I get something like this
As you can see, the json has two times the same personal who belongs to two areas and one subarea for each area... It's ok, but I don't want this json
{
"items": [{
"id": 1,
"personal": {
"id": 1,
"curp": "AEMM680119HHGRRR03",
"nombre": "MARIO RUBIZEL",
"primerApellido": "ARTEAGA",
"segundoApellido": "MORALES",
"telefono": "7711263949",
"fechaCreacion": null,
"fechaModificacion": null
},
"area": {
"id": 2,
"nombreArea": "PERSONAL ADMINISTRATIVO"
},
"subArea": {
"id": 1,
"idArea": 2,
"nombre": "CONTROL ESCOLAR"
},
"fechaCreacion": "2021-06-24T16:26:49.000+00:00"
},
{
"id": 2,
"personal": {
"id": 1,
"curp": "AEMM680119HHGRRR03",
"nombre": "MARIO RUBIZEL",
"primerApellido": "ARTEAGA",
"segundoApellido": "MORALES",
"telefono": "7711263949",
"fechaCreacion": null,
"fechaModificacion": null
},
"area": {
"id": 5,
"nombreArea": "PERSONAL EXTERNO"
},
"subArea": {
"id": 2,
"idArea": 5,
"nombre": "FOTOCOPIADO"
},
"fechaCreacion": "2021-06-24T16:26:49.000+00:00"
}
]
}
I need my json something like this: Show just once personal and areas y subareas as child
{
"items": [{
"id": 1,
"personal": {
"id": 1,
"curp": "AEMM680119HHGRRR03",
"nombre": "MARIO RUBIZEL",
"primerApellido": "ARTEAGA",
"segundoApellido": "MORALES",
"telefono": "7711263949",
"fechaCreacion": null,
"fechaModificacion": null
},
"area": [{
"id": 1,
"nombreArea": "DIRECTOR",
"subArea": {
"id": 1,
"idArea": 2,
"nombre": "CONTROL ESCOLAR"
}
},
{
"id": 5,
"nombreArea": "PERSONAL EXTERNO",
"subArea": {
"id": 2,
"idArea": 5,
"nombre": "FOTOCOPIADO"
}
}
],
"fechaCreacion": "2021-06-24T16:26:49.000+00:00"
}
]
}
thanks in advance
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?
None of the similar questions on this site have been able to resolve my problem.
Error: org.hibernate.PersistentObjectException: detached entity passed to persist: healthcheckapi.model.Checks
I am trying to use Hibernate (JPA) to persist two objects to a MYSQL database. "Health" has a oneToMany relationship with "Checks".
Code:
Health Class:
#Entity
#Table(name="health")
public class Health {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String server;
private String description;
private String timestamp;
private String offset;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "health")
private List<Checks> checks;
public Health() {
}
public Health(int id, String server, String description, String timestamp, String offset, List<Checks> checks) {
this.server = server;
this.description = description;
this.timestamp = timestamp;
this.offset = offset;
this.checks = checks;
}
Checks Class:
#Entity
#Table(name="checks")
public class Checks {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private int status;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "healthid", referencedColumnName="id")
private Health health;
public Checks() {
}
public Checks(String name, int status) {
this.name = name;
this.status = status;
}
Sample JSON Health Object:
{
"server": "Server18",
"description": "Fine",
"timestamp": "00:02:30",
"offset": "00:01:00",
"checks": [
{ "id": "1",
"name": "cpu",
"status": 90
},
{
"id": "2",
"name": "memory",
"status": 88
},
{
"id": "3",
"name": "apacheService",
"status": 76
}
]
}
Note that the id's for both objects are auto-generated which I think is part of the problem.
Use the CascadeType.MERGE instead of CascadeType.ALL.
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.