Working with Foreign keys in JpaRepository - java

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

Related

Hibernate Entity Mapping

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

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.

Saving data on jpa hibernate table gives null on join columns

I have two tables which are related to each other, table "user" and "address":
#Entity
#Table(name = "user")
#Data
public class User{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotNull
#Column(unique = true)
private String user_name;
#Column
private String description;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
protected Set<Address> addresses= new HashSet<>();
}
While in the other table:
#Entity
#Table(name = "address")
#Data
public class Address{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToOne
#JoinColumn(name = "user_id")
protected User user;
private String description;
}
I did a post request to create new user with some addresses:
#PostMapping ("/user/create")
public ResponseEntity post(#RequestBody User user) {
userService.saveNewUser(user);
// return
}
In a post request I sent this json:
{
"user_name": "example",
"description": "this is a user description",
"addresses": [
{
"description": "this is a address 1"
},
{
"description": "this is a address 2"
}
]
}
When I insert data I get the "address" table the "user_id" is null, the data are inserted but the relations are not there?
What I'm doing wrong here? Please help!
Update: Let's say I have this method saveNewUser on the service, how to call update Address?
public Oject saveNewUser(User user ) {
//
return jpaRepository.save(user);
}
You have to support all relations manually. Hibernate doesn't do it for you.
So you need to set a user for each address.
public Oject saveNewUser(User user ) {
user.getAddresses().forEach(address -> address.setUser(user));
return jpaRepository.save(user);
}

Insert a record into multiple Table using JPA

