How configure for a (Optional) OneToOne Composite Primary Key? - java

I am using Hibernate and have two tables, STUDENTS and DORM_ROOMS, that are related with a composite key:
STUDENTS table:
CAMPUS(String) Part of Composite Key
STUDENT_ID (int) Part of Composite Key
NAME (String)
...
DORM_ROOMS table:
CAMPUS(String) Part of Composite Key
STUDENT_ID (int) Part of Composite Key
ROOM_NUMBER(int)
...
The relationship is one to one because a student can be associated with exactly one dorm room and and a dorm room is associated with one student (wow - a private room!). However, not all students have a dorm room.
My initial code (stripped down) looks like:
FOR STUDENTS:
#Embeddable
public class StudentsPK implements Serializable {
#Column(name = "CAMPUS")
private String Campus;
#Column(name = "STUDENT_ID")
private String StudentID;
...
}
#Entity
#Table(name = "STUDENTS")
public class Students implements Serializable {
#EmbeddedId
private StudentsPK studentsPK;
...
}
FOR DORM_ROOMS:
#Embeddable
public class DormRoomsPK implements Serializable {
#Column(name = "CAMPUS")
private String Campus;
#Column(name = "STUDENT_ID")
private String StudentID;
...
}
#Entity
#Table(name = "DORM_ROOMS")
public class DormRooms implements Serializable {
#EmbeddedId
private DormRoomsPK dormRoomsPK;
...
}
Assume that the database schema is already defined and created. In particular, CAMPUS+STUDENT_ID is a PK for STUDENTS and CAMPUS+STUDENT_ID is a FK for DORM_ROOMS that serves as the PK in that table. At this point I can successfully insert a row into STUDENTS and a row into DORM_ROOMS. I can also retrieve any student from STUDENTS even if the student does not have a dorm room. However, I have not yet "informed" Hibernate about the relationship between the two tables. That is where I am confused.
I tried to "relate" the two tables by using a "JOIN" annotation but I discovered that this causes any attempt to fetch a student that has no dorm room to return an empty result set. I suppose that makes since if "JOIN" states that the tables are to always be viewed as joined then joining a student having no dorm room with no matching rows in the DORM_ROOMS table would result in an empty result set.
Since using a "JOIN" annotation doesn't work, how do I modify my code to describe the relationship between the two tables but still allow me to fetch students that have no matching dorm rooms?
Thank you.

It sounds like you are looking for the #OneToOne annotation, which also has the ability to specify if the relationship is optional. There are some examples described in the JBoss JPA 2.1 docs, here is one of them.
Example 3: One-to-one association from an embeddable class to another entity.
#Entity
public class Employee {
#Id int id;
#Embedded LocationDetails location;
...
}
#Embeddable
public class LocationDetails {
int officeNumber;
#OneToOne ParkingSpot parkingSpot;
...
}
#Entity
public class ParkingSpot {
#Id int id;
String garage;
#OneToOne(mappedBy="location.parkingSpot") Employee assignedTo;
}

Found the problem! I discovered that in a #OneToOne relationship with a composite key, using a separate FK class to manage the composite key in both entities causes the error. The problem is shown in my original posting where I define and use StudentsPK and DormRoomsPK! Once I changed to use a single "PK" class instead of these two my problem was eliminated. (This doesn't appear to be a well documented requirement!)

Related

in Spring jpa using the #ManyToMany relationship, why create a new class with #Embeddable?

