Spring Hibernate product - category relationship - java

I've read many tutorials about spring-hibernate relationships but I'm a bit confused about how to use them in my case... I've product/category entities defined as follow:
Product
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#Column
private int category;
.
.
.
Category
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#NotEmpty
#Column
#Size (max = 25)
private String name;
.
.
.
So, I'd like in the product list page, under the voice "category" would appear the category name, and in the product form the category list...
In my case a product fits only one category so if I'm right it should be a #ManyToOne but I don't know how to implement this... in my product database I've the categoryId field, but if I mark the category entity field as #OneToMany it will not be stored to the db...
EDIT
I've changed like this (as suggested):
Product.class
#Table(name = "products")
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#NotEmpty
#Column
#Size (max = 25)
private String name;
#Column
#Size (max = 255)
private String description;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "category_id", nullable = false)
private Category category;
Category.class
#Entity
#Table(name = "categories")
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#NotEmpty
#Column
#Size (max = 25)
private String name;
#Column
#Size (max = 255)
private String description;
//Here mappedBy indicates that the owner is in the other side
#OneToMany(fetch = FetchType.EAGER, mappedBy = "category", cascade = CascadeType.ALL)
private Set<Product> products = new HashSet<Product>();
Controller
#RequestMapping(value = "/add/", method = RequestMethod.POST)
public String addProduct(
#ModelAttribute(value = "product") #Valid Product product,
BindingResult result, ModelMap model, Category category) {
if (result.hasErrors()) {
return "forms/productForm";
}
try {
category.addProduct(product);
product.setCategory(category);
// Add product to db
productService.addProduct(product);
} catch (Exception e) {
log.error("/add/---" + e);
return "redirect:/product/deniedAction/?code=0";
}
return "redirect:/admin/product/";
}
I also added a #initbinder on the product controller to translate the data from the product form string to Category... but now when I save a product it automatically saves a category instead of attach the existing selected one...

As the Product will have only one Category and Category will have a list of Products, you can relate these two by creating a Foreign Key in the Product table to refer to the primary key in the Category table:
Category Table: id, name, other fields...
Product Table: id, category_id (FK), and other fields.
And the mapping can be defined as below:
public class Category {
//Here mappedBy indicates that the owner is in the other side
#OneToMany(fetch = FetchType.LAZY, mappedBy = "category", cascade = CascadeType.ALL)
private Set<Product> products = new HashSet<Product>();
...
}
public class Product {
//Here JoinColumn states that this entity is the owner of the relationship
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", nullable = false)
private Category category;
...
}
The mappedBy attribute tells Hibernate that the collection is a mirror image of the many-to-one association on the other side. Its like telling Hibernate that it should propagate changes made at the Product end of the association to the database, ignoring changes made only to the products collection that you have in the Category. Thus if we only call category.getProducts().add(product), no changes will be made persistent. As the association is bidirectional, you have to create the link on two sides, not just one.
For your convenience, you can add one addProduct method in the Category class to save the association:
public void addProduct(Product product) {
product.setCategory(this);
products.add(product);
}

You appear to have a one-to-many relationship between Category and Product (one category has many products)
In Java (and OO generally) you'd expect the Category class to contain a list of Products, so the Category can be said to 'own' products.
In SQL it's the other way round - you'd expect Product table to hold a foreign key reference to a Category, so here, the Product can be said to 'own' a Category.
Looks like your using JPA, so you could have something like this:
Category class:
#Entity
public class Category {
//other stuff...
#OneToMany(cascade=CascadeType.ALL, mappedBy="category")
private Set<Product> products;
}
Product class:
#Entity
public class Product {
//other stuff...
#ManyToOne
private Category category;
}

so you have this:
Product{
atributtes...
#ManyToOne
Category category; --so every product has a category
}
Category {
attributtes...
#OneToMany(cascade=CascadeType.ALL)
#JoinColumn(name="id_Product")
private List<Product> products;
}
try this, if not we can look another solution..

You are right, you should use #ManyToOne because "...a product fits only one category...".
In Product entity declare a Category field instead of int category and annotate it with #ManyToOne. Also add #JoinColumn to specify the name of product.category_id column in the database.
Product:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column
private int id;
#ManyToOne
#JoinColumn(name = "category_id")
private Category category;
.
.
.

Related

Is there any way to optimize ManyToOne Relationship using JPA?

