Is there any way to optimize ManyToOne Relationship using JPA? - java

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

Related

Springboot JPA Hibernate CascadeType confusion

How to make a column cascade enable only for insertion not update and delete.
Here are two class. There are #ManyToOne relationship between Qualification and Department. At the time of insertion I want to insert new department with qualification. But at the time of update of qualification I don't want to update department. How can I configure that??
#Table(name = "department")
public class Department {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
}
#Table(name = "qualification")
public class Qualification implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id;
private Integer passingYear;
private String result;
#ManyToOne(optional = true, cascade = { CascadeType. ? }, fetch = FetchType.EAGER)
#JoinColumn(name = "department_id", referencedColumnName = "id")
private Department department;
}
CascadeType.PERSIST is what you are looking for. The various CascadeType enums essentially say, which persistence operation of EntityManager persist/merge/remove etc. should cascade to the objects of an association.

can't find the pattern of setting cascade logic

I have 3 tables which are Person Login and Account.
Person and Login is OneToOne relation and Login has one FK which is connected Person's id column called PERSON_ID.
Person(one) and Account(many) is OneToMany relation and Account has one FK which is connected Person's id column called PERSON_ID as well .
what i want to do is when i delete one data from Account , nothing happen to Person and Login.
if i delete one data from Person which id=1, Login's PERSON_ID=1 data will be deleted , and all of the data PERSON_ID=1 from Account will be deleted as well.
if i delete one data from Login which PERSON_ID=1, Person 's id=1 data will be deleted , and all of the data PERSON_ID=1 from Account will be deleted as well.
how should i set the cascade ?
i've tried dozens of times and still can't find the logic in there, thanks!!
here's my code of all 3 tables without setting cascade:
`
#Entity
#Table(name = "PERSON")
public class Person {
#Id
#Column(name = "ID", nullable = false, unique = true)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "NAME")
private String name;
#Column(name = "SEX")
private String sex;
#OneToMany(mappedBy = "person",fetch = FetchType.LAZY)
private List<Account> account;
#OneToOne(mappedBy = "person")
private Login login;
#get..
#set..
}
`
#Entity
#Table(name = "ACCOUNT")
public class Account {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "ACCOUNT")
private String account;
#Column(name = "AMOUNT")
private String amount;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID",referencedColumnName = "ID")
public Person person;
#get..
#set..
}
`
#Entity
#Table(name = "LOGIN")
public class Login {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private long id;
#Column(name = "USERNAME")
private String userName;
#Column(name = "PASSWORD")
private String password;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID", referencedColumnName = "ID")
private Person person;
#get..
#set..
}
It's been a while, but if I'm not mistaken you need to use the cascade=REMOVE option on the OneToMany and OneToOne relationships. In the OneToOne I think you need to specify cascade=REMOVE on the side that does NOT own the relationship, that is, the side that also contains the "mappedBy" property.
Finally, I believe JPA will NOT automatically load lazy relationships and then cascade them. I'm thinking you may need to fetch the relationship before you delete the parent entity (otherwise JPA will not know what to delete).

how to manage many-to-one jpa for save and find the data using DTO

I have two table with many-to-one relationship. Example is, I have Office table and Employee table. One Employee belong to one Office and one Office belong to many Employee.
Office
#Entity(name = "office")
#Table(name = "office", uniqueConstraints = {#UniqueConstraint(columnNames = {"id"})})
public class Office {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "office_name", nullable = false)
private String officeName;
}
Employee
#Entity(name = "employee")
#Table(name = "employee", uniqueConstraints = {#UniqueConstraint(columnNames = {"id"})})
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "employee_name", nullable = false)
private String employeeName;
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "office_id", referencedColumnName = "id", nullable = false)
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
private Office office;
}
OfficeDto
public class OfficeDto {
private Long id;
private String officeName;
}
EmployeeDto
public class EmployeeDto {
private Long id;
private String employeeName;
private OfficeDto office;
}
With above way of defining the entity and the DTO, when I do employee.findAll(), the JSON result is also include the detail of the office data.
Is there any way that I could achieve (objective):
When do saving new employee, I just have to mention the id of the office.
When do findAll employee, I could choose whether I want to gove the id only or also with the entire object to the client.
Because, with current situation, I think I need to define two employee DTO. First one is contain the entire office data (like the code of EmployeeDto) and the second one is replace private OfficeDto office with private int office.
The second problem you can solve by projection : https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
Or just specific mapper to DTO, for mapping you can use mapstruct : http://mapstruct.org/documentation/installation/
For the first problem i found some answer in stack, but you need verify it : JPA many-to-one relation - need to save only Id

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.

Spring Hibernate product - category relationship

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;
.
.
.

Categories