I'm testing my app via Swagger and when trying to post an object, which contains a list of other objects, I get an error java.lang.StackOverflowError. Basically, I'm trying to write an invoice, represented as JSON in Swagger form, and while filling in the data for my invoice I've noticed that invoice item contains the duplicated fields for invoice.
{
"date": "2017-08-27",
"counterparty": {
"address": {
"houseNumber": "string",
"streetName": "string",
"townName": "string",
"zipCode": "string"
},
"bankName": "string",
"bankNumber": "string",
"companyName": "string",
"nip": "string",
"phoneNumber": "string"
},
"invoiceItems": [
{
"amount": 0,
"description": "string",
"id": 0,
"invoice": { //SHOULD NOT BE HERE
"id": 0,
"date": "2017-09-07",
"counterparty": {
"address": {
"houseNumber": "string",
"streetName": "string",
"townName": "string",
"zipCode": "string"
},
"bankName": "string",
"bankNumber": "string",
"companyName": "string",
"nip": "string",
"phoneNumber": "string"
},
"invoiceItems": [
{}
]
},
"numberOfItems": 0,
"vat": "VAT_23",
"vatAmount": 0
}
]
}
#Entity
#Table(name = "invoices")
public class Invoice implements Comparable<Invoice> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "invoice_id")
private int id;
#Column(name = "date")
private LocalDate date = LocalDate.now();
#OneToOne(mappedBy = "invoice", cascade = {CascadeType.DETACH, CascadeType.MERGE,
CascadeType.PERSIST, CascadeType.REFRESH})
private Counterparty counterparty;
#OneToMany(mappedBy = "invoice", cascade = {CascadeType.PERSIST, CascadeType.MERGE,
CascadeType.DETACH, CascadeType.REFRESH})
private List<InvoiceItem> invoiceItems = new ArrayList<>();
#Entity
#Table(name = "items")
public class InvoiceItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id")
private int id;
private String description;
private int numberOfItems;
private BigDecimal amount;
private BigDecimal vatAmount;
#JoinColumn(name = "vat_code")
#Enumerated(EnumType.ORDINAL)
private Vat vat;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH})
#JoinColumn(name = "invoice_id")
private Invoice invoice;
I have the very same mapping with Counterparty, but it doesn't show me invoice data in JSON:
#Id
private String nip;
private String companyName;
private String phoneNumber;
private String bankName;
private String bankNumber;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "counterparty", cascade = CascadeType.ALL)
private Address address;
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH})
#JoinColumn(name = "invoice_id")
private Invoice invoice;
Maybe there are some issues with my annotations that invoice is being called recursively?
You should mark your Invoice property on InvoiceItems with the #JsonIgnore annotation which means it won't be serialized into JSON when the InvoiceItems is serialized which should prevent your infinitely recursive JSON issue:
#Entity
#Table(name = "items")
public class InvoiceItem {
...
#ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
CascadeType.REFRESH})
#JoinColumn(name = "invoice_id")
#JsonIgnore
private Invoice invoice;
Related
I want the Employee object to only display the id property when adding a CV. When I put #JsonIgnore on the employee object in the CV, the id doesn't come either. How can I hide property that I do not want to appear in employee class (password, national identity) on the CV class?
Cv Class :
#Entity
#Table(name = "cvs")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class CV {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#OneToOne
#JoinColumn(name = "employee_id")
private Employee employee;
#OneToMany(mappedBy = "cv", cascade = CascadeType.ALL, orphanRemoval = true)
List<Education> educations;
#OneToMany(mappedBy = "cv", cascade = CascadeType.ALL, orphanRemoval = true)
List<Work> works;
#OneToMany(mappedBy = "cv", cascade = CascadeType.ALL, orphanRemoval = true)
List<Languege> langueges;
#OneToMany(mappedBy = "cv", cascade = CascadeType.ALL, orphanRemoval = true)
List<Technology> technologies;
#Column(name = "github")
private String github;
#Column(name = "linkedin")
private String linkedin;
#NotNull
#NotBlank
#Column(name = "cover_letter")
private String coverLetter;
#Column(name = "photo")
private String photo;
}
Employee Class :
#Data
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "employees")
#Entity
#PrimaryKeyJoinColumn(name = "user_id", referencedColumnName = "id")
public class Employee extends User {
#NotNull
#NotBlank
#Column(name = "first_name", length = 50)
private String firstName;
#NotNull
#NotBlank
#Column(name = "last_name", length = 50)
private String lastName;
#NotNull
#NotBlank
#Column(name = "national_identity", length = 11)
private String nationalIdentity;
#NotNull
#Column(name = "year_of_birth")
private int yearOfBirth;
#JsonIgnore
#OneToOne(mappedBy = "employee")
private CV cv;
}
I read data this format;
{
"id": 0,
"employee": {
"id": 0, //visible
"email": "string", //not visible
"password": "string", //not visible
"firstName": "string",
"lastName": "string",
"nationalIdentity": "string", //not visible
"yearOfBirth": 0 //not visible
},
"educations": [
{
"id": 0,
"schoolName": "string",
"department": "string",
"startingDate": "2022-02-11",
"graduationDate": "2022-02-11"
}
],
"works": [
{
"id": 0,
"workplace": "string",
"job": {
"id": 0,
"jobName": "string"
},
"startingDate": "2022-02-11",
"endDate": "2022-02-11"
}
],
"langueges": [
{
"id": 0,
"languege": "string",
"level": 0
}
],
"technologies": [
{
"id": 0,
"technology": "string"
}
],
"github": "string",
"linkedin": "string",
"coverLetter": "string",
"photo": "string"
}
You can use a custom serializer that you would use on your CV employee field : #JsonSerialize(using = CustomEmployeeSerializer.class)
public class CustomEmployeeSerializer extends SerializerBase<Employee> {
public CustomEmployeeSerializer() {
super(Employee.class, true);
}
#Override
public void serialize(Employee employee, JsonGenerator jgen, SerializerProvider provider)
throws IOException, JsonProcessingException {
jgen.writeString(employee.getId());
}
I have created a many to many relationship. It is returning the data correctly. However, I noticed that if the data exist in canUse section of the json and has been called before by another Player, it does not print the data again only the new values are printed.
JOSN
[
{
"id": 12,
"firstname": "asdfghjkl ",
"surname": "asda",
"canUse": [
{
"id": 0,
"techniqueName": "JAVA"
},
{
"id": 1,
"techniqueName": "PHP"
},
{
"id": 2,
"techniqueName": "PYTHON"
}
]
},
{
"id": 49,
"firstname": "asc",
"surname": "as",
"canUse": []
},
{
"id": 90,
"firstname": "LOL",
"surname": "LOL",
"canUse": []
}*,
{
"id": 91,
"firstname": "Kareem",
"surname": "LOL",
"canUse": [
1,
2,
{
"id": 3,
"techniqueName": "HASKELL"
}
]
},*
{
"id": 92,
"firstname": "Omar",
"surname": "LOL",
"canUse": []
}
]
Player entity
#Entity
#Table(name = "players")
#EntityListeners(AuditingEntityListener.class)
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Players {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="player_id")
private int id;
private String firstname;
private String surname;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(
name = "able_to_use",
joinColumns = {#JoinColumn(name = "player_id", referencedColumnName = "player_id")},
inverseJoinColumns = {#JoinColumn(name = "technique_id", referencedColumnName = "id")}
)
private List<Technique> canUse;
//Getters and Setters
Technique entity
#Entity
#Table(name = "technique")
#EntityListeners(AuditingEntityListener.class)
#JsonIdentityInfo(
generator = ObjectIdGenerators.PropertyGenerator.class,
property = "id")
public class Technique {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "technique_name")
private String techniqueName;
#JsonIgnore
#ManyToMany(mappedBy = "canUse", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Players> ableToUse;
//Getters and Setters
Thank you in advance, the problem is occurring for player with ID 91. this player should also print PHP and PYTHON but only the ID is printed.
I have two entities. Customer which is mapped in one to many relation with the CustomerDepartment. CustomerDepartment table has a column to store customer Id.
I want to map them in such a way that Customer Object store a list of Customer Department, and the Customer Department stores the id of the customer it belongs to.
The code that is working compels me to send the all the customer details while creating or updating a customer Department.
Is there a way I can only send the id of the customer and it maps itself?
I have tried changing from -
#JsonBackReference
#ManyToOne
#JoinColumn(name = "customer_no", nullable = false)
private Customer customer;
to this -
#JsonBackReference
#ManyToOne(targetEntity = Customer.class)
#JoinColumn(name = "customer_no", nullable = false)
private Integer customer;
which gives me the requestbody I want but it does not work giving the following error -
2019-08-03 04:59:08 ERROR CustomerController:72 - org.springframework.orm.jpa.JpaSystemException: Error accessing field [private java.lang.Integer com.enquero.pulse.entity.Customer.customerNo] by reflection for persistent property [com.enquero.pulse.entity.Customer#customerNo] : 1; nested exception is org.hibernate.property.access.spi.PropertyAccessException: Error accessing field [private java.lang.Integer com.enquero.pulse.entity.Customer.customerNo] by reflection for persistent property [com.enquero.pulse.entity.Customer#customerNo] : 1
Working Code:
Customer:-
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#DynamicUpdate
#Entity
#Table(name = "customer")
public class Customer extends Auditable<Integer>{
#Id
#Column(name = "customer_no")
private Integer customerNo;
#NotBlank
#Column(name = "customer_name")
private String customerName;
#Column(name = "industry")
private String industry;
#Column(name = "country")
private String country;
#Column(name = "state")
private String state;
#Column(name = "city")
private String city;
#Column(name = "postal_code")
private String postalCode;
#Column(name = "address_line1")
private String addressLine1;
#Column(name = "address_line2")
private String addressLine2;
#Column(name = "address_line3")
private String addressLine3;
#Column(name = "payment_term")
private String paymentTerm;
#Column(name = "customer_segment")
private String customerSegment;
#JsonFormat(pattern="dd-MMM-yyyy")
#Column(name = "engagement_start_on")
private Date engagementStartOn;
#JsonManagedReference
#OneToMany(fetch = FetchType.LAZY, mappedBy = "customer")
private List<CustomerDepartment> customerDepartments;
}
CustomerDepartment:-
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#DynamicUpdate
#Entity
#Table(name = "customer_department")
public class CustomerDepartment extends Auditable<Integer>{
#Id
#Column(name = "dept_id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer deptId;
#Column(name = "dept_name")
private String deptName;
#Column(name = "primary_contact")
private String primaryContact;
#JsonBackReference
#ManyToOne
#JoinColumn(name = "customer_no", nullable = false)
private Customer customer;
}
Current RequestBody:-
{
"createdBy": 0,
"creationDate": "2019-08-02T23:05:33.993Z",
"customer": {
"addressLine1": "string",
"addressLine2": "string",
"addressLine3": "string",
"city": "string",
"country": "string",
"createdBy": 0,
"creationDate": "2019-08-02T23:05:33.993Z",
"customerDepartments": [
null
],
"customerName": "string",
"customerNo": 0,
"customerSegment": "string",
"engagementStartOn": "string",
"industry": "string",
"lastUpdateDate": "2019-08-02T23:05:33.993Z",
"lastUpdatedBy": 0,
"paymentTerm": "string",
"postalCode": "string",
"state": "string"
},
"deptId": 0,
"deptName": "string",
"lastUpdateDate": "2019-08-02T23:05:33.994Z",
"lastUpdatedBy": 0,
"primaryContact": "string"
}
expected requestbody:-
{
"createdBy": 0,
"creationDate": "2019-08-02T23:05:33.993Z",
"customer": 1, //id instead of json
"deptId": 0,
"deptName": "string",
"lastUpdateDate": "2019-08-02T23:05:33.994Z",
"lastUpdatedBy": 0,
"primaryContact": "string"
}
Have you considered a unidirectional #OneToMany: https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#associations?
For example on CustomerDeparment change
#JsonBackReference
#ManyToOne
#JoinColumn(name = "customer_no", nullable = false)
private Customer customer;
}
to
#JsonBackReference
#ManyToOne
#Column(name = "customer_no")
private int customer;
...and on Customer change
#JsonManagedReference
#OneToMany(fetch = FetchType.LAZY, mappedBy = "customer")
private List<CustomerDepartment> customerDepartments;
}
to
#JsonManagedReference
#OneToMany(cascade = CascadeType.ALL)
private List<CustomerDepartment> customerDepartments;
}
As a bit of an aside, I honestly find Hibernate relationships to sometimes be more a hindrance than a help. As an alternative, you may wish to consider dropping the explicit relationship properties, using "regular" columns (#Column(name="customer_no") private int customer') and just writing queries in your repo classes (ex. findByCustomerNo(int customNumber)) to meet your requirements.
I have 2 tables email_address and group_email
I want to get a list of email addresses and if it is group also list the emails of group. Here is my hibernate dom object:
#Entity
#Table(name = "email_address")
public class EmailAddressDom {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "email_address_id_seq")
#SequenceGenerator(name = "email_address_id_seq", sequenceName = "email_address_id_seq")
private int id;
#Column(name = "name")
private String name;
#Column(name = "email")
private String email;
#Column(name = "is_group")
private boolean isGroup;
#Column(name = "status")
private boolean status;
// Getters and setters
And here is the implementation how I am getting the list
public List<EmailAddressDom> getEmailAddresses() {
Session session = getCurrentSession();
Criteria criteria = session.createCriteria(EmailAddressDom.class);
List<EmailAddressDom> emailAddresses = criteria.list();
return emailAddresses;
}
And the result is
[
{
"id": 451,
"name": "HS",
"isGroup": true,
"status": true
},
{
"id": 452,
"name": "test4",
"email": "test4#mail.com",
"isGroup": false,
"status": true
},
{
"id": 450,
"name": "test3",
"email": "test3#mail.com",
"isGroup": false,
"status": true
},
{
"id": 402,
"name": "test2",
"email": "test2#mail.com",
"isGroup": false,
"status": true
},
{
"id": 401,
"name": "test1",
"email": "test1#mail.com",
"isGroup": false,
"status": true
}
]
I want to get result like this
[
{
"id":451,
"name":"HS",
"isGroup":true,
"status":true,
"groupEmails":[
{
"id":450,
"name":"test3",
"email":"test3#mail.com",
"isGroup":false,
"status":true
},
{
"id":452,
"name":"test4",
"email":"test4#mail.com",
"isGroup":false,
"status":true
}
]
},
{
"id":402,
"name":"test2",
"email":"test2#mail.com",
"isGroup":false,
"status":true
}
]
Which mapping should I use in EmailAddressDom entity?
Updated accordingly to comments
I have written a simple application mapping following way and it is working fine
#ManyToMany(fetch= FetchType.EAGER, cascade={CascadeType.ALL})
#JoinTable(name="group_email",
joinColumns={#JoinColumn(name="group_id")},
inverseJoinColumns={#JoinColumn(name="email_addr_id")})
private List<EmailAddressDom> groupEmails;
But I have confused setting it in my web application, it throws following exception:
Association has already been visited: AssociationKey(table=group_email, columns={email_addr_id})
What can be the reason?
Either Do Eager loading
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "group_email",
joinColumns = #JoinColumn(name = "email_addr_id", referencedColumnName = "id"),
inverseJoinColumns=#JoinColumn(name="group_id", referencedColumnName="id" ))
private List<EmailAddressDom> emailAddressDoms;
OR
//explicitly join in criteria
public List<EmailAddressDom> getEmailAddresses() {
Session session = getCurrentSession();
Criteria criteria = session.createCriteria(EmailAddressDom.class);
criteria.setFetchMode("emailAddressDoms", fetchMode.JOIN);
List<EmailAddressDom> emailAddresses = criteria.list();
return emailAddresses;
}
To achieve self join, in your EmailAddressDom class add reference to itself like:
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(name = "group_email",
joinColumns = #JoinColumn(name = "email_addr_id", referencedColumnName = "id"),
inverseJoinColumns=#JoinColumn(name="group_id", referencedColumnName="id" ))
private List<EmailAddressDom> emailAddressDoms;
In group_email table you do not require id column as email_addr_id and group_id combination would be unique or do you allow same email more than once in a group?
Update after comment from OP:
To group by email groups, change the mapping like shown below.
#ManyToOne(fetch = FetchType.LAZY)
#JoinTable(name = "group_email",
joinColumns = #JoinColumn(name="group_id", referencedColumnName="id" ),
inverseJoinColumns=#JoinColumn(name = "email_addr_id", referencedColumnName = "id"))
private List<EmailAddressDom> emailAddressDoms;
I am having two models which is having many to many relations. I have used #JsonManagedReference and #JsonBackReference to avoid looping.
When I retrieve JSON of Outage class it working fine it has set of instances value, but when I retrieve Instance it is not having outage set there.
Here is my Outage.java
public class Outage implements Serializable {
/**
*
*/
private static final long serialVersionUID = 3688202816610111558L;
#Id
#GeneratedValue
#Column(name = "outage_id")
private long outageId;
#JsonManagedReference
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "ManyToMany",
joinColumns = #JoinColumn(name = "outage_id"),
inverseJoinColumns = #JoinColumn(name = "instance_id"))
private Set<Instances> instances;
#Column(name = "start_date")
private Date startDate;
#Column(name = "end_date")
private Date endDate;
#Column(name = "start_time")
private Time startTime;
#Column(name = "end_time")
private Time endTime;
#Column(name = "outage_type")
private int outageType;
#Column(name = "jira_number")
private String jiraNumber;
#Column(name = "reason")
private String reason;
}
Here is my Instances.java
public class Instances implements Serializable {
/**
*
*/
private static final long serialVersionUID = -5280937805735107384L;
#Id
#GeneratedValue
#Column(name = "instance_id")
private long id;
#Column(name = "name")
private String instanceName;
#ManyToOne
#JoinColumn(name = "application_id")
private Applications applications;
#ManyToOne
#JoinColumn(name = "environment_id")
private Environments environments;
#JsonBackReference
#ManyToMany(cascade = CascadeType.ALL, mappedBy = "instances")
private Set<Outage> outages;
}
Here is my respective JSON
Outage JSON
{
"outageList": [{
"outageId": 40,
"instances": [{
"id": 10,
"instanceName": null,
"applications": null,
"environments": null,
"version": null
}, {
"id": 9,
"instanceName": "UAT7",
"applications": null,
"environments": null,
"version": null
}],
"startDate": "3916-06-06",
"endDate": "3916-06-06",
"startTime": "01:00:00",
"endTime": "12:00:00",
"outageType": 1,
"jiraNumber": "123",
"reason": "checking without applicaion & envi"
}]}
Instances
{
"instancesList": [{
"id": 9,
"instanceName": "UAT7",
"applications": null,
"environments": null,
"version": null
}, {
"id": 10,
"instanceName": null,
"applications": null,
"environments": null,
"version": null
}]}
Thanks in advance