Spring data JPA save and updating parent entity - java

I have two entities called Student and Subject. They are stored in tables in the following format
student_id
name
grade
1
John
1
subject_id
name
1
English
2
Math
subject_id
student_id
mark
1
1
75
2
1
75
**Student:**
#Table(name = "student")
#Data
public class Student {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "grade")
private int grade;
//getters and setters left out for this example
}
**Subject:**
#Table(name = "subject")
#Data
public class Subject {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(name = "name")
private String name;
//getters and setters left out for this example
}
**StudentRepository:**
public interface StudentRepository extends JpaRepository<Student, Long> {
}
How do I make it so that everytime I add a student using a StudentController, the subjects are automatically added to the student.

Create the third entity for the third table, create the student object and the subject object . put it in the third entity object, create the third repository and save that, all three tables will be updated together. Just make sure your relationships are correctly mentioned and you are done.

Update your Student entity to encapsulate Subject.
The idea is to explicitly define relationship between Student and Subject , and further leverag Cascade to propagate changes :
#Table(name = "student")
#Data
public class Student {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Column(name = "grade")
private int grade;
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.REMOVE, CascadeType.MERGE}, orphanRemoval = true)
#JoinColumn(name = "SUBJECT_ID")
Subject subject;
}
Note : You need to make sure that you populate Subject when storing Student.
For more clarity , explore the examples presented here : https://vladmihalcea.com/a-beginners-guide-to-jpa-and-hibernate-cascade-types/

Related

Many to one relationship with a condition

I have two tables, student and teacher, with a relationship ManyToOne. The table structure is as follows
student(
id long,
student_id string,
....
teacher_id string,
active boolean
)
teacher(
id long,
teacher_id string,
....
active boolean
)
I'm using Spring boot and Hibernate. Here when updating an entity, the active column of the existing row in the table will be set to false and a new row will be added with a new id(long) and active as true. That is why there are two id values in each table. The problem here is I have specified the student-teacher relation as many to one in my entity with the foreign key as teacher_id.
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "student_id")
private String studentId;
#ManyToOne
#JoinColumn(name = "teacher_id", referencedColumnName = "teacher_id")
private Teacher teacher;
#Column(name = "active")
#JsonIgnore
private Boolean active = true;
}
#Entity
#Table(name = "teacher")
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "teacher_id")
private String teacherId;
#OneToMany(mappedBy = "teacher")
private Set<Student> students = new HashSet<>();
#Column(name = "active")
#JsonIgnore
private Boolean active = true;
}
But since multiple teachers can occur with the same teacher_id, this fails. Is there any way to give a condition to the relationship to fetch the teacher with active as true? In table, there will be only one teacher with the given id and active as true.
I just came across your post. I had the same requirement few months ago and this is what i did..
public class Student {
#Column(name = "teacher_id ")
private String teacherId;
#ManyToOne
#JoinFormula(value = "(Select t.id from teacher t where t.teacher_id= teacher_id and t.active=1)"
)
private Teacher teacher;
}
#Entity
#Table(name = "teacher")
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "active")
#JsonIgnore
private Boolean active = true;
}
This approach works for my case. If there is a better one you can share it.
Thanks
There should be no teacher_id foreign key field in the Teacher entity. Rather, just use the primary key id column instead. Consider this version of your entities:
#Entity
#Table(name = "student")
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "student_id")
private String studentId;
#ManyToOne
#JoinColumn(name = "teacher_id", referencedColumnName = "id")
private Teacher teacher;
#Column(name = "active")
#JsonIgnore
private Boolean active = true;
}
#Entity
#Table(name = "teacher")
public class Teacher {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "teacher")
private Set<Student> students = new HashSet<>();
#Column(name = "active")
#JsonIgnore
private Boolean active = true;
}
This design should enforce that a given student can be associated with only one teacher (though a given teacher can have multiple students).

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

Spring boot: JPA incorrectly adds where clause