I have three entities namely
product
product details
stock
category
reference is given below
when I try to get the details of product it works fine when I try to save it shows below error in the console
2020-08-12 13:17:22.279 WARN 18612 --- [nio-9002-exec-1] .c.j.MappingJackson2HttpMessageConverter : Failed to evaluate Jackson deserialization for type [[simple type, class com.eMart.main.entity.Product]]: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot handle managed/back reference 'defaultReference': back reference type (java.util.List) not compatible with managed type (com.eMart.main.entity.Product)
My question is how to add the product into the database and how did I need to optimize my entities
Input
{
"skuId": "2",
"category": {
"categoryId": 2,
"categoryName": "food"
},
"description": "cow milk",
"stock": {
"stockId": 1,
"inventoryCount": 5,
"selfCount": 5
},
"productDetails": [
{
"productDetailId": 1,
"cost": 10.0,
"currency": "inr",
"expiryDate": "2020-08-11T18:30:00.000+00:00",
"supplierCode": 1
}
]
}
controller method
#PostMapping(value = "/test")
public ResponseEntity<Product> test(#RequestBody Product product) throws Exception {
productRepositry.save(product);
return new ResponseEntity(productRepositry.findAll(),OK);
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class Product {
#Id
#Column(name = "SKU_ID")
String skuId=null;
#ManyToOne
#JoinColumn(name = "category_id")
#JsonManagedReference
Category category;
#Column(name = "description")
String description=null;
#JsonManagedReference
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "stock_id", referencedColumnName = "id")
Stock stock=null;
#JsonManagedReference
#OneToMany(mappedBy = "product", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
Set<ProductDetails> productDetails;
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class Stock {
#Id
#Column(name = "id")
Integer stockId;
#Column(name = "inventory_count")
Integer inventoryCount;
#Column(name = "self_count")
Integer selfCount;
#JsonBackReference
#OneToOne(mappedBy = "stock",cascade = CascadeType.ALL)
Product product;
}
#Entity
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Category {
#Id
#Column(name = "category_id")
Integer categoryId;
#Column(name = "category_name")
String categoryName;
#OneToMany(mappedBy = "category", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
#JsonBackReference
List<Product> product;
#Override
public String toString() {
return "Category{" +
"categoryId=" + categoryId +
", categoryName='" + categoryName + '\'' +
", product=" + product +
'}';
}
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#ToString
#Entity
public class ProductDetails {
#Id
#Column(name = "id")
Integer productDetailId;
#Column(name = "cost")
Double cost;
#Column(name = "currency")
String currency;
#Column(name = "expiry_date")
Date expiryDate;
Integer supplierCode;
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
Product product;
}
I think you have missed adding #JoinColumn on Product in ProductDetails entity
#JsonBackReference
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "SKU_ID")
Product product;
Also, you can try be removing #JsonManagedReference from Category or Stock

Spring JPA One To Many Infinite Recursion

im trying to return to a JPA data (converted to DTO, ofcourse) where it has a #OneToMany and #ManyToOne bidirectional relationship. Im currently apply thing fix. The problem is that the output is recusrive. comments has post has comments then has posts (comments -> post -> coments -> so on..).
I only wnat to have something like this
{
"post_id": 1
"user": {
// user data
},
"description": "some description",
"images": "{images,images}",
"tags": "{tags, tags}",
"comments": [
{
//some comments data
},
{
//some comments data
}
]
"lastUpdated": "2020-04-08T14:23:18.000+00:00"
}
Here are my code
This is my Posts.class
#Data
#Entity
#Table(name = "posts")
#EntityListeners(AuditingEntityListener.class)
public class Posts {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "post_id")
private Long post_id;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private Users user;
#Column(name = "description")
private String description;
#Column(name="images")
private String images;
#Column(name="tags")
private String tags;
#OneToMany(
fetch = FetchType.LAZY,
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JsonIgnoreProperties(value = { "comments" ,"hibernateLazyInitializer", "handler" }, allowSetters = true)
//#JsonManagedReference
private List<Comments> comments;
#LastModifiedDate
#Column(name = "last_updated")
private Date lastUpdated;
public void addComments(Comments comment) {
this.comments.add(comment);
}
}
Here is my PostDTO.class
#Data
public class PostDTO {
private Long post_id;
private UserDTO user;
private String description;
private String images;
private String tags;
private List<CommentsDTO> comments;
private Date lastUpdated;
}
This is my Comments.class
#Data
#Entity
#Table(name = "comments")
#EntityListeners(AuditingEntityListener.class)
public class Comments {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="comment_id")
private Long comment_id;
#OneToOne
#JoinColumn(name = "user_id")
private Users user;
#Column(name = "description")
private String description;
#Column(name="images")
private String images;
#ManyToOne
#JoinColumn(name ="post_id" , nullable = false)
#JsonIgnoreProperties(value = { "post" ,"hibernateLazyInitializer", "handler" }, allowSetters = true)
//#JsonBackReference
private Posts post;
#Column(name="last_updated")
#LastModifiedDate
private Date lastUpdated;
}
Here is my CommentsDTO.class
#Data
public class CommentsDTO {
private Long comment_id;
private UserDTO user;
private String description;
private PostDTO post;
private String images;
private Date lastUpdated;
}
Here is my REST Controller
#GetMapping
public #ResponseBody ResponseEntity<List<PostDTO>> getAll() throws Exception {
return new ResponseEntity<List<PostDTO>>(service.getAll(), HttpStatus.OK);
}
Here is my service
public List<PostDTO> getAll() throws Exception {
return repo.findAll()
.stream()
.map(this::convert)
.collect(Collectors.toList());
}
private PostDTO convert(Posts e) {
return mapper.map(e, PostDTO.class);
}
Hope someone can shed light on my issue. Kinda lost as of this time.
Problem is when you convert Post into PostDTO then by using ModelMapper it calls all field's getter of Post for PostDTO. So this happened recursively for this
mapper.map(e, PostDTO.class)
So, just remove private PostDTO post from CommentDTO ,
then modelmapper don't try to set PostDTO->comment-> post field.
And you don't need bidirectional relation in DTO. DTO is all about what you want to show in response.
You are breaking the json serialization cycle on the wrong class.
You are sending a list of PostDTO, but applied JsonBackReference to Comments and JsonManagedReference to Posts
Update
Note that ObjectMapper class, JsonManagedReference and JsonBackReference may from 2 packages
com.fasterxml.jackson.databind.ObjectMapper
com.fasterxml.jackson.annotation.JsonManagedReference
com.fasterxml.jackson.annotation.JsonBackReference
or:
org.codehaus.jackson.map.ObjectMapper
org.codehaus.jackson.annotate.JsonManagedReference
org.codehaus.jackson.annotate.JsonBackReference
If you are not consistent between all of them, the mis-matched annotation will be ignored and you will still experience infinite loop during serialization.
Use JsonIgnoreProperties along with Manage and backreference .
#Data
#Entity
#Table(name = "comments")
#EntityListeners(AuditingEntityListener.class)
public class Comments {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="comment_id")
private Long comment_id;
#OneToOne
#JoinColumn(name = "user_id")
private Users user;
#Column(name = "description")
private String description;
#Column(name="images")
private String images;
#ManyToOne
#JoinColumn(name ="post_id" , nullable = false)
#JsonIgnoreProperties(value = { "comments" ,"hibernateLazyInitializer", "handler" }, allowSetters = true)
#JsonBackReference
private Posts post;
#Column(name="last_updated")
#LastModifiedDate
private Date lastUpdated;
}
And Your Post class should be
#Data
#Entity
#Table(name = "posts")
#EntityListeners(AuditingEntityListener.class)
public class Posts {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "post_id")
private Long post_id;
#ManyToOne
#JoinColumn(name = "user_id", nullable = false)
private Users user;
#Column(name = "description")
private String description;
#Column(name="images")
private String images;
#Column(name="tags")
private String tags;
#OneToMany(
fetch = FetchType.LAZY,
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
#JsonIgnoreProperties(value = { "post" ,"hibernateLazyInitializer", "handler" }, allowSetters = true)
#JsonManagedReference
private List<Comments> comments;
#LastModifiedDate
#Column(name = "last_updated")
private Date lastUpdated;
public void addComments(Comments comment) {
this.comments.add(comment);
}
}
Also I would suggest to Use Getter and Setter annotation instead of #Data .Because Tostring() method will cause recursion also .

Categories

Resources