I have an employee who can work in multiple departments. I have OneToMany relashionship in Employee and ManyToOne in the Department class.
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Size(min = 3, max = 10, message = "Invalid name")
private String name;
#Email
private String email;
#OneToMany(mappedBy = "employee")
private List<Department> departments;
}
#Entity
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String department;
#JsonIgnore
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "employee_id", referencedColumnName = "id")
private Employee employee;
}
The created tables in MySQL looks like the following:
The employees table:
The departments table:
The problem is that I will have multiple employees and they can have multiple departments. The departments table will be too large and the departments name will be repeated for the different employees as you can see in the picture above I have 2xManagement. My question is if it is possible to create the departments table without the employee_id (only to have the departments name) and do the linking in a separate table with only two properties - employee_id and department_id. Do I need to create a new class for that? How can I optimize this relashionship? Is there a way to do it?
You need change solution to #ManyToMany by using weak entity,
References: https://www.baeldung.com/hibernate-many-to-many

Why spring data jpa-java.lang.IllegalStateException: Multiple representations of the same entity in OneToMany relationship?

I am working a very small application which contains 3 entity classes.
1.Category.
2.Products.
3.User
Relationships:-
a. OneToMany between User and Products.
b. OneToMany and ManyToOne between category and products i.e. a category can have multiple products and multiple products can belong to same category.
Entity Classes are shown below:-
User Entity:-
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String username;
private String lastname;
private String email;
private String password;
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.EAGER)
private Set<Products> products;
//getter and setter
}
Products Entity:-
#Entity
public class Products {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String productname;
private String cost;
#ManyToOne(cascade = {CascadeType.MERGE,CascadeType.PERSIST},
fetch = FetchType.LAZY)
private Category category;
//getter and setters
}
Category Entity:-
#Entity
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
#OneToMany(mappedBy = "category",
cascade = {CascadeType.PERSIST, CascadeType.MERGE},
fetch = FetchType.EAGER)
private List<Products> products;
//getter and setters
}
Method to merge User with products in data base:-
#GetMapping("/cart")
public String Cart(Model model){
model.addAttribute("cart",productsSet);
System.out.println(productsSet);//At this stage in console I am able to see products added in set
User user = userRepository.findById(1);//hard coded for now.
user.setProducts(productsSet);
userService.saveUserProducts(user);//saveUserProducts() method in shown below.
productsSet.clear();
return "mycart";
}
saveUserProducts() :-
#Override
#Transactional
public void saveUserProducts(User user) {
entityManager.merge(user);
}
But when I am running the program I see the following exception in console:-
java.lang.IllegalStateException: Multiple representations of the same entity [com.demo.shopping.com.Entity.Products#2] are being merged. Detached: [Products{id=2, productname='p2', cost='200'}]; Detached: [Products{id=2, productname='p2', cost='200'}]
I found an article on stack-overflow but it was not fit in my situation.(java.lang.IllegalStateException: Multiple representations of the same entity with #ManyToMany 3 entities).Except this I don't get any relevant thing.
Please help me to let me know how to deal with this situation. Hope someone will help.
Thanks in advance.
Remove CascadeType.MERGE user class because in my program I am not adding new products also except this I am creating relation between existing user and products.

How to remove children from parent entity record in JPA?

I have Product entity and ProductRating entity, each Product can have many ProductRatings. When Product is deleted I want to have associated ratings deleted too, but nothing works so far (also orphanRemoval set to true)...
Classes:
#Getter
#Setter
#Entity
#Table(name = "PRODUCT")
public class Product extends AbstractEntity<Long> {
#Column(nullable = false)
private String name;
private String description;
#Column(nullable = false)
#Min(value = 0)
private Float cost;
#OneToMany(mappedBy = "product",
orphanRemoval = true, cascade = CascadeType.PERSIST,
fetch = FetchType.EAGER)
//#OnDelete(action = OnDeleteAction.CASCADE)
#Fetch(value = FetchMode.SELECT)
private Set<ProductRating> productRatings;
}
#Getter
#Setter
#Entity
#Table(name = "PRODUCT_RATING")
public class ProductRating extends Rating {
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "product_id")
#NotNull(message = "Rating must be in context of Product")
private Product product;
}
After Product deletion ratings stay with deleted Product's ID
AbstractEntity implementation:
#Getter
#Setter
#MappedSuperclass
public abstract class AbstractEntity<I> implements Serializable {
private static final long serialVersionUID = 1700166770839683115L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "ID", unique = true, nullable = false)
private I id;
}
In the #OneToMany relation you need to add the cascade type delete: cascade = {CascadeType.PERSIST, CascadeType.REMOVE}
Or if you don't mind having all cascade types you can just put: cascade = CascadeType.ALL
EDIT:
Also check the name of the Product primary key in the database.
It should match the defined in the #JoinColumn annotation of ProductRating
The default database field for the attribute id of the Product class would be product_id.
However you have defined the id in AbstractEntity as name = "ID" so the #JoinColumn should be something like: #JoinColumn(name = "ID")
My alternative approach to fix this problem is to:
On parent-side relation create method with #PreRemove annotation
in this method iterate over collection with #[One/Many]ToMany annotation and call delete(obj) method for corresponding repository on child
On child-side relation create method with #PreRemove annotation
In this method set parent to null

