I have a Spring boot application in which I have a one to many relationship
between my category entity and my product entity. A category contains several products
and a product belongs to one and only one category.
In the database, each category contains several registered products.
The category that contains less products in database has 1000 products
I have a category of my products in database which contains 15 000 products.
At the level of my category class, I have defined the one-to-many annotation
with the product entity and I have opted for the fetch mode = FetchType.LAZY.
This only retrieves the category entity and not the product entity.
How to do for the performance problem, because I want to create a method that
retrieves for each category, all its products and loop on it.
Even if I use the fetch mode = FetchType.EAGER,
I will have the same problem, how to better use FetchType.LAZY or FetchType.EAGER
while managing well the performance problem
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Category {
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy = "category")
private List<Product> product;
}
#Data
#SuperBuilder
#NoArgsConstructor
#AllArgsConstructor
#Entity
public class Product {
#Id
#GeneratedValue
private Integer id;
private String code;
private double price;
#ManyToOne
#JoinColumn(name="id_cat")
private Category category;
}
How can i solve it
You have 2 options:
Eager fetch type with #Fetch(FetchMode.JOIN) (https://www.baeldung.com/hibernate-fetchmode),
JOIN FETCH (https://thorben-janssen.com/hibernate-tips-difference-join-left-join-fetch-join/)
Related
I have Order entity, each order has a list of Dish entity.
Only important part showed for brevity.
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
#Table(name = "orders")
#ToString
public class Order {
#OnDelete(action = OnDeleteAction.CASCADE)
#OneToMany(
mappedBy = "order",
cascade = CascadeType.ALL,
orphanRemoval = true)
#ToString.Exclude
private List<Dish> dishes;
}
My front sends me a Hashmap of dish.id and quantity of this dish ordered.
To convert it to Order entity I have to add the same dish to list times it was ordered.
For example. if order has 5 dishes of id 1, I fetch id 1 and add it 5 times to list, then - save order.
To display this again in front - I use Collections.frequency to convert the Order back to Hashmap.
Should I convert my relationship container to hashmap? I've heard that I should avoid using hashmaps in Hibernate.
I'm coming from Front End so all this is new to me. I have the following entities Client, Cart, Product and they are connected between them as such:
Client <=> #OneToOne <=> Cart
Cart => #OneToMany => Product(#ManyToOne for Cart)
Running my SpringDataJpaApplication creates correctly all my 3 tables with all the respective columns and their foreign keys in Postgres. Creating a new Cart with all the fields, will create both the Client and Cartin the Postgres, but will not create the Product which is a #ManyToOne. Querying the database will generate empty fields for Product
// Client
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
public class Client {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int client_id;
private String name;
private String lastName;
#OneToOne(mappedBy = "client")
private Cart cart;
}
// Cart
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
public class Cart {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int cart_id;
#OneToMany(mappedBy = "cart")
private List<Product> productList;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_client_id", referencedColumnName = "client_id")
private Client client;
}
//Product
#Data
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int product_id;
private String productName;
private int price;
private int qty;
#ManyToOne
#JoinColumn(name = "fk_cart_id")
private Cart cart;
}
// CartController
#RestController
public class CartController {
#Autowired
CartRepository cartRepository;
#PostMapping("/createCart")
public Cart createCart(#RequestBody Cart request) {
return cartRepository.save(request);
}
}
As mentioned, posting to the endpoint, returns a 200 with records (as mentioned above) only created for Client and Cart. The post request is as follow:
{
"productList": [
{
"productName": "mobile",
"price": 800,
"qty": 2
}
],
"client": {
"name": "John",
"lastName": "Done"
}
}
What Am I doing wrong? Why isn't Product table populated?
(Note: With only #OneToMany and #ManyToOne between Cart and Product I'm able to populate both of them, but when the Client is thrown in to the mix, I have the issue describbed above)
The concept of owning entity is what you are missing.
In the Cart entity you have:
#OneToMany(mappedBy = "cart")
private List<Product> productList;
This says that the Product entity owns the relationship. Only the entity that owns the relationship can persist a relation. This means that a Product can persist a Cart but not visa-versa. In short, you have to persist a Product yourself. So, save the cart, then set the cart in the product, and save the product.
If you didn't have it then the #ManyToOne by itself dictates the same course of action.
#ManyToOne
#JoinColumn(name = "fk_cart_id")
private Cart cart;
The productList should be considered as a query only field.
Note that you can play games with Cascade annotations in Cart but I do not recommend that. It is a somewhat complicated and vexing annotation. Instead of having a Repository in your Controller layer, create a #Service layer and put the persist, retrieve, and other business logic in there and include that in your controller. Standard pattern.
Then write junit/mockito test cases for your service layer.
EDIT: Just to be clear, a Product cannot persist a Cart, but rather must have a previously persisted Cart to set to the cart field. No such requirement for the Cart entity.
I have a one-to-many relationship with Customer and Address, but I'm not sure how to represent this with JPA. I don't want use #OneToMany for the AddressEntity's in CustomerEntity because I want to avoid wrapping it in a Collection.
I'm wondering what annotation or even other strategies I can use to maintain the relationship where one customer will, for simplicity, always have two addresses. Any suggestions are appreciated!
Address Entity
#Data
#NoArgsConstructor
#Entity(name = "address")
public class AddressEntity
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#?
private CustomerEntity customer;
}
Customer Entity
#Data
#NoArgsConstructor
#Entity(name = "customer")
public class CustomerEntity
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#?
private AddressEntity shippingAddress;
#?
private AddressEntity billingAddress;
}
For the case when an address can belong different customers.
#Entity
public class AddressEntity
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
}
#Entity
public class CustomerEntity
{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ManyToOne
private AddressEntity shippingAddress;
#ManyToOne
private AddressEntity billingAddress;
}
if each customer has unique address, better to store the addresses in the same customer record.
You can create class EmbeddedAddress and use #Embedded and #Embeddable annotations.
For your exact scenario, I think you could go for #PostLoad.
The steps would be:
use #OneToMany annotation to load the addresses into a collection
annotate both shippingAddress and billingAddress with #Transient
create a public method annotated with #PostLoad
initialise your 2 transient fields (you need to have at least an enum to discriminate between the addresses)
Why would the steps above work?
PostLoad is invoked after an entity is loaded from the database
the fields need to be transient, because they are not mapped to database columns
A relevant example can be found here.
While the approach above would solve your problem, it adds some degree of verbosity in your JPA entities. I would suggest to go for #OneToMany and make sure you add an enum in AddressEntity to check if an address is for shipping or billing.
Also, given that you mentioned that there is a one-to-many relationship between a customer and an address, then there is a many-to-one relationship between an address and a customer. The annotation to use in the AddressEntity class is #ManyToOne
The idea that raised this question is the following:
Each User has an active cart and a history of purchased/finalized carts.
I know there might be logical/reasoning flaws in my approach so I'm basically just asking if this is doable: Is it possible to have the 2 separate tables for the list of carts which contains carts with a certain value in the status field? I'd like for a cart to be in a certain table while status is active and to move to the other table once it's finalized.
#Entity
#Table(name = "users")
class User{
#Id
private Long id;
#OneToOne
private ShoppingCart activeCart;
#OneToMany
private List<ShoppingCart> shoppingCartHistory;
//getters setters
}
#Entity
#Table(name = "carts")
class ShoppingCart{
#Id
Long id;
private List<String> items;
private String status;
//getters setters
}
Have next tables structure in SQL schema :Clients, Employees, Orders.
And 3 Entity classes in java code accordingly : Client, Employee, Order.
Both primary id fields from Clients and Employees are in Orders table as foreign keys.
Question is how it should be displayed in java code?
As I understand here it should be done smth like adding Set field to Clients and Employees annotated with #OneToMany.
But what should be done in Order Entity and maybe I have to add any additional annotations except #OneToMany?
I think you have some misconceptions about the relational mapping of Hibernate.
If in fact your Orders table have foreign keys of Clients and Employees, then the annotation you are looking for is #ManyToOne
#OneToMany annotation is used when your entity have multiple records referenced by the targeted entity, while #ManyToOne is used when your entity have only one record referencing the targeted entity.
For example:
Orders entity have one reference from Clients and one reference from Employees entities.
In this case, Orders entity could be mapped by the following way:
#Entity
#Table(name = "Orders")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Client client;
#ManyToOne
private Employee employee;
//getters and setters
}
#Entity
#Table(name = "Clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String attribute1;
//getters and setters
}
#Entity
#Table(name = "Employees")
public class Employee implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String attribute1;
//getters and setters
}
With the example given above you should be able to make your schema work fine with Hibernate, but for the sake of understanding, let's imagine a scenario where you would need to get all the Orders from a Client, of course you could do it with a query selecting only the Orders inside the Client table, however Hibernate offers the #OneToMany annotation which will give you the possibility to access all the Orders from a Client without the need of a separate query, only by mapping! Let's see an example:
#Entity
#Table(name = "Orders")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Client client;
//getters and setters
}
#Entity
#Table(name = "Clients")
public class Client implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String attribute1;
#OneToMany(mappedBy = "client")
private List<Order> orders;
//getters and setters
}
In this example you should be able to get all the Orders from a Client just by calling the get of the "orders" attribute. Please, note that on the #OneToMany mapping we have specified the "mappedBy" attribute as "client", it was needed because we have a bidirectional mapping between Client and Order, in a simple usage of #OneToMany you would not need this mapping.
Important: When working with #OneToMany mapping you would eventually face some lazy fetching problems, in this case I highly recommend you to take a look at this question:
Solve “failed to lazily initialize a collection of role” exception
Also, I think you should start reading more about Hibernate to understand about it's basic concepts, please, check this other question about #OneToMany and #ManyToOne annotations on Hibernate:
Hibernate/JPA ManyToOne vs OneToMany