One-to-Many Uni-Directional
Pada materi One-to-Many sebelumnya, yang kita gunakan adalah Bi-directional (dua arah). Pada materi ini kita akan membahas One-to-Many Uni-directional (satu arah) menggunakan studi kasus Student dan Grade (Nilai).

📌 Konsep Dasar
Dalam relasi Uni-directional (Satu Arah), hanya satu entity yang memiliki referensi ke entity lain.
- Studi Kasus: Satu Siswa (
Student) memiliki banyak Nilai (Grade). - Logika: Objek
Studentakan memiliki daftarGrade, namun objekGradetidak tahu siapa pemiliknya (tidak ada fieldStudentdi dalam classGrade).

Baca materi: Uni-Directional vs Bi-Directional
📅 Struktur Tabel (Database)
Secara fisik di database, Hibernate akan menempatkan Foreign Key pada tabel grade.
🚀 Implementasi
1️⃣ Entity Child: Grade.java
Sisi ini bersifat independen dan tidak memiliki referensi ke Student.
@Entity
@Table(name = "grade")
public class Grade {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "subject_name")
private String subjectName;
@Column(name = "score")
private Double score;
public Grade() { }
public Grade(String subjectName, Double score) {
this.subjectName = subjectName;
this.score = score;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getSubjectName() {
return subjectName;
}
public void setSubjectName(String subjectName) {
this.subjectName = subjectName;
}
public Double getScore() {
return score;
}
public void setScore(Double score) {
this.score = score;
}
@Override
public String toString() {
return "Grade [id=" + id + ", subjectName=" + subjectName + ", score=" + score + "]";
}
}
2️⃣ Entity Parent: Student.java
Sisi ini yang mengelola relasi menggunakan @JoinColumn.
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "first_name")
private String firstName;
@Column(name = "last_name")
private String lastName;
@ManyToOne(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST, CascadeType.REFRESH})
@JoinColumn(name = "class_id")
private Classes classes;
// Relasi Satu Arah (Uni-directional)
@OneToMany(
fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
@JoinColumn(name = "student_id") // FK yang akan dibuat di tabel 'grade'
private List<Grade> grades = new ArrayList<>();
public Student() {
}
public Student(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Classes getClasses() {
return classes;
}
public void setClasses(Classes classes) {
this.classes = classes;
}
public List<Grade> getGrades() {
return grades;
}
public void setGrades(List<Grade> grades) {
this.grades = grades;
}
// Helper method untuk menambah data Grade
public void addGrade(Grade grade) {
if (grades == null) {
grades = new ArrayList<>();
}
grades.add(grade);
}
@Override
public String toString() {
return "Student [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + "]";
}
}
Dalam relasi One-to-Many, aturan utamanya adalah: @JoinColumn selalu diletakkan di sisi "Many" (tabel yang memiliki kolom Foreign Key di database), tapi dalam kasus relasi satu arah(Uni-Directional), karena dalam table Grade bersifat independen maka @JoinColum digunakan pada Student (yang mengelola relasi).
Tambahkan Orphan Removal: Fitur orphanRemoval = true sangat direkomendasikan. Jika seorang siswa(Student) dikeluarkan dari daftar nilai(Grade), baris nilainya di database akan langsung terhapus secara otomatis.
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
3️⃣ Implementasi DAO CRUD
- Pada contoh kasus ini kita akan membuat DAO umtuk operasi CRUD:
- 💾 Create Student sekaligus dengan Grade-nya.
- 🔎 Find data Student sekaligus dengan data Grade terkait.
- ❌ Menghapus data Student sekaligus dengan data Grade terkait.
public interface SchoolDAO {
void saveStudentWithGrade(Student student);
Student findStudentAndGradeByStudentId(Integer id);
void deleteStudentById(Integer id);
}
- Implementasi DAO:
@Repository
public class SchoolDAOImpl implements SchoolDAO {
private EntityManager entityManager;
public SchoolDAOImpl(EntityManager entityManager) {
this.entityManager = entityManager;
}
@Override
@Transactional
public void saveStudentWithGrade(Student student) {
entityManager.persist(student);
}
@Override
@Transactional(readOnly = true)
public Student findStudentAndGradeByStudentId(Integer id) {
TypedQuery<Student> query = entityManager.createQuery(
"SELECT s FROM Student s "
+ "JOIN FETCH s.grades "
+ "WHERE s.id = :data", Student.class);
query.setParameter("data", id);
Student student = query.getSingleResult();
return student;
}
@Override
@Transactional
public void deleteStudentById(Integer id) {
entityManager.remove(entityManager.find(Student.class, id));
}
}
▶️ Main Class
Kita akan menghapus data Classes dengan id 5, dan data pada Student terkait tidak ikut terhapus.
- Pada main class application kita akan menyimpan data
Studentsekaligus denganGrade:
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
// Bean ini akan dieksekusi di command line
@Bean
public CommandLineRunner commandLineRunner(SchoolDAO dao) {
return runner -> {
createStudentWithGrade(dao);
findStudentAndGrade(dao);
deleteStudentAndGrade(dao);
};
}
private void createStudentWithGrade(SchoolDAO dao) {
Student student = new Student("Shino", "Aburame");
student.addGrade(new Grade("Matematika", 80.5));
student.addGrade(new Grade("Pendidikan Jasmani", 87.5));
student.addGrade(new Grade("Biologi", 90.2));
dao.saveStudentWithGrade(student);
System.out.println("Saved Student");
System.out.println("Student: " + student);
System.out.println("Grade: " + student.getGrades());
}
private void findStudentAndGrade(SchoolDAO dao) {
int id = 4;
Student student = dao.findStudentAndGradeByStudentId(id);
System.out.println("Get Student= " + student);
System.out.println("Get Grade Student= " + student.getGrades());
}
private void deleteStudentAndGrade(SchoolDAO dao) {
int id = 4;
dao.deleteStudentById(id);
System.out.println("Delete Student by id = " + id);
}
}