I have three entity classes; Student, Subject and StudentSubject.
Student has one to many relation on StudentSubject, and Subject also has one to many relation on StudentSubject.
Student class
#Entity
#Getter
#Setter
public class Student {
#Id
private String email;
#OneToMany(mappedBy = "student")
#JsonManagedReference
private List<StudentSubject> subjects;
//more elements
}
Subject class
#Entity
#Getter
#Setter
public class Subject {
#Id
#GeneratedValue
private Long id;
#ManyToOne
#JsonBackReference
private Teacher teacher;
#OneToMany(mappedBy = "student")
#JsonManagedReference
private List<StudentSubject> students;
//more elements
}
StudentSubject class
#Entity
#IdClass(StudentSubjectId.class)
#Getter
#Setter
public class StudentSubject implements Serializable {
//Primary keys
#Id
#Column(name = "subject_id")
Long subjectId;
#Id
#Column(name = "student_email")
String studentEmail;
String uid;
#ManyToOne
#JsonBackReference
#JoinColumn(name = "subject_id", insertable = false, updatable = false)
private Subject subject;
#ManyToOne
#JsonBackReference
#JoinColumn(name = "student_email", insertable = false, updatable = false)
private Student student;
}
I have 3 classes, and not 2, because there are attributes specific to each student subject pair. Hence this arrangement.
When I read a subject from repository, as such
Subject subject = subjectRepository.findByNameAndTeacher(subjectName, teacher);
subject.getStudents();
all it's details are correct, except for list of students. It is always empty.(checked this by adding breakpoint)
The queries that are executed by Hibernate/JPA are,
To get subject(?)
select
subject0_.id as id1_3_,
subject0_.name as name2_3_,
subject0_.teacher_email as teacher_3_3_
from
subject subject0_
left outer join
teacher teacher1_
on subject0_.teacher_email = teacher1_.email
where
subject0_.name =?
and teacher1_.email =?
To select student list(?)
select
students0_.student_email as student_1_2_0_,
students0_.subject_id as subject_2_2_0_,
students0_.student_email as student_1_2_1_,
students0_.subject_id as subject_2_2_1_,
students0_.uid as uid3_2_1_,
subject1_.id as id1_3_2_,
subject1_.name as name2_3_2_,
subject1_.teacher_email as teacher_3_3_2_,
teacher2_.email as email1_5_3_,
teacher2_.name as name2_5_3_
from
student_subject students0_
left outer join
subject subject1_
on students0_.subject_id = subject1_.id
left outer join
teacher teacher2_
on subject1_.teacher_email = teacher2_.email
where
students0_.student_email =?
and some more.
I think the issue here is that the last where clause is incorrectly added, and common attributes in tables are not shown once. How do I fix this?
Your mapping has a typo. In Subject class, it should be #OneToMany(mappedBy = "subject") instead of mappedBy="student" hence your wrong where clause.
This is the reason it is using
where students0_.student_email =?
instead of
where students0_.subject_id =? as it thinks the way to get to students from subject is through student_email column as indicated by your mapping.
You have not specified fetch type. This should fix it.
#OneToMany(mappedBy = "student", fetch = FetchType.EAGER)
#JsonManagedReference
private List<StudentSubject> students;

How to force a zero to one relationship?

My requirement is to have a list of classes and students of each class. Each students must be in zero or at most one class. My code is as following, but in database, each student can be in many classes. How to keep each student in zero to one and only one class?
Student item table is as following
StudentItem
id student_id code
1 1 233
2 5 453
3 1 567
4 6 565
Entities
#Entity
public class MyClass{
#Id
#GeneratedValue
private long id;
#OneToMany( cascade = CascadeType.ALL)
#LazyCollection(LazyCollectionOption.FALSE)
private List<StudentItem> students;
private String season;
...
}
#Entity
public class StudentItem{
#Id
#GeneratedValue
private long id;
#OneToOne
private Student student;
private String code;
...
}
#Entity
public class Student{
#Id
#GeneratedValue
private long id;
private String fname;
private String lname;
....
}
It seems to me that it's a many-to-one relationship between MyClass and StudentItem, with MyClass owning the relationship between them. Further, it may be simpler and more straightforward to use a join table between these two entities; this way, you don't run the risk of creating more than one MyClass entry in your database.
Think of it like this: what is the actual effective relationship between a class and a student? From your description, one class may hold many students, but any given student may only be in one class.
Here's a rough-hand example from memory on how to create it. I'll assume that there is a joining table between StudentItem and MyClass called classroom_students, with columns student_item_id and class_id.
#Entity
public class MyClass {
#Id
#GeneratedValue
private Integer id;
#OneToMany
#JoinTable(name = "classroom_students",
joinColumns = #JoinColumn(name = "student_item_id"),
inverseJoinColumns = #JoinColumn(name = "class_id"))
private List<MyClass> students;
// getters and setters for entity
}
#Entity
public class StudentItem {
#Id
#GeneratedValue
private Integer id;
#OneToOne(mappedBy = "student_id")
private Student student;
#ManyToOne(targetEntity = MyClass.class,
mappedBy = "students")
private MyClass myClass;
}

Categories