According to the Spring JPA documentation, in the Many-To-Many relationship (student - course) we must create a new table (student_course)
class student ---> class student_course <--- class course
According to the documentation, if we want to add a new property to the table (student_course) we must create a new class that will contain the compound keys of the student class and the course class
#Embeddable
class CourseStudentKey implements Serializable {
#Column(name="student_id")
Long studentId;
#Column(name = "course_id")
Long courseId;
}
_ Then to the Student_Course class we assign the id of type CourseStudentKey that contains the compound keys:
#Entity
class StudentCourse {
#EmbeddedId
CourseRatingKey id;
#ManyToOne
#MapsId("studentId")
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#MapsId("courseId")
#JoinColumn(name = "course_id")
Course course;
}
My question is: What is the difference in creating only the StudentCourse class and doing the #ManyToOne mapping to the Student class and the Course class??... in this way we can also add attributes to the StudentCourse class
_Clase Student
#Entity
class Student {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private idStudent;
#JsonIgnore
#OneToMany(mappedBy = "student")
List<StudentCourse> studentCourses = new ArrayList<>();
_Clase Course
#Entity
class Course{
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private idCourse;
#JsonIgnore
#OneToMany(mappedBy = "course")
List<StudentCourse> studentCourses = new ArrayList<>();
}
_Clase StudentCourse
#Entity
class StudentCourse {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private idStudentCourse;
#ManyToOne
#JoinColumn(name = "student_id")
Student student;
#ManyToOne
#JoinColumn(name = "course_id")
Course course;
}
The only difference in the examples posted by you, is, in case of Embeddable, the student_id course_id would be a composite key, so there would only be one row allowed per student_id course_id combination. Whereas, in the second example, you have used generated primary key, ensuring multiple rows for each student_id course_id combination. This would be particularly useful if the student fails the course for the first time and attempts it again. You can then add parameters like attemped_on, is_completed, etc. to the student_course entity
Your examples show differences in the key, and as Chetan's answer states, this affects the key used in the table. The choices here isn't necessarily in using a separate class/embbeded class, but in using a single generated Identifier vs using a composite primary key for the entity.
In the embedded example you've posted, you have a composite primary key based on foreign key mappings. There are many other ways to map this same setup though, but the common parts will be:
composite PKs need an ID class. It doesn't have to be embedded in your class (see JPA derived IDs) but does need to exist. This is part of the JPA spec and allows em.find operations to deal with a single object.
ID values are immutable. They cannot change without remove/persist operations as per the JPA specification. Many providers don't like you even attempting to modify them in an Entity instance. In your embeddable example, you cannot change the references, while in the generated id example, you can.
It also affects what JPA requires you to use in foreign keys. If you use a composite ID, any references to that entity (*ToOne) that require foreign keys to that table are required to use its defined IDs - all columns that make up that ID. Some providers don't enforce this, but it will affect entity caching; since entities are cached on their IDs, using something else as the target of FKs might mean database hits for entities already in the cache.

How to correctly set up #OneToOne relationship with JPA Hibernate?

I'm quite new to this and I am attempting to set up a simple one-to-one relationship between a Student object and their Grades object. I'm using spring boot with the hsqldb in-memory database. I'll show my code first then explain my issues.
Student.java
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
#OneToOne(cascade = CascadeType.ALL)
private Grades grades;
//Getters and setters
}
Grades.java
#Entity
public class Grades {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private int midterm;
private int finalExam;
#OneToOne(cascade = CascadeType.ALL)
private Student student;
//Getters and setters
}
StudentRepository.java
public interface StudentRepository extends JpaRepository<Student, Long> {
}
Is my setup for Student and Grades correct? I just one a simple one-to-one relationship where I store the Grades ID in a Student object as a foreign key.
Whenever I create a Grades object using new Grades() and pass that into a Student object in the constructor, it allows me to assign the same Grades object to multiple students. How is that possible when they're annotated with one-to-one?
I turned on hibernate sql logging to see what is happening when using the database. It seems to store grades_id in a Student object just fine, but it shows student_id in the Grades object as being null. Why is this null?
Thanks for any help.
Is my setup for Student and Grades correct?
It's questionable. Both sides of the relationship are mapped as owners of the relationship. One of them should be mapped as the owner, and other should use mappedBy attribute of the #OneToOne annotation. #OneToOne
Whenever I create a Grades object using new Grades() and pass that
into a Student object in the constructor, it allows me to assign the
same Grades object to multiple students. How is that possible when
they're annotated with one-to-one?
You should create composite primary key or use uniqueness constraint to forbid using the same Grades record by multiple Students.
I turned on hibernate sql logging to see what is happening when using
the database. It seems to store grades_id in a Student object just
fine, but it shows student_id in the Grades object as being null. Why
is this null?
Looks like Hibernate generated both tables with a foreign key column. Only one should have been generated.
You have to specify one side as an owner of the relationship. The other side should use mappedBy attribute of the #OneToOne annotation to tell Hibernate where is the mapping of the relationship.
...
#OneToOne(mappedBy="grades")
private Student student;
...

How can I join 3 entities JPA?

