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
Related
I have following relationship in my spring boot:
public class Clazz {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id")
private Integer id;
#Column(name = "lesson_id")
#NotNull(message = "{lesson.is_required}")
private Integer lessonId;
#Column(name = "level_id")
#NotNull(message = "{level.is_required}")
private Integer levelId;
#Column(name = "name")
#NotEmpty(message = "{name.is_required}")
private String name;
#OneToOne
#JoinColumn(name = "lesson_id", referencedColumnName = "id", insertable = false, updatable = false)
private Lesson lesson;
#OneToOne
#JoinColumn(name = "level_id", referencedColumnName = "id", insertable = false, updatable = false)
private Level level;
}
Now my Lesson entity has Level entity:
public class Lesson {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "name")
#NotEmpty(message = "{name.is_required}")
private String name;
#Column(name = "description")
private String description = "";
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "lesson_id")
Collection<Level> levels = new ArrayList<>();
}
And finally my Level class:
public class Level {
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
#Column(name = "id", insertable = false, updatable = false)
private Integer id;
#Column(name = "lesson_id")
private Integer lessonId;
#Column(name = "name")
private String name;
#Column(name = "step")
private Integer step;
#Column(name = "description")
private String description="";
}
With this relationship defined, when I fetch data, getting some extra info in my results:
[
{
"id": 2,
"lessonId": 1,
"levelId": 1,
"name": "English Class March",
"branch": {
"id": 1,
"name": "3 mikr branch",
"email": "",
"phone": "",
"address": "3 mikr, foo, bar"
},
"lesson": {
"id": 1,
"name": "math",
"description": "math lesson",
"levels": [
{
"id": 1,
"lessonId": 1,
"name": "First level",
"step": 1,
"description": "1st level descr"
}
]
},
"level": {
"id": 1,
"lessonId": 1,
"name": "First level",
"step": 1,
"description": "1st level descr"
}
}
]
How can I tell JPA not to fetch sub-entities, ideally I do not need levels in lesson field of result.
Use #JsonIgnore. It can be used at setter,getter or field.
Please refer the code below.
public class Lesson {
#Id #Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "name")
#NotEmpty(message = "{name.is_required}")
private String name;
#Column(name = "description")
private String description = "";
#JsonIgnore // Add this to ignore the property in json output
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "lesson_id")
Collection<Level> levels = new ArrayList<>();
}
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).
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?
I'm using Spring Boot 2.2, Spring Data REST, Spring HATEOAS.
I'm facing a strange problem. I've a RestController accepting this object:
#Data
public class DocumentJSON {
#Valid
private Document document;
private List<DocumentRow> rows = new ArrayList<>();
private List<DocumentVat> vats = new ArrayList<>();
private Set<DocumentPayment> payments = new HashSet<>();
private boolean updateContactDetail = false;
}
and DocumentPayment is:
#Data
#EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
#NoArgsConstructor
#AllArgsConstructor
#Builder
#ToString(callSuper = true)
public class DocumentPayment extends AbstractEntity {
#ToString.Exclude
#JsonDeserialize(using = DocumentUriDeserializer.class)
#NotNull
#OnDelete(action = OnDeleteAction.CASCADE)
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private Document document;
#NotNull
#Column(nullable = false, columnDefinition = "DATE")
private Instant date;
//Optional contact (the receipt has not a contact)
#ToString.Exclude
#ManyToOne(fetch = FetchType.LAZY)
private Contact contact;
#NotBlank
#Column(nullable = false)
private String description;
#ToString.Exclude
#JsonDeserialize(using = FinancialAccountUriDeserializer.class)
#NotNull(message = "{documentpayment.financialaccount.missing}")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
private FinancialAccount financialAccount;
#Enumerated(EnumType.STRING)
#NotNull
#Column(nullable = false, length = 30)
private PaymentType paymentType;
//The amount, negative for payment to suppliers
#NotNull
#ColumnDefault("0.00")
#Column(nullable = false, scale = 2, columnDefinition = "DECIMAL(12,2)")
private BigDecimal amount = BigDecimal.ZERO;
//The amount paid
#NotNull
#ColumnDefault("0.00")
#Column(nullable = false, columnDefinition = "DECIMAL(12,2)")
private BigDecimal paid = BigDecimal.ZERO;
#JsonProperty(access = JsonProperty.Access.READ_ONLY)
#Generated(value = GenerationTime.ALWAYS)
#Column(columnDefinition = "DECIMAL(12,2) AS (amount-paid) VIRTUAL NOT NULL")
private BigDecimal due;
#ToString.Exclude
#JsonDeserialize(using = StoreUriDeserializer.class)
//#NotNull(message = "{documentpayment.store.missing}")
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "store_id", updatable = false)
private Store store;
}
The client is sending a Json like this:
{
"document": {
"date": "2019-10-18T00:00:00.000Z",
"type": "SALES_RECEIPT",
"store": "http://95.255.117.252:8082/api/v1/stores/1",
"rounding": 0,
"amount": 19.23,
"taxAmount": 0.77,
"totalAmount": 20
},
"rows": [
{
"index": 1,
"productType": "FRAME",
"qty": 1,
"rowGroup": null,
"unitPrice": 9.615,
"percentageDiscount": 0,
"purchaseUnitPrice": null,
"amount": 9.615,
"description": "Prodotto1",
"taxRate": "http://95.255.117.252:8082/api/v1/taxRates/2",
"note": false
},
{
"index": 1,
"productType": "OPHTHALMIC_LENS",
"qty": 1,
"rowGroup": null,
"unitPrice": 9.615,
"percentageDiscount": 0,
"purchaseUnitPrice": null,
"amount": 9.615,
"description": "Lente",
"taxRate": "http://95.255.117.252:8082/api/v1/taxRates/2",
"note": false
}
],
"payments": [
{
"date": "2019-10-18T00:00:00.000Z",
"financialAccount": "http://95.255.117.252:8082/api/v1/financialAccounts/1",
"paymentType": "CASH",
"amount": "10"
},
{
"date": "2019-10-18T00:00:00.000Z",
"financialAccount": "http://95.255.117.252:8082/api/v1/financialAccounts/3",
"paymentType": "CREDIT_CARD",
"amount": "10"
}
],
"updateContactDetail": false
}
but when I debug in the first line of the REST controller I see only 1 payment.
Changing the property payments in DocumentJSON from Set<DocumentPayment> to List<DocumentPayment>, I get 2 payments as expected with the same JSON.
Am I missing something or Spring/Jackson are not able to deserialize in the right way a Set<>?
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"
} ]
}