Spring Boot JPA one-to-many bidirectional foreign key is null - java

I have two entities Account and Order where one account can have one or more orders. I want to be able to log into an Account and then have order(s) for that account. Whenever I post an order for an existing account everything gets filled in properly but "account_id"(the foreign key). I would like to know why "account_id" is null and what I can do to address this problem, thanks.
Here is the Account entity:
#Entity
#Table(name="account")
public class Account {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private long id;
#Column(name="first_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#Column(name="email")
private String email;
#Column(name="user_name")
private String userName;
#Column(name="password")
private String password;
#Column(name="address")
private String address;
#Column(name="city")
private String city;
#Column(name="state")
private String state;
#Column(name="country")
private String country;
#Column(name="zipcode")
private String zipcode;
#Column(name="credit_card_number")
private String creditCardNumber;
#Column(name="credit_card_code")
private int creditCardCode;
#Column(name="credit_card_name")
private String creditCardName;
#Column(name="credit_card_expiration_month")
private int creditCardExpirationMonth;
#Column(name="credit_card_expiration_year")
private int creditCardExpirationYear;
#OneToMany(mappedBy = "account", cascade = CascadeType.ALL)
private List<Order> orders;
/* constructor */
/* Getters and setters */
Here is the Order entity:
#Entity
#Table(name="orders")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private long id;
#Column(name="total_price")
private int totalPrice;
#Column(name="total_quantity")
private int totalQuantity;
#Column(name="date_created")
#CreationTimestamp
private Date dateCreated;
#Column(name="shipping_address")
private String shippingAddress;
#ManyToOne
#JoinColumn(name = "account_id",nullable = false, insertable = false, updatable = false)
private Account account;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "order")
private Set<OrderItem> orderItems = new HashSet<>();
/* constructor */
/* Getters and setters */
Here is the Order Repository
public interface OrderRepository extends JpaRepository<Order, Long> {
Page<Order> findByAccountId(#Param("id") Long id, Pageable pageable);
}
Here is the Order Controller
#RestController
#RequestMapping("/api")
public class OrderController {
#Autowired
private OrderRepository orderRepository;
#PostMapping("/save-order")
public Order saveOrder(#RequestBody Order order) {
return orderRepository.save(order);
}
}
Here is the postman post for order
URL: "http://localhost:8080/api/save-order"
{
"totalPrice": 10.17,
"totalQuantity": 3,
"dateCreated": "2022-09-05",
"shippingAddress": "123 Road, London, United Kingdom",
"account": [{
"id": 1,
"firstName": "Winston",
"lastName": "Smith",
"email": "smith22#gmail.com",
"userName": "wilsonsmith22",
"password": "mypassword22",
"address": "123 Road",
"city": "London",
"state": "London Region",
"country": "United Kingdom",
"zipcode": "220220",
"creditCardNumber": "4422336644885500",
"creditCardCode": 234,
"creditCardName": "Winston Smith",
"creditCardExpirationMonth": 5,
"creditCardExpirationYear": 2025
}]
}

You have :
#ManyToOne
#JoinColumn(name = "account_id",nullable = false, insertable = false, updatable = false)
private Account account;
#Column(name = "account_id")
private long accountId;
Both fields account & accountId have the same name, which souldn't happen I guess.

Related

Spring Boot GET method returns duplicated values with StackOverFlow error

I'm building a RESTful API GET method with Spring Boot to get return of a Bill entity as JSON from database. The return is not expected as it has many duplicated values and a StackOverFlowError.
[{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":{"id":1,"date":"2022-05-20","time":"16:48:06","total":330000.0,"billDetails":[{"billMenuItemID":{"billId":1,"menuItemId":1},"bill":
//continues for eternity
Hibernate log:
Hibernate:
select
bill0_.bill_id as bill_id1_0_,
bill0_.date as date2_0_,
bill0_.time as time3_0_,
bill0_.total as total4_0_
from
bill bill0_
Hibernate:
select
billdetail0_.bill_id as bill_id1_1_0_,
billdetail0_.menu_item_id as menu_ite2_1_0_,
billdetail0_.bill_id as bill_id1_1_1_,
billdetail0_.menu_item_id as menu_ite2_1_1_,
billdetail0_.quantity as quantity3_1_1_,
billdetail0_.subtotal as subtotal4_1_1_,
menuitem1_.menu_item_id as menu_ite1_2_2_,
menuitem1_.description as descript2_2_2_,
menuitem1_.img_url as img_url3_2_2_,
menuitem1_.name as name4_2_2_,
menuitem1_.price as price5_2_2_,
menuitem1_.status as status6_2_2_,
menuitem1_.type as type7_2_2_
from
bill_detail billdetail0_
inner join
menu_item menuitem1_
on billdetail0_.menu_item_id=menuitem1_.menu_item_id
where
billdetail0_.bill_id=?
How can I get a return of a Bill like this:
{
"billId": 1,
"date": 2022-05-20,
"time": 16:48:06,
"total": 330000,
"billDetails": [
{
"menuItem": {
"id": 1,
"name": Rice,
// other attributes of MenuItem
},
"quantity": 2
"subtotal": 90000
},
{
"menuItem": {
"id": 2
"name": Wine
// other attributes of MenuItem
},
"quantity": 4
"subtotal": 240000
}
]
}
This is my classes and related functions
Class Bill
#Entity(name = "bill")
#Table(name = "bill")
public class Bill {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(name = "bill_id")
private Long id;
private LocalDate date;
private LocalTime time;
private Double total;
#OneToMany(mappedBy = "bill", cascade = CascadeType.ALL)
private List<BillDetail> billDetails = new ArrayList<>();
Class MenuItem
#Entity
#Table(name = "menuItem",
uniqueConstraints = {
#UniqueConstraint(name = "menu_item_name_unique", columnNames = "name")
}
)
public class MenuItem {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(name = "menu_item_id")
private Long id;
private ItemType type;
private String name;
private String description;
private String imgUrl;
private Double price;
private MenuItemStatus status = MenuItemStatus.ENABLED;
#OneToMany(mappedBy = "menuItem", cascade = CascadeType.ALL)
private List<BillDetail> billDetails = new ArrayList<>();
Class BillDetail
#Entity
#Table(name = "bill_detail")
public class BillDetail {
#EmbeddedId
private BillMenuItemID billMenuItemID = new BillMenuItemID();
#ManyToOne
#MapsId("billId")
#JoinColumn(name = "bill_id")
private Bill bill;
#ManyToOne
#MapsId("menuItemId")
#JoinColumn(name = "menu_item_id")
private MenuItem menuItem;
#Column
private Long quantity;
#Column
private Double subtotal;
GET method
#GetMapping
public List<Bill> getMenuItems() {
return billService.getBills();
}
public List<Bill> getBills() {
return billRepository.findAll();
}
public interface BillRepository extends JpaRepository<Bill, Long> {
}
Database
database
In the class MenuItem you should add the annotation #JsonIgnore to prevent an infinite loop in the JSON format returned; a bill has a BillDetails , a BillDetails has a MenuItem , a MenuItem Has a BillDetails , every BillDetail has a List of MenuItem ...
#Entity
#Table(name = "menuItem",
uniqueConstraints = {
#UniqueConstraint(name = "menu_item_name_unique", columnNames = "name")
}
)
public class MenuItem {
#Id
#GeneratedValue(
strategy = GenerationType.IDENTITY
)
#Column(name = "menu_item_id")
private Long id;
private ItemType type;
private String name;
private String description;
private String imgUrl;
private Double price;
private MenuItemStatus status = MenuItemStatus.ENABLED;
// ADD JSON IGNORE ANNOTATION HERE :
#JsonIgnore
#OneToMany(mappedBy = "menuItem", cascade = CascadeType.ALL)
private List<BillDetail> billDetails = new ArrayList<>();

Deserialize inner json string property with GSON

I'm using Springboot and Gson for my backend.
I have these two classes in a many to many relation:
Order class
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table( name = "orders")
public class Order {
#Id
#Expose
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Expose
#NotBlank
private String poNumber;
#Expose
#NotBlank
private String dimension;
#Expose
#NotBlank
private int initialQuantity;
#Expose
#NotBlank
private int leftQuantity;
#Expose
#NotBlank
private Date startDate;
#Expose
#NotBlank
private Date endDate;
#Expose
#SerializedName("status")
#Enumerated(EnumType.STRING)
#Column(length = 20)
private PoStatus status;
#OneToMany(mappedBy="order")
private Set<Log> logs;
#Expose
#SerializedName("products")
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable( name = "orders_products",
joinColumns = #JoinColumn(name = "order_id"),
inverseJoinColumns = #JoinColumn(name = "sap_code"))
private Set<Product> products = new HashSet<>();
Product Class
#Getter
#Setter
#Builder
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table( name = "products")
public class Product {
#Expose
#Id
#NotBlank
private String sapCode;
#Expose
#NotBlank
private String sapCodeDescription;
#Expose
private String productData;
}
And this is the service that I use to serve the data to my rest endpoint
public String getAllOrders() {
List<Order> allOrders = orderRepository.findAll();
String allOrdersResult = gson.toJson(allOrders);
return allOrdersResult;
}
And this is the response:
[
{
"id": 1,
"poNumber": "003100059361",
"dimension": "INTBKGR",
"initialQuantity": 200000,
"leftQuantity": 200000,
"startDate": "17/08/2022 00:00",
"endDate": "17/08/2022 00:00",
"status": "READY",
"products": [
{
"sapCode": "000000000000416234",
"sapCodeDescription": "1.STUFE 15X",
"productData": "{\"pieces\": 85, \"mark\": true, \"description\": \"elementum pellentesque quisque porta volutpat erat quisque erat eros viverra eget congue eget\"}"
}
]
}
]
My aim is to deserialize/escape the productData String property.
I've tried by creating a ProductData class and using the #JsonAdapter annotation, but as far as I understood this annotation Is used when you need to give a custom behaviour to your deserialization, the JSON string in my example is very simple and I don't need any particular logic behind it.
I think you have to declare a class for that and change productData type from string to that class.
I resolved in this way, I think this is NOT a good approach and I hope that there is a more automatic approach to solve this.
Product Class
public class Product {
#Expose
#Id
#NotBlank
private String sapCode;
#Expose
#NotBlank
private String sapCodeDescription;
//THIS PROPERTY IS TO SAVE THE JSON IN THE DB
#NotBlank
#Column(columnDefinition="TEXT")
#JsonProperty
private String productDataColumn;
//THIS PROPERTY IS TO EXPOSE THE DATA TO THE API
#Transient
#Expose
private ProductData productData;
public void createProductData() {
this.productData = new Gson().fromJson(productDataColumn, ProductData.class);
}
}
ProductData Class
public class ProductData {
#Expose
public int pieces;
#Expose
public boolean marcatura;
#Expose
public String description;
}
OrderService
public String getAllOrders() {
List<Order> allOrders = orderRepository.findAll();
for(Order o : allOrders){
Product orderProduct = o.getProducts().stream().findFirst().get();
orderProduct.createProductData();
}
String allOrdersResult = gson.toJson(allOrders);
return allOrdersResult;
}

Could not write JSON: failed to lazily initialize a collection of role one to one

I have the following code...
It's an application where the user has a wall, and on that wall there can be several games.
#Entity
#Table(name = "tb_user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(mappedBy = "user",fetch = FetchType.EAGER)
private Mural mural;
public User() {
}
public User(Long id, String name, Mural mural) {
this.id = id;
this.name = name;
this.mural = mural;
//getters and setters...
}
#Entity
#Table(name = "tb_mural")
public class Mural implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany
#JoinTable(name = "tb_mural_games",
joinColumns = #JoinColumn(name = "mural_id"),
inverseJoinColumns = #JoinColumn(name = "games_id"))
private Set<Game> games = new HashSet<>();
#OneToOne
#JoinColumn(name = "user_id")
private User user;
public Mural() {
}
public Mural(Long id, String name, User user) {
this.id = id;
this.name = name;
this.user = user;
}
//getters and setters...
And the following request...
#GetMapping
public ResponseEntity<List<User>> findAll(){
List<User> list = repository.findAll();
return ResponseEntity.ok().body(list);
}
But I have the following error...
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: failed to lazily initialize a collection of role... continue...
I tried using FETCH.EAGER but it doesn't work, it only works if I use jsonignore, but I would like it to return the "mural" in the user request.
The user request data are these =
[
{
"id": 1,
"name": "Anderson Conforto"
},
{
"id": 2,
"name": "Airton Conforto"
}
]
but using jsonignore, it ignores the mural class data, and that's not what I'd like.
Put the JsonIgnore in the Mural class on
#OneToOne
#JoinColumn(name = "user_id")
private User user;
Also try to avoid directly returning entities to the end user. Either try to map the entity to DTO or use projection (https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections).

how to display DTO correctly in Spring boot

I have two Entities in my spring boot application. I am working with dtos and displaying my dto in the end. But I am getting the wrong output from my getRequest. My first entity is MeetingSetting which can have multiple MeetingTimes and inside MeetingTime I have meetingName as a foreign key. I want to display meetingTime like this:
{
"id": 1,
"date": "2021-06-31",
"startTime": "15:30",
"endTime": "16:30",
"meetingName": "Test"
}
But I am getting instead this one:
{
"id": 1,
"date": "2021-06-31",
"startTime": "15:30",
"endTime": "16:30",
"meetingName": {
"id": 1,
"meetingName": "Tewasddweewrst2",
"meetingUrl": null,
"meetingPw": ""
}
}
Could someone take a look at my code and tell me what I am doing wrong?
MeetingSetting Entity::
#Entity
#Table(name = "meeting_settings")
#Setter
#Getter
public class MeetingsSetting implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "meeting_name", unique = true)
private String meetingName;
#Column(name = "meeting_url")
private String meetingUrl;
#Column(name = "meeting_pw")
private String meetingPw;
#OneToMany(mappedBy = "meetingName", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set<MeetingTime> meetingTime = new HashSet<>();
}
MeetingSettingDTO:
#Getter
#Setter
public class MeetingSettingDTO {
private Long id;
#NotNull
private String meetingName;
#NotNull
private String meetingUrl;
#NotNull
private String meetingPw;
#JsonIgnore
private Set<MeetingTime> meetingTime;
}
MeetingTimeEntity:
#Entity
#Table(name = "meeting_times")
#Getter
#Setter
public class MeetingTime implements Serializable {
#JsonIgnore
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "meeting_date")
private String date;
#Column(name = "start_time")
private String startTime;
#Column(name = "end_time")
private String endTime;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "meeting_name" , referencedColumnName = "meeting_name")
private MeetingsSetting meetingName;
}
MeetingTimeDTO:
#Getter
#Setter
public class MeetingTimeDTO {
private Long id;
#NotNull
private String date;
#NotNull
private String startTime;
#NotNull
private String endTime;
private MeetingSettingDTO meetingName;
}
In my service I am first getting MeetingTime as an entity from my repository then converting it to DTO and returning it for my controller:
#Service
public class MeetingTimeService {
ModelMapper modelMapper = new ModelMapper();
#Autowired
MeetingTimeRepository meetingTimeRepository;
public List<MeetingTimeDTO> findAllMeetingTimes(){
List<MeetingTime> meetingTimeList = meetingTimeRepository.findAll();
return meetingTimeList.stream()
.map(this::convertToDto)
.collect(Collectors.toList());
}
private MeetingTimeDTO convertToDto(MeetingTime meetingTime) {
MeetingTimeDTO meetingTimeDTO = modelMapper.map(meetingTime, MeetingTimeDTO.class);
return meetingTimeDTO;
}
}
Controller:
#GetMapping(value = "/" )
public List<MeetingTimeDTO> getAllTimes() {
return meetingTimeService.findAllMeetingTimes();
}
In MeetingTimeDTO:
private MeetingSettingDTO meetingName;
The type needs to be changed to:
private string meetingName;
I have not worked with ModelMapper so cannot help you with how to map a specific field from the related object but this answer here seems to provide the info needed.

Working with Foreign keys in JpaRepository

I am developing simple API for practice project Online Shopping system. Since I am very new in working with APIs, I am having a trouble with my Entities and relationships. First, I give all my schema and classes before introduce the problem.
Here is a link for my database schema.
These are #Entity classes:
----
#Entity
#Table(name = "Customer")
public class Customer {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "country")
private String country;
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Order> orders = new ArrayList<>();
// constructor, getters, setters ....
#Entity
#Table(name = "Order")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "date")
private Date date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "cust_id", nullable = false)
#JsonIgnore
private Customer customer;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Detail> details = new ArrayList<>();
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Invoice> invoices = new ArrayList<>();
//constructor, setters, getters ....
#Entity
#Table(name = "Product")
public class Product {
#Id
#GeneratedValue
#Column(name = "id")
private int id;
#Column(name = "name")
private String name;
#Column(name = "description")
private String description;
#Column(name = "price")
private Double price;
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Detail> orderDetails = new ArrayList<>();
//cons, setters, getters ...
#Entity
#Table(name = "Detail")
public class Detail {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ord_id", nullable = false)
#JsonIgnore
private Order order;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "pr_id", nullable = false)
#JsonIgnore
private Product product;
#Column(name = "quantity")
private int quantity;
//similar classes for Invoice and Payment (no problem with them)
Here is my Sample Repository class:
#Repository
public interface ProductRepository extends JpaRepository<Product, Integer> {
}
Also here is my controller class:
#RestController
public class OnlineShoppingApiController {
#Autowired
ProductRepository productRepository;
#Autowired
OrderRepository orderRepository;
#Autowired
CustomerRepository customerRepository;
#Autowired
DetailRepository detailRepository;
#Autowired
InvoiceRepository invoiceRepository;
#Autowired
PaymentRepository paymentRepository;
#GetMapping("/products")
public List<Product> getProductsList(){
return productRepository.findAll();
}
#GetMapping("/customers")
public List<Customer> getCustomersList(){
return customerRepository.findAll();
}
#GetMapping("/orders")
public List<Order> getOrdersList(){
return orderRepository.findAll();
}
#GetMapping("/invoices")
public List<Invoice> getInvoicesList(){
return invoiceRepository.findAll();
}
#GetMapping("/payments")
public List<Payment> getPaymentsList(){
return paymentRepository.findAll();
}
#GetMapping("/details")
public List<Detail> getDetailsList(){
return detailRepository.findAll();
}
I am doing the same approach for all APIs and relationships.
When I call for /products in postman, I am getting result JSON like this:
[{
"id": 3,
"name": "pname_816",
"description": "pdesc_871_871_871_87",
"price": 1.41,
"orderDetails": [
{
"id": 9,
"quantity": 831
},
{
"id": 51,
"quantity": 701
},
{
"id": 87,
"quantity": 310
}
]
},
{
"id": 4,
"name": "pname_395",
"description": "pdesc_495_495_495_49",
"price": 26.65,
"orderDetails": [
{
"id": 85,
"quantity": 853
}
]
}]
Same fine results for /details, /invoices, and /payments.
The problem is if I send GET request for /customers, the result:
{
"timestamp": "2018-04-05T11:53:39.558+0000",
"status": 500,
"error": "Internal Server Error",
"message": "Could not write JSON: could not extract ResultSet; nested exception is com.fasterxml.jackson.databind.JsonMappingException: could not extract ResultSet (through reference chain: java.util.ArrayList[0]->com.example.project.pojo.Customer[\"orders\"])",
"path": "/customers"
}
And if i send request for /orders, the result is:
{
"timestamp": "2018-04-05T11:54:37.316+0000",
"status": 500,
"error": "Internal Server Error",
"message": "could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.SQLGrammarException: could not extract ResultSet",
"path": "/orders"
}
The same approach is not working for all. I cannot understand where the problem is. Please help me to find it.
Thanks for the answer
I have finally found the answer by myself. Here the problem is not with Annotation or key referencing but with the naming the Entities.
Since order is reserved keyword for MySql, naming the entity and variables like this causes unexpected problems.
So I have just changed the Entity name to Orders in schema and code and working fine.
Hope this post will help for others too

Categories

Resources