I have two entities: Person and Passport. I map it as bidirectional.
Passport (parent):
#Entity
#Table(name = "passports")
public class Passport {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private Integer serial;
private Integer number;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "person_id", referencedColumnName = "id")
private Person person;
Person (child):
#Entity
#Table(name = "people")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
#OneToOne(mappedBy = "person", fetch = FetchType.LAZY)
private Passport passport;
Both relations fetch types are LAZY.
Now I'm trying to find only child:
Person p = entityManager.find(Person.class, 1);
System.out.println(p.getName());
Hibernate generates two SELECT statements instead of one (must select only person).
Hibernate: select p1_0.id,p1_0.name from people p1_0 where p1_0.id=?
Hibernate: select p1_0.id,p1_0.number,p1_0.person_id,p1_0.serial from passports p1_0 where p1_0.person_id=?
I expect only one select, but get two.
Why doesnt LAZY work?
What am I doing wrong?
For all other relation types, FetchType.LAZY will work as expected, but for #OneToOne it is different.
It would work if you change your Person entity such that you don't have relation to Passport, and in Passport have:
#OneToOne(fetch = FetchType.LAZY)
#MapsId
private Person person;
Then if you are using Spring JPA and you want to find a Passport for a Person, you can do passportRepository.findByPerson(person).
Or if you are using entityManager, you can find it as:
entityManager.find(Passport.class, person.getId());
For more explanation, please read https://thorben-janssen.com/hibernate-tip-lazy-loading-one-to-one/.
Related
I am new in Hibernate and just look at several examples and started to make some practice.
Here in the example, there are 3 entities which have relations e.g. #ManyToMany, #OneToMany and #ManyToOne.
Student:
#Entity
#Data
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
private String name;
#JsonIgnore
#ManyToMany(mappedBy = "students")
private Set<Subject> subjects = new HashSet<>();
}
Subject:
#Entity
#Data
public class Subject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
private String name;
#ManyToMany
#JoinTable(
name = "subject_student",
joinColumns = #JoinColumn(name = "subject_id"),
inverseJoinColumns = #JoinColumn(name = "student_id")
)
Set<Student> students = new HashSet<>();
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "teacher_id", referencedColumnName = "id")
private Teacher teacher;
}
Teacher:
#Entity
#Data
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#JsonIgnore
#OneToMany(mappedBy = "teacher")
private Set<Subject> subjects;
}
My questions are:
1. In the subject entity, I tried to remove #JoinColumn and the related entities are connected as the example above:
#ManyToMany
#JoinTable(name="subject_student")
public Set<Student> students = new HashSet<>();
#ManyToOne(cascade = CascadeType.ALL)
private Teacher teacher;
So, if we want to use subject_id - student_id pair in subject_student table and use teacher_id in subject table as it is created in the example, can I use my simplified notation by removing #JoinColumn? Because, if there is not a special case, I think it is redundant to verbose notation of relations.
2. When I use the second approach, the columns are created as plural e.g. subjects_id - students_id in subject_student. So, can I prevent this and create them as in the previous example by using my approach?
I have defined the following two classes in hibernate
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
}
#Entity
public class PhoneNumber {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(cascade = CascadeType.ALL)
private Person person;
}
When I persist a phone number object or a person object it's getting inserted properly.
But when I do
Person person = session.get(Person.class,1);
session.remove(person);
transaction.commit();
I get the foreign key violation exception. But since I have declared a column as ManyToOne shouldn't hibernate automatically delete the corresponding phonnumber records?
I am not sure if I need to add any extra code to do that
You have a bi-directional relationship, that is why you have to add the PhoneNumbers in your Person too. And use the mappedBy attribute to show that the Person is the inverse side and whenever it is deleted, please delete every phone number also.
Like this:
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
#OneToMany(mappedBy="person", cascade = CascadeType.ALL)
private Set<PhoneNumber> phoneNumbers;
}
Check this for more information.
First of all, you have to define the relationship between Person and PhoneNumber in both classes.
The error is thrown because there are phone numbers depending on the person object you removed.
If you add #OneToMany you can define also the property cascade = CascadeType.ALL and then all phone numbers are removed consequently.
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
#OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
private List<PhoneNumber> phoneNumbers;
}
#Entity
public class PhoneNumber {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne
private Person person;
}
I'm struggling with inserting #OneToMany entities in the JPA-Hibernate setup.
There are two associated tables with one of the table having the foreign key as the primary key of the source table.
employee
- id (PK)
employee_location
- employee_id (FK to employee)
Here are my entities:
Employee
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue()
private Long id;
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id")
private List<EmployeeLocation> employeeLocations;
}
Employee Location
#Entity(name = "employee_location")
class EmployeeLocation {
#Id
#Column(name = "employee_id")
private Long employeeId;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id", insertable = false, updatable = false)
private Employee employee;
}
Saving the entities:
List<EmployeeLocation> locations = Arrays.asList(new EmployeeLocation(), new EmployeeLocation());
Employee employee = new Employee();
employee.setLocations(locations);
employee.save(); // Throws exceptions
Which throws me this error:
org.springframework.orm.jpa.JpaSystemException: ids for this class must be manually assigned before calling save():
I tried changing #Entity to #Embeddable and removed the #Id on EmployeeLocation, but it gave me other Unmapped entity exceptions.
How do I handle inserting/updating #OneToMany entities? Is this possible?
How do I handle inserting/updating #OneToMany entities? Is this possible?
If you want the DB to generate the primary key values for you, you need to ask for it by using the #GeneratedValue annotation
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue // mean -> "Hey, DB, give me an ID"!
private Long id;
Same applies for EmployeeLocation
More details can be found here
If this does not fully solve your problem, leave a comment.
In your EmployeeLocation entity (detail) you cannot have as primary key the master's primary key, it needs its own. As follows:
#Entity(name = "employee_location")
class EmployeeLocation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "employee_location_id")
private Integer id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "employee_id", referencedColumnName = "id", insertable = false, updatable = false)
private Employee employee;
}
In the case of Employee entity having a OneToOne relationship with EmployeeLocation entity you can use just #MapsId. This way, the EmployeeLocation id property is populated with the identifier of the post association.
class EmployeeLocation {
#Id
#Column(name = "id")
private Long id;
#OneToOne
#MapsId
private Employee employee;
}
but since your Employee entity has an OneToMany relationship with EmployeeLocation, Employee id property value can't be used as EmployeeLocation id property value because two or more EmployeeLocation entities asociated to the same Employee entity will have the same id value.
You'll need something like this:
#Entity
public class EmployeeLocation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#ManyToOne Employee employee;
}
In both cases you need to bind the EmployeeLocation entity to Employee entity, for example:
class Employee {
....
public void addLocation(EmployeeLocation location) {
location.setEmployee(this);
this.employeeLocations.add(location);
}
public void setLocations(List<EmployeeLocation> locations) {
for (EmployeeLocation location : locations) {
location.setEmployee(this);
}
this.employeeLocations = locations;
}
....
}
ANOTHER OPTION: Using ElementCollection
#Entity(name = "employee")
class Employee {
#Id
#Column(name = "id")
#GeneratedValue
private Long id;
#ElementCollection
#CollectionTable(
name="employee_location",
joinColumns=#JoinColumn(name="EMPLOYEE_ID"))
private Set<EmployeeLocation> employeeLocations;
}
#Embeddable
class EmployeeLocation {
// properties
}
I am trying to stop my relationship making new tables. I have tried multiple approaches to this problem, but there seems to be an error every way I turn. For instance when I try the following code:
//other variables
#OneToMany(fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<User> users= new ArrayList<>();
I get the following error:
Caused by: java.sql.SQLIntegrityConstraintViolationException: Cannot add or update a child row: a foreign key constraint fails (`eb322`.`#sql-3140_2e7`, CONSTRAINT `FK20sqpkpotyyf5wx4jfmp519lu` FOREIGN KEY (`user_id`) REFERENCES `year` (`year_id`))
I have checked all my tables and indexes in the database and I cannot find this constraint anywhere. How do I go about removing it. I basically want to have my schema be like this:
Year will have a list of all students, teachers. When a student is enrolled they will be added to that year etc.
If I don't add the join Column I simply get another table saying
Year.students
How do I combine these together.
This is my student class just incase there's something wrong here:
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int User_id;
}
How I am adding data to year table
//get data about student
Student s = ssrepo.findByName(name);
Year y = yyrepo.findByYear(year);
List<Student> students = y.getStudents();
students.add(s);
yyrepo.save(y)
You seem to be using Unidirectional OneToMany relationship
Hibernate uses an association table to map the relationship so when you remove #JoinColumn annotation an association table is created.
As Year has one to many relationship with student, the type of the List should be List<Student> instead of List<User>
#OneToMany(fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private List<Student> users= new ArrayList<>();
And using OneToMany Unidirectional association is normally not recommended because of its performance issues. You can consider using bidirectional association. It would be something as follows
public class Year {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "YEAR_ID")
private Long id;
#Column(name = "TYPE_ID")
private Long typeId
#Column(name = "TYPE")
private Boolean type // 1 or 0 to know if typeId is of student or teacher
#Column(name = "YEAR")
private Date year
#OneToMany(mappedBy="typeId", fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
private List<Student> students;
#OneToMany(mappedBy="typeId", fetch = FetchType.LAZY ,cascade = CascadeType.ALL)
private List<Teacher> teachers;
}
public class Teacher{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "TEACHER_ID")
private Long id;
#ManyToOne
#JoinColumn(name="TYPE_ID", nullable=false)
private Year typeId;
}
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "STUDENT_ID")
private Long id;
#ManyToOne
#JoinColumn(name="TYPE_ID", nullable=false)
private Year typeId;
}
There are two ways to do this. The first is bidirectional. Where you do the mapping in the two entities. here in this link.(https://dzone.com/articles/introduction-to-spring-data-jpa-part-4-bidirection)
hava exemples.
public class MyClass {
#OneToMany(mappedBy = "myClass", fetch = FetchType.LAZY,
cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "user_id")
private List<User> users;
}
mappedBy is to say who is the dominate in the relationship. In this case, MyClass has the strongest relationship.
public class Student{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private MyClass myClass;
}
I believe that this is the best way, because her realities are apparent in both entities. There is a way to do it in a unidirectional way. Exemple in link (How to define unidirectional OneToMany relationship in JPA)
I have three entities. The first one is Company entity (see below).
#Entity
public class Company {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column
private String name;
#JoinColumn(name = "company_id")
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<Employee> employees;
#OneToMany(mappedBy = "company")
private List<HistoryRecord> historyRecords;
The second is Employee
#Entity
public class Employee {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Integer id;
#Column
String name;
#ManyToOne
#JoinColumn(name = "company_id", nullable = true)
private Company company;
#OneToMany(mappedBy = "employee")
private List<HistoryRecord> historyRecords;
Here is my HistoryRecord class
#Entity
public class HistoryRecord {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Integer id;
#ManyToOne
#JoinColumn(name = "company_id")
Employee employee;
#ManyToOne
#JoinColumn(name = "employee_id")
Company company;
#Column(name = "hire_date")
Date hireDate;
#Column(name = "resign_date")
Date resignDate;
When I'm trying to execute delete operation on Employee I'm getting this error
HTTP Status 500 - Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: Could not execute JDBC batch update; SQL [delete from employee where id=?]; constraint ["CONSTRAINT_12: PUBLIC.HISTORY_RECORD FOREIGN KEY(EMPLOYEE_ID) REFERENCES PUBLIC.EMPLOYEE(ID)
I think the problem is in cascade operation but I'm not sure. Is anybody can say how can I fix it?
The problem is due to the relationship of Employee -- HistoryRecord. The employee property on HistoryRecord is not nullable. If you want the HistoryRecord to be deleted when an employee is being deleted you need to add the cascade attribute to the #OneToMany(mappedBy = "employee") for historyRecords on Employee.
#OneToMany(mappedBy = "employee",cascade = CascadeType.REMOVE)
The ENDDM generates
ALTER TABLE "public"."project_group" ADD CONSTRAINT "mandant" FOREIGN KEY (mandant_id) REFERENCES "mandant" ("mandant_id") ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED;
for the database and
#OneToMany(cascade = CascadeType.REMOVE)
in Java.
Update your relation mapping in Company class is is missing cascade.
#OneToMany(mappedBy = "company", cascade = CascadeType.ALL)
private List<HistoryRecord> historyRecords;