I have 3 entities: Aluno, Turma and Modalidade. Now I need create Matricula, this entity Matricula will contain all ids of Aluno, Turma and Modalidade with others attribute.
one Matricula can have one Aluno and can have many Turma and can have many Modalidade.
Entity Matricula, can have:
OneToOne Aluno
OneToMany Turma
OneToMany Modalidade
I hope can yours understand.
How to I do that ?
I have a tutorial that goes into a fair bit of detail about how you set up various relationships using Hibernate annotations. You can find it here.
I'm going to assume that you'd want bi-directional relationships using a foreign key mapping (as shown in the tutorial, if this is wrong, you can find the uni-directional configurations there), you can basically just declare your classes like this:
#Entity
#Table
public class Matricula {
#Id
private long matriculaId;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "alunoId")
private Aluno aluno;
#OneToMany(mappedBy="turma")
private List<Turma> turmas;
#OneToMany(mappedBy="modalidade")
private List<Modalidade> modalidades;
}
#Entity
#Table
public class Turma {
//Put a unique ID here to be used as PK
#ManyToOne
#JoinColumn(name="matriculaId)
private Matricula matricula;
}
#Entity
#Table
public class Modalidade {
//Put a unique ID here to be used as PK
#ManyToOne
#JoinColumn(name="matriculaId)
private Matricula matricula;
}
#Entity
#Table
public class Aluno {
//Put a unique ID here to be used as PK
#OneToOne(mappedBy="aluno")
private Matricula matricula;
}
Please note that this is assuming that your column names will match, and that your database is correctly set up.
Hope it goes well

Java - A JPQL Query to delete a oneToMany relationship

If I have this 3 entities :
#Entity
public class Student {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
protected Long id;
private String name;
}
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
public class Course {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
protected Long id;
#OneToMany
private List<Student> students;
private String name;
}
#Entity
#Inheritance(strategy=InheritanceType.JOINED)
public class Group {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
protected Long id;
#OneToMany
private List<Student> students;
private String name;
}
How can I delete students with a JPQL query ?
I try
DELETE FROM Student s
WHERE s.name = "John Doe"
But I have
Cannot delete or update a parent row: a foreign key constraint fails (database, CONSTRAINT FK_course_student_students_ID FOREIGN KEY (students_ID) REFERENCES student (ID))
I need to do this in pure JPQL for performance, I can't do an entity.remove, because I have 10000 John doe and I need to delete them in a second.
Why JPQL doesn't say : "Hey, let's remove this john doe from this biology course, he doesn't exist" instead of "Hey, the biology course is so important that no student can be remove from this course ! "
What I am missing and what sort of annotation I have to use ?
Thanks !
Edit : Add a #JoinColumn to the OnToMany relationship could work, unless the students are referenced by different tables...
By default unidirectional one-to-many relationship is mapped via join table. If you don't have any special requirements about using join talbe you can use foreign key in Student instead, it can be configured as follows:
#OneToMany
#JoinColumn
private List<Student> students;
It also should solve your problem with constrain violation.
Sometimes you can clear the references to the objects being deleted in a deleted all using an update all query.
You can also configure you constraint on the database to cascade or null on delete to avoid constraint issues.

Joining tables with composite keys in a legacy system in hibernate

I'm currently trying to create a pair of Hibernate annotated classes to load (read only) from a pair of tables in a legacy system. The legacy system uses a consistent (if somewhat dated) approach to keying tables. The tables I'm attempting to map are as follows:
Customer CustomerAddress
-------------------------- ----------------------------
customerNumber:string (pk) customerNumber:string (pk_1)
name:string sequenceNumber:int (pk_2)
street:string
postalCode:string
I've approached this by creating a CustomerAddress class like this:
#Entity
#Table(name="CustomerAddress")
#IdClass(CustomerAddressKey.class)
public class CustomerAddress {
#Id
#AttributeOverrides({
#AttributeOverride(name = "customerNumber", column = #Column(name="customerNumber")),
#AttributeOverride(name = "sequenceNumber", column = #Column(name="sequenceNumber"))
})
private String customerNumber;
private int sequenceNumber;
private String name;
private String postalCode;
...
}
Where the CustomerAddressKey class is a simple Serializable object with the two key fields. The Customer object is then defined as:
#Entity
#Table(name = "Customer")
public class Customer {
private String customerNumber;
private List<CustomerAddress> addresses = new ArrayList<CustomerAddress>();
private String name;
...
}
So, my question is: how do I express the OneToMany relationship on the Customer table?
I may have an answer for my own question. Add the following to Customer:
#OneToMany(mappedBy="customer")
#JoinColumn(name="customerNumber")
List<CustomerAddress> addresses = new ArrayList<CustomerAddress>();
And the following to CustomerAddress:
#ManyToOne
#JoinColumn(name="customerNumber")
protected Customer customer;
This turns out to be a simpler problem to solve than I first thought. Typical.
I assume you have read the Bauer/King Hibernate book, which is extremely bad at explaining how do implement composite primary keys correctly. Don't let yourself be fooled by a bad book: composite primary key are not a property of legacy systems...

Categories

Resources