JPA: Reference column in the child entity is null when using unidirectional #OneToMany

I have two entity classes.
Order.java
#Entity
#Table(name = "order_table")
public class Order implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "order_id", referencedColumnName = "id", nullable = false, insertable=false, updatable=false)
private Set<Item> items;
// getters & setters & toString
Item.java
#Entity
#Table(name = "item")
public class Item implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "order_id", nullable = false)
private Long orderId;
// getters & setters && toString
I created a test class like this:
#Test
public void createOrderWithItems() {
Item item = new Item();
item.setName("Iron Man");
Order order = new Order();
order.setName("Toy");
order.getItems().add(item);
Order created = service.createOrder(order);
Order orderById = service.getOrderById(order.getId());
System.out.println("Created Order: " + orderById);
Item itemById = service.getItemById(item.getId());
System.out.println("Created item: " + itemById);
Assert.notNull(created.getId(), "Order ID is Null");
}
Test is green but if you check output, you'll see that orderId field in the Item class is null.
Created Order: Order{id=1, name='Toy', items=[Item{id=2, name='Iron Man', orderId=null}]}
Created item: Item{id=2, name='Iron Man', orderId=null}
Does JPA not update this column in the db automatically? Is this column is redundant? If so, how can I retrieve this information from test code?
You need to set orderId explicitly.
item.setOrderId(order.getId());
order.getItems().add(item);
You can create a method addItem(Item item) in your Order class and hide this logic within it.
Cascading will create an entry in db but it won't initialize field. JPA annotations just indicate to JPA provider how to perform mapping between entity and table.
Moreover, check your annotations. #JoinColumn should be used in the entity which owns the relationship (the corresponding table has column as a foreign key). Check the top answer for this question for detailed explanations: What's the difference between #JoinColumn and mappedBy when using a JPA #OneToMany association

How to manage OnetoOne inserting data in child only

I am very new to hibernate and I am working with JPA and Hibernate4. Trying to insert parent object in child as onetoone relationship.
I went through some tutorials but All the example in the web shows, inserting both parent and child tables.
I want to insert data in child table only.
I have two tables called user and department.
User table consists of user details with department as onetoone relationship, as follows,
#Entity
#Table(name = "User")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "departmentId")
private Department departmentId;
// getters and setters...
}
Below is my Department entity,
#Entity
#Table(name = "Department")
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "name")
private String name;
// getters and setters...
}
In department table there is only 4 data. I want to insert data only in user data while insert into it and don't want to insert in Department.
How can I do that.Please assist.
You have to use mappedBy for this, as mentoned below in child Table, Department in your case
#OneToOne(mappedBy="department")
private UserEntity user;
These posts explain you better this,
JPA JoinColumn vs mappedBy
Understanding mappedBy annotation in Hibernate
You need to specify the relationship owner using mappedBy property in the OneToOne mapping in the owner side, here in your case in the Department class, you should add:
#OneToOne(mappedBy="department")
private UserEntity user;
I updated your code, to included the stated annotation and also renamed the Department property in your UserEntity class from departmentId to department to avoid confusion between relationship owner and its id:
#Entity
#Table(name = "User")
public class UserEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "firstName")
private String firstName;
#Column(name = "lastName")
private String lastName;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "departmentId")
private Department department;
// getters and setters...
}
Below is the Department entity,
#Entity
#Table(name = "Department")
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "_id")
private String id;
#Column(name = "name")
private String name;
#OneToOne(mappedBy="department")
private UserEntity user;
// getters and setters...
}
This will give you the right mapping with the expected behaviour.
In the #OneToOne annotation, the default value for parameter optional is true. So your annotation is the same as #OneToOne(fetch = FetchType.EAGER, optional = true). This means you can simply leave the Department in a UserEntity instance empty. In that case, persisting it results in persisting only a user entity and no department.
Even if you created a Department instance and assigned it to a UserEntity instance, persisting the UserEntity would not automatically persist the Department, since you don't have any cascade parameter in your annotation. If you don't automatically cascade persists, you would have to persist the Department first and then persist the corresponding user entity.
Maybe you're asking about using existing departments for your user entities. In that case, you first need to get the department via Hibernate (or the JPA API) from an entity manager. The entity instance you get is managed by Hibernate, and you can then set it in a UserEntity and persist that, to have it refer to the department.
Finally, I think one department will probably have more than one user. It might make more sense to have a #ManyToOne annotation instead of #OneToOne, indicating multiple users can refer to the same department, but that depends on your domain model.

Categories