Issue
I am using @manytomany relationship in spring boot between Account and Customer class. Account class
@Entity
@Table(name = "Account")
public class Account {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public int accountNumber;
public String accountType;
public int balance;
@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JsonManagedReference
@JsonIgnore
private List<Customer> customers;
}
//skipped constructors, getters and setters and tostring method
Customer class
@Entity
@Table(name = "Customer")
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public int customerId;
public String firstName;
public String lastName;
public String email;
@ManyToMany(mappedBy = "customers", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@JsonBackReference
private List<Account> accounts;
}
//skipped constructors, getters and setters and tostring method
I am trying to send a post request as shown below which fetches the customer present in Customer table with the given id, and created new account for it as given in the json body in post request.
Below is the handler method which handles the post request as shown in the screenshot attached above.
@PostMapping("/customers/{id}/accounts")
public ResponseEntity<String> createAccountForCustomer(@RequestBody Account account, @PathVariable("id") int id) {
account.setAccountNumber(0);
System.out.println(account);
// fetching customer object based on given customer id
Customer fetchCustomerByID = this.customerServices.fetchAccountByID(id);
if (fetchCustomerByID == null) {
System.out.println("Customer doesn't exist");
return new ResponseEntity<String>("Customer doesn't exit", HttpStatus.NOT_FOUND);
} else {
System.out.println(fetchCustomerByID);
System.out.println("Customer exists. Creating account!!");
List<Account> listOfAccounts = fetchCustomerByID.getAccounts();
listOfAccounts.add(account);
fetchCustomerByID.setAccounts(listOfAccounts);// adding given account to fetched customer
System.out.println("newly created customer" + fetchCustomerByID);
Customer ac = this.customerServices.addAccount(fetchCustomerByID);
// saving fetched customer back to database with updated customer details
// adding fetched customer to newly created account from given json
List<Customer> listOfCustomers = new ArrayList<Customer>();
listOfCustomers.add(fetchCustomerByID);
account.setCustomers(listOfCustomers);// creating customer list for given account
System.out.println("newly created account" + account);
Account addAccount = this.accountServices.addAccount(account);// saving new account to database
return new ResponseEntity<String>("account created for customer", HttpStatus.OK);
}
}
CustomerServices addAccount method-
// add an account
public Customer addAccount(Customer account) {
Customer acc = this.customerRepository.save(account);
return acc;
}
AccountServices addAccount method-
// add an account
public Account addAccount(Account account) {
Account acc = this.accountRepository.save(account);
return acc;
}
AccountRepository and CustomerRepository are interfaces which extends CrudRepository interface to do database related operations.
Question - When I run the shown post request, everything is working fine. Details are also added in database as shown below -
But when I try to fetch account (using get request with id) which is added by the above post request, it is showing error.(For account details which were not added by the post request above are getting fetched without any error) Error is -
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at ma20099449.foundation.bank.ma20099449_bank.Entities.Account.toString(Account.java:69) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at ma20099449.foundation.bank.ma20099449_bank.Entities.Customer.toString(Customer.java:80) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at ma20099449.foundation.bank.ma20099449_bank.Entities.Account.toString(Account.java:69) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at ma20099449.foundation.bank.ma20099449_bank.Entities.Customer.toString(Customer.java:80) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at ma20099449.foundation.bank.ma20099449_bank.Entities.Account.toString(Account.java:69) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at ma20099449.foundation.bank.ma20099449_bank.Entities.Customer.toString(Customer.java:80) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at ma20099449.foundation.bank.ma20099449_bank.Entities.Account.toString(Account.java:69) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at ma20099449.foundation.bank.ma20099449_bank.Entities.Customer.toString(Customer.java:80) ~[classes/:na]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
at java.base/java.lang.StringBuilder.append(StringBuilder.java:167) ~[na:na]
at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457) ~[na:na]
at org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622) ~[hibernate-core-5.4.32.Final.jar:5.4.32.Final]
at java.base/java.lang.String.valueOf(String.java:3367) ~[na:na]
Adding stacktrace error shown in HTTP response -
java.lang.StackOverflowError\r\n\tat java.base/java.lang.AbstractStringBuilder.<init>(AbstractStringBuilder.java:112)\r\n\tat java.base/java.lang.StringBuilder.<init>(StringBuilder.java:125)\r\n\tat ma20099449.foundation.bank.ma20099449_bank.Entities.Account.toString(Account.java:68)\r\n\tat java.base/java.lang.String.valueOf(String.java:3367)\r\n\tat java.base/java.lang.StringBuilder.append(StringBuilder.java:167)\r\n\tat java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457)\r\n\tat org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622)\r\n\tat java.base/java.lang.String.valueOf(String.java:3367)\r\n\tat java.base/java.lang.StringBuilder.append(StringBuilder.java:167)\r\n\tat ma20099449.foundation.bank.ma20099449_bank.Entities.Customer.toString(Customer.java:80)\r\n\tat java.base/java.lang.String.valueOf(String.java:3367)\r\n\tat java.base/java.lang.StringBuilder.append(StringBuilder.java:167)\r\n\tat java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457)\r\n\tat org.hibernate.collection.internal.PersistentBag.toString(PersistentBag.java:622)\r\n\tat java.base/java.lang.String.valueOf(String.java:3367)\r\n\tat java.base/java.lang.StringBuilder.append(StringBuilder.java:167)\r\n\tat ma20099449.foundation.bank.ma20099449_bank.Entities.Account.toStrin
Adding get method to fetch account details for id
// get method to fetch single record based on id given in request
@GetMapping("/accounts/{id}")
public ResponseEntity<Account> getAccountById(@PathVariable("id") int id) {
Account fetchAccountByID = this.accountServices.fetchAccountByID(id);
if (fetchAccountByID == null) {
return new ResponseEntity<>(fetchAccountByID, HttpStatus.NOT_FOUND);
} else {
// System.out.println(fetchAccountByID);
System.out.println(fetchAccountByID != null ? fetchAccountByID : "null");
return new ResponseEntity<>(fetchAccountByID, HttpStatus.OK);
}
}
I am adding toString method of Account Class
@Override
public String toString() {
return "{" + " accountNumber='" + getAccountNumber() + "'" + ", accountType='" + getAccountType() + "'"
+ ", balance='" + getBalance() + "'" + ", customers='" + getCustomers() + "'" + "}";
}
Solution
I think it is not the root cause of the problem, but your Account.toString()
method causes in infitite loop: Account.toString()
invoke Customer.toString()
and Customer.toString()
invoke Account.toString()
for the same account.
You need to stop that loop! I would recommend not to "print" the full customer when Account.toString
is invoked. Instead I would "print" only some identifying details.
public class Customer {
public String toStringShort() {
return "{id=" + id
+ ", firstName='"+ firstName
+ "', lastName='"+ lastName + "'}"
}
}
public class Account {
...
public String toString() {
final String shortCustomersToString;
if(this.customers != null)
shortCustomersToString = this.customers.stream()
.map(Customer::toStringShort)
.collect(Collectors.joining(", ", "[", "]"));
else
shortCustomersToString = null;
return "{" + " accountNumber='" + getAccountNumber() + "'"
+ ", accountType='" + getAccountType() + "'"
+ ", balance='" + getBalance() + "'"
+ ", customers=" + shortCustomersToString + "}";
}
}
Answered By - Ralph