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

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.

Related

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 configure for a (Optional) OneToOne Composite Primary Key?

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!)

Java/Hibernate/JPA: cannot persist with compound key -> transient object

my problem is that I cannot save my entity because it contains another entity, mapped by a key that is also a part of this table's primary key. The table looks like this:
table C:
+-----+------+
| id_A | id_B |
+-----+------+
..where idA is the primary key of table A with EntityA and idB the primary key of table B with EntityB.
so its basically a n-to-m relation. This is the entity I'm using for table C:
#Entity
public class EntityC {
private long idA;
private EntityB b;
#Id
#Column(name = "id_A")
public long getIdA() {
return idA;
}
#Id
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_B")
public EntityB getB() {
return b;
}
...setters are here...
}
Please note that id_A is mapped as is (the id), while id_B is mapped as its object representation, EntityB. This is what I want to do with it:
EntityC c = new EntityC();
c.setIdA(123);
c.setB(new EntityB());
em.persist(c);
tx.commit();
em.close();
I want to persist EntityB ONLY IF I can persist EntityC.
on tx.commit() I get this exception: org.hibernate.TransientObjectException: object references an unsaved transient instance
I suppose this happens because part of the primary key, id_B, is not saved. But i set cascading to all so there should be no problem!
Why is this not working?
EDIT:
When I do this:
em.persist(c.getB());
em.persist(c);
it works. But can't Hibernate/JPA do that automatically? I thought that's what cascading is good for.
EDIT2:
added an embeddedId instead of id_A and id_B:
#Embeddable
public class EntityCID implements Serializable {
public long idA;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "id_B", referencedColumnName = "id")
public EntryB b;
}
EntityC now looks like:
#Entity
public class EntityC implements Serializable {
private EntityCID id;
...
#EmbeddedId
public void getId() {
return id;
}
}
but I still get the transient object exception if I don't em.persist(c.getId().b); before em.persist(c). Sticking to that, although it is ugly.
#Trein: it is not bidirectional. EntityB code:
#Entity
public class EntityB implements Serializable {
public long id;
public String text;
}
If you think about it what you are seeing makes perfect sense.
EntityC is is the 'owning side' of the relationship C<>B: it defines the JoinColumn and EntityB has the 'mappedBy' attribute.
So on saving C, order of events would normally be:
insert into C/update C
insert into B/update B
Now in your case this causes issues as obviously C can only be saved if B has been persisted first.
In terms of your statement above: I want to persist "EntityB ONLY IF I can persist EntityC." How can this ever be the case?
JPA has a concept of 'Derived Identifiers', which I am not overly familiar with however is defined in the book Pro JPA as occurring when:
When an identifier in one entity includes a foreign key to another
entity, we call it a derived identifier. Because the entity containing
the derived identifier depends upon another entity for its identity,
we call the first the dependent entity. The entity that it depends
upon is the target of a many-to-one or one-toone relationship from the
dependent entity, and is called the parent entity
Now, despite the original advice that you had two #Id attributes defined and this was wrong it would however appear that having an additional #Id on a 1-2-m is in fact valid in JPA 2 for precisely this case.
The book gives a number of ways of dealing with Derived Identifiers however one example given below looks fairly similar to your case. So you may want to investigate further the #MapsId attribute.
#Entity
public class Project {
#EmbeddedId private ProjectId id;
#MapsId("dept")
#ManyToOne
#JoinColumns({
#JoinColumn(name="DEPT_NUM", referencedColumnName="NUM"),
#JoinColumn(name="DEPT_CTRY", referencedColumnName="CTRY")})
private Department department;
// ...
}
#Embeddable
public class ProjectId implements Serializable {
#Column(name="P_NAME")
private String name;
#Embedded
private DeptId dept;
// ...
}
See further:
How do I properly cascade save a one-to-one, bidirectional relationship on primary key in Hibernate 3.6
Is it a bidirectional relationship? I would suggest you to remove #Id getB() and perform the modifications:
#OneToOne(cascade = CascadeType.ALL, mappedBy = "id_B")
#PrimaryKeyJoinColumn(name = "id_B")
public EntityB getB() {
return b;
}
Your entity class must have only one attribute annotated with #Id. Usually when you need this, you create a class that will store both properties and this will act as a Id Class.
You can not pass new Entity() for reference. Because it won't have any values in it(even primary key). So how can hibernate will insert it as foreign key to the table. And cascade will save your parent object if its not saved,no need to call save method for all. But when you passing new object it won't do.

Many-to-one unidirectional relation in DataNucleus

For the context, client-side I use the MVP pattern, so the view with the One list knows only the ID, and when my new Many is received on the server, I want to be able to just update the One's foreign key, with a "setOneId" or an empty One object with an ID set to the wanted value.
So I try to create a many-to-one unidirectional in DataNucleus, and I'm struggling a bit. I'm ok to use JDO or JPA, I don't really care. In JPA, I tried this :
#Entity
public class Many {
#Id
String id;
#ManyToOne
#Join(name = "idOne")
One one;
}
#Entity
public class One {
#Id
String id;
}
It's almost what I want. The one-to-many is created but with a join table. I want to have a direct relation. And when I insert/update a Many, I don't want to insert/update the related One, just update the idOne with the good id in my Many object.
I found this blogpost, but it's with Hibernate, and I think it still use a join table :
#Entity
public class Many {
#Id
public String id;
#Column(name="idOne")
private String idOne;
#ManyToOne
#JoinColumn(name="idOne", nullable=false, insertable=false, updatable=false)
private One one;
}
I tried it, but I got exactly this error.
I don't understand how I am struggling with that. My goal is to have a table that keep some reference data (like a list of country as the class One), and a list of "working item" (like a town as the class Many) that I create/update without create/update the reference data, just its foreign key in the Many object.
If its a unidirectional association, and Many is the owning side (as per your second example), you are heading in the wrong direction. It doesn't make much sense to delegate the update and insert responsibility on the owning side of a unidirectional relationship (as done with the insertable=false and updateable=false).
EDIT: updated answer
So what you want is a many-to-one, with a foreign key column on the owning side. Try this
#Entity
public class Many {
#Id
String id;
#ManyToOne
#JoinColumn(name = "foreignKeyColumn")
One one;
}
#Entity
public class A {
#Id
String id;
#OneToOne(cascade=CascadeType.ALL)
B b;
}
#Entity
public class B {
#Id
String id;
}
and then if you persisted initial objects as
tx.begin();
A a = new A("FirstA");
B b1 = new B("FirstB");
B b2 = new B("SecondB");
a.setB(b1);
em.persist(a);
em.persist(b2);
tx.commit();
... (some time later)
tx.begin();
A a = em.find(A.class, "FirstA");
B b2 = em.getReference(B.class, "SecondB");
// update the B in A to the second one
a.setB(b2);
tx.commit();
This updates the FK between A and B. Can't get simpler

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.

Categories

Resources