#OneToMany relationship issue while saving multiple values in Hibernate and Spring - spring

I have two entities with #OneToMany bidirectional relationship as below:
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer companyId;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy="company")
Set<Employee> employees = new LinkedHashSet<>();
Employee class
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer empId;
private String name;
private String address;
private String email;
#ManyToOne
#JoinColumn(name="companyId")
Company company;
Intially i saved 2 employees as below:
Employee emp= new Employee();
emp.setName("John");
emp.setEmail("John#gmail.com");
employeeRepository.save(emp);
Employee emp2= new Employee();
emp2.setName("Smith");
emp2.setEmail("smith#gmail.com");
employeeRepository.save(emp2);
Now I want to save one employee working for 2 different companies like below:
Company company =new Company();
company.setName("Google");
Employee emp = employeeRepository.findOne(1);
company.getEmployees().add(emp);
emp.setCompany(company);
companyRepository.save(company);
Company company2 =new Company();
company2.setName("Microsoft");
company2.getEmployees().add(emp);
emp.setCompany(company2);
companyRepository.save(company2);
It is updating only second company id into employee table. I want both the companies to be assigned to that employee. How can I do that?

The issue here is that you have a one-to-many when obviously it should be a many-to-many, viz. an employee can work for more than one company and a company has many employees.
You can either change the relationship to a #ManyToMany (and use #JoinTable if required) or you can create another entity, say, CompanyEmployee to which both Employee and Company have a one-to-many-relationship. The latter approach is probably preferable as you can then record additional information about the association e.g. start_date, end-date etc.
#Entity
#Table(name = "company_employees")
public class CompanyEmployee {
#ManyToOne
private Employee employee;
#ManyToOne
private Company company;
private Date startDate;
private Date endDate;
}

Related

How to make sure country is not added twice in mysql database in hibernate using spring mvc one to many relationship?

I have two entity first one is Student and the other one is an address. It is one too many relations ie one address can have many students. Now I have is a registration page. When I first register a student say with country name united states, it is saved in database giving primary id as 1 to united states and correspondingly gives correct id in student's database. But when I again try to register the next student with different information but the same country in my case united states it gives me a new primary key for the same country. But as this one is, one to many relationships I am thinking if there is anything in hibernate that maps to the same id in address database, therefore, I will only have one value of the united states in the address database. I need to have only a single entry of the united states a database. What is the appropraite way of doing need? Thank you
This one is Address table
#Entity
#Table(name = "tbl_address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "address_id")
private int addressId;
private String country;
#OneToMany(targetEntity = Student.class, mappedBy = "address")
private List<Student> student;
This one is Student table
#Entity
#Table(name = "tbl_student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "student_id")
private int studentId;
#Column(name = "first_Name")
private String firstName;
#Column(name = "second_Name")
private String secondName;
private String email;
#Column(name = "mobile_no")
private float mobileNo;
#DateTimeFormat(pattern = "yyyy-MM-dd")
#Temporal(TemporalType.DATE)
private Date dob;
private String gender;
#ManyToOne(cascade = {CascadeType.MERGE , CascadeType.ALL} )
#JoinColumn(name = "address_id")
private Address address;
}
This one is just the implementation in StudentRepositoryImpl class
#Override
public void saveUserInfo(Student user) {
Session session = HibernateUtil.getSession(sessionFactory);
session.save(user);
}
#Override
public void saveAddressInfo(Address address) {
// TODO Auto-generated method stub
Session session = HibernateUtil.getSession(sessionFactory);
session.save(address);
}
First option. On UI you should have a list of available countries (so I expect you already have populated country table in database). So UI will display to users available country names, but on backend side you will operate with countryId.
Second option. In case on UI you want users insert any string as country name, and if you want to register country on any new provided country name. For that you need to have unique index in country table on country name (it's up to you to decide whether it will be case insensitive or not).
Before saving student entity, you should fetch available country by it's name. if such one exist - use countryId with saving studend. if not exist - create a new country entity, and use generated countryId in studend entity.
Second option is risky in that way users could provide any values for country names, and probably with typo, so the first option is preferable.
Have a separate table for Country (That would be called master data). Then the Address entity would be something like this:
#Entity
#Table(name = "tbl_address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "address_id")
private int addressId;
#ManyToOne
private Country country;
#OneToMany(targetEntity = Student.class, mappedBy = "address")
private List<Student> student;
You can get the list of students doing a join query with Country and Address tables.
Hope this is helpful

spring data - how to make unique constraint with custom logic?

using spring data, I created User 1:N UserDog N:1 Dog relation. Both 1:N relations are unidirectional #ManyToOne with UserDog being the custom relation table.
User entity:
#Entity
public class User {
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
}
Dog entity:
#Entity
public class Dog {
#Id
#GeneratedValue
private long id;
#Column(nullable = false)
private String name;
}
User dog relation table:
#Entity
public class UserDog {
#Id
#GeneratedValue
private long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
#OnDelete(action = OnDeleteAction.CASCADE)
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn
#OnDelete(action = OnDeleteAction.CASCADE)
private Dog dog;
#Column(nullable = false)
private Instant createdOn = Instant.now();
#Column
private Instant disabledOn;
}
Use case
Use case is to store history of User-Dog bindings, where the concrete Dog can be bound only to one User at the time. That's why I added createdOn and disabledOn columns to UserDog. disabledOn being null indicates that the relation is active and the Dog can't be assigned another User. If disabledOn is not null, then the record is stored only for evidence purposes and the Dog can be assigned to the same or another User again.
Question
How to ensure that the combination of Dog's id and disabledOn being null is unique in UserDog table?
In pseudo code I want something like this:
#Entity
#UniqueConstraint({#UniqueConstraint(this.dog.id), #NullConstraint(this.disabledOn)})
public class UserDog {...}
You can simply create a unique constraint for dogId and disabledOn.
It does add the limitation that no two relationships may end at the same time but this seems to fit your use case.

Jpa OneToOne shared primary key half works

I have SpringBoot 2.1.3 and Java 8 application. Building DB with JPA I have 3 table in one to one relationship. Suppose the tables is the follows:
#Entity
#Data //lombok
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private Address address;
}
And then:
#Entity
#Data
#Table(name = "address")
public class Address {
#Id
#Column(name = "id")
private Long id;
#OneToOne
#MapsId
private User user;
}
That's works.. and it is the best way to do (this exactly example is taken from documentation).
If I start the application the DB is created and if I tried to add entities all works well. The model created follows:
Now I want to add a Country object to my address Entities (for example) and I modified the Entities as follows:
#Entity
#Data
#Table(name = "address")
public class Address {
#Id
#Column(name = "id")
private Long id;
#OneToOne
#MapsId
private User user;
#OneToOne
#MapsId
private Country country;
}
And Country Entities:
#Entity
#Data
#Table(name = "country")
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "country", cascade = CascadeType.ALL)
private Address address;
}
The application still starts, the DB is created and the model follows:
But if I try to save a User as follows:
User user = new User();
Address address = new Address();
Country country = new Country();
user.setAddress(address);
address.setUser(user);
address.setCountry(country);
country.setAddress(address);
userRepository.save(user);
I obtain the error:
java.sql.SQLException: Field 'country_id' doesn't have a default value
Anyway I solve the issue removing #MapsId and added #JoinColumn but I would like to understand what's wrong.
P.S.: I'm using MySQL 5.7 with InnoDB dialect (setting on application.properties)
Thanks all
It works only with one #MapsId annotation. Using two is causing that country id is not inserted:
insert into Country (id) values (?)
insert into Users (id) values (?)
insert into Address (user_id) values (?)

Spring boot entities,relationships and repository confusion

I am confused as to how relationships work in entities and what this means with regard to my JPA repository.
I have a class called Loan which stores a list of albums for each loan.
I have a loan repository and an album repository. The album repository is filled with albums when I start the application. The albumId is autogenerated.
When I create a new loan and try to add an album from the repository I get an exception :
Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: detached entity passed to persist: com.library.demo.entity.Album; nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.library.demo.entity.Album
If I create a new loan and add a new album on the fly then it works as expected. During the debug I realised that this is because albumId is null when adding a new album on the fly to the loan, presumably because it adds the album to the repository and generates a new albumId when the loan is created.
My album entity looks like this :
#Entity
public class Album implements Serializable {
private static final long serialVersionUID = 0x63A6DA99AA12AAA8L;
#Column #GeneratedValue(strategy = GenerationType.AUTO) #Id private Integer albumId;
#Column (unique=true) private String barcode;
#Column private String band;
#Column private String title;
#Column private String genre;
#Column private Integer year;
#Column private String artworkFilename;
#Column private Boolean enabled;
#Column private Boolean isLoanable;
#Column private Integer numberOfCopies;
#ManyToOne
private Loan loan;
And my loan looks like this :
public class Loan implements Serializable {
private static final long serialVersionUID = 0x62B6DA99AA12AAA8L;
public void setLoanId(Integer loanId) {
this.loanId = loanId;
}
#Column #GeneratedValue(strategy = GenerationType.AUTO) #Id private Integer loanId;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Album> albums= new ArrayList<>();
#Column private Integer customerId;
#Column private Date dateLoaned;
#Column private Date dateToReturn;
#Column private Boolean expired;
I am also confused as to why the album has to refer back to the loan with a ManyToOne annotation. Why does the album have to refer to the loan?
I am mostly used to relation databases so maybe I am thinking about things in the wrong way. If I can only add new albums to the loan then it defeats the purpose of what I am trying to do.
Loan and Album tables have a one-to-many relationship.
So in Album your mapping should be like below:
#ManyToOne
#JoinColumn(name = "loan_id")
private Loan loan;
Considering loan_id is the primary key of Loan.
And in Loan your mapping should be like below:
#OneToMany(mappedBy = "loan", cascade = CascadeType.ALL)
private List<Album> albums;
#OneToMany and #ManyToOne defines a one-to-many and many-to-one relationship between 2 entities. #JoinColumn indicates the entity is the owner of the relationship: the corresponding table has a column with a foreign key to the referenced table. mappedBy indicates the entity is the inverse of the relationship
I have updated my source as you have described and made some changes to the start up.
If I create the loan first and then save it to the loan repository and add the album set afterwards, it no longer crashes.
Loan loan = new Loan(1, new Date(), calendar.getTime(),null);
loanRepository.save(loan);
List<Album> albumList = AlbumImport.getAlbumList();
albumRepository.save(albumList);
List<Album> albums = new ArrayList<>();
albums.add(albumList.get(1));
albums.add(albumList.get(5));
loan.setAlbums(albums);
However, when I run my getLoan test the album list is empty
Removing cascade = CascadeType.ALL fixed my problem.

2 Foreign Keys Into a New Table from Different Entities Hibernate

In my projecet people has role based access.One person can work at more than one departments.
My Role Table
Role_id Role
1 Manager
2 Employee
My Department Table
Departmant_id Departmant
1 Production
2 Research
3 Marketing
My User Table
User_id User_name
1 Jennifer
2 Kate
3 David
What i want is a new table that specifies which people are in which departmant and what role do they have in that department.
User_id Departmant_id Role_id
x x x
What i tried is
Class User{
#ManyToOne(cascade = CascadeType.ALL)
#JoinTable(name = "user_department_role",joinColumns = {#JoinColumn(name = "department_id",referencedColumnName = "department_id"),#JoinColumn(name = "user_id",referencedColumnName = "user_id")}, inverseJoinColumns = {#JoinColumn(name = "role_id")})
private Set<Department> departmentList;
}
You need an association table, often constructed in JPA for various reasons mostly to do with control over what goes in the table or in this case mapping an n-way M:N relationship.
Create all your Entities:
#Entity
public class User {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String userName;
#OneToMany(mappedBy="user")
private Set<UserDepartmentRoleAssociation> associations;
... etc
}
and
#Entity
public class Department {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String department;
#OneToMany(mappedBy="department")
private Set<UserDepartmentRoleAssociation> associations;
... etc
}
and
#Entity
public class Role {
#Id #GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
private String role;
... etc
}
and create your association table and id class.
#Entity
public class UserDepartmentRoleAssociation {
#EmbeddedId private UserDepartmentRoleAssociationId id;
#ManyToOne #MapsId("userId")
private User user;
#ManyToOne #MapsId("departmentId")
private Department department;
#ManyToOne #MapsId("roleId")
private Role role;
public UserDepartmentRoleAssociation() {
id = new UserDepartmentRoleAssociationId();
}
... etc
}
and
#Embeddable
public class UserDepartmentRoleAssociationId implements Serializable {
private Integer userId;
private Integer departmentId;
private Integer roleId;
... etc
}
and to persist a relationship then ...
User user = new User();
user.setUserName("user1");
Department department = new Department();
department.setDepartment("department 1");
Role role = new Role();
role.setRole("Manager");
UserDepartmentRoleAssociation association = new UserDepartmentRoleAssociation();
association.setUser(user);
association.setDepartment(department);
association.setRole(role);
em.persist(user);
em.persist(department);
em.persist(role);
em.persist(association);
and to read it with join fetch then
User user = em.createQuery("select u from User u left join fetch u.associations ass left join fetch ass.department left join fetch ass.role where u.id = :id", User.class).setParameter("id", 1).getSingleResult();
Note that I have used a Set instead of a List in Department and User which causes much less problems in these cases. Also, I don't have to create associations when I persist the relationship because the UserDepartmentRoleAssociation is the owning entity and therefore does the persisting. The associations sets are created by JPA when it reads a record.

Resources