MS Lesson11: Entity Manager, Entity relations






0. Ön səhnə: DB, connection, transaction (Step-by-step)

Təsəvvür et ki, DB = kitabxana, application = kitab gətirən maşın, connection = bu kitabxanaya açılan yol, transaction = “bir dəfəyə gətirmə qaydası”.

  1. Connection açılır.
  2. Transaction başlayır: “bu aralıqdakı iş ya hamısı olur, ya da heç biri”.

Əsas qayda:

  • commit = kitablar həqiqətən rəfə gedir.
  • rollback = gətirdin, amma “heç nə olmamış kimi” geri qaytarırsan.


1. EntityManagerFactory nədir? (Step 1)

EntityManagerFactory = “mətbəx maşınlarının fabrikası”.

  • Bir dənə olur (tipik).
  • Sənə hər transaction üçün uyğun yeni EntityManager yaratmağa kömək edir.

Yəni:

  • EMF = fabrika
  • EM = mətbəx (işin içində baş verir)


2. EntityManager nədir? (Step 2)

EntityManager = “iş vərəqi / kassa”.

Sən JPA əmrlərini verəndə (find/persist/merge/remove/refresh/detach) “bu vərəq” içində baş verir.

Bu vərəqin ən önəmli hissəsi var:

3. Persistence Context nədir? (Step 3 – ən kritik)

Persistence context = EntityManager-in içindəki magistral sinəciy (cache + izləmə).

İçində iki böyük şey olur:

  1. Identity Map (ilk cache)
    • “Eyni Student(id=1)-ə bir daha baxsam, həmin object-i yenidən yaratmaram.”
  2. Dirty Checking tracking (dəyişiklik izləmə)
    • Managed object dəyişəndə “fərq nədir?” deyə SQL hazırlamaq üçün məlumat saxlayır.

Terasoluna guideline bunu “PersistenceContext area for managing entities” kimi izah edir və “managed entity” anlayışını buradan çıxarır:

  • Entity lifecycle bu “area” ilə idarə olunur
  • Managed entity = persistence context tərəfindən izlənən entity

Link: Terasoluna – PersistenceContext / managed entity / lifecycle


4. Entity states: New / Managed / Detached / Removed (Step 4)

Uşağa uyğun “status nişanları” kimi düşün:

  1. New (Transient): DB-də yoxdur, Hibernate onu izləməyə başlamayıb.
  2. Managed: persistence context içindədir, hər dəyişiklik izlənir (dirty checking işləyir).
  3. Detached: persistence context-dən çıxıb, artıq izlənmir (dirty checking işləməyə bilər).
  4. Removed: “silinəcək kimi işarələnib”.


package guru.springframework.cruddemo;

import guru.springframework.cruddemo.entity.Student;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@RequiredArgsConstructor
public class CrudDemoApplication implements CommandLineRunner {

private final EntityManagerFactory entityManagerFactory;

public static void main(String[] args) {
SpringApplication.run(CrudDemoApplication.class, args);
}

@Override
public void run(String... args) throws Exception {
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
// 1) Persistence Context (Identity Map) məşqi:
// eyni (entityClass + id) üçün 2 dəfə find -> eyni instance (==) qaytarmalıdır.
Long idToUse = 4L;
Student s1 = em.find(Student.class, idToUse);
Student s2 = em.find(Student.class, idToUse);

System.out.println("---- Identity Map testi ----");
System.out.println("idToUse = " + idToUse);
System.out.println("s1 == s2 ? " + (s1 == s2));
System.out.println("s1.name (managed) = " + s1.getName());

// 2) Dirty Checking testi:
// Managed entity-də setter edirik, flush/commit olmadan DB dəyişməz,
// flush ilə SQL görünəcək.
s1.setName("pc-managed-" + System.currentTimeMillis());
System.out.println("---- setName-dan sonra (managed) ----");
System.out.println("s1.name = " + s1.getName());
System.out.println("em.contains(s1) = " + em.contains(s1));

Student s3 = em.find(Student.class, idToUse);
System.out.println("s3 == s1 ? " + (s3 == s1));
System.out.println("s3.name = " + s3.getName());

System.out.println("---- flush (SQL-in göndərilməsi) ----");
em.flush(); // logda UPDATE görəcəksən

// 3) Rollback ilə sübut: DB qalıcı dəyişmir
System.out.println("---- rollback (qalıcı deyil) ----");
tx.rollback();

// PersistenceContext-i təmizlə ki, yenidən DB-dən oxuyaq (və yeni managed instance yaransın)
em.clear();
System.out.println("---- clear ----");
System.out.println("s2 == s1 ? " + (s2 == s1));
System.out.println("s3 == s1 ? " + (s3 == s1));


// 4) Yeni transaksiya: rollback-dən əvvəlki DB state-i qayıtmalıdır
tx.begin();
Student afterRollback = em.find(Student.class, idToUse);
System.out.println("---- rollback sonrası DB-dən oxunan ----");
System.out.println("afterRollback.name = " + afterRollback.getName());
tx.commit();
} catch (Exception e) {
if (tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
}
}








1. One to One relationship

package guru.springframework.cruddemo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.proxy.HibernateProxy;

import java.util.Objects;

@Getter
@Setter
@Entity
@ToString
@RequiredArgsConstructor
public class Student {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
private String pin;
private Integer age;

@OneToOne(mappedBy = "student")
private Profile profile;

@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) return false;
Student student = (Student) o;
return getId() != null && Objects.equals(getId(), student.getId());
}

@Override
public final int hashCode() {
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
}

} 


package guru.springframework.cruddemo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.proxy.HibernateProxy;

import java.util.Objects;

@Getter
@Setter
@Entity
@ToString
@RequiredArgsConstructor
public class Profile {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String bio;
private String country;

@OneToOne
@ToString.Exclude
@JoinColumn(name = "student_id", unique = true)
private Student student;

@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) return false;
Profile profile = (Profile) o;
return getId() != null && Objects.equals(getId(), profile.getId());
}

@Override
public final int hashCode() {
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
}
}


@JoinColumn foreignKey owning side-da qoyulur.

MappedBy - bidirectional relationlarda istifade olunur, ve foreignKey-ni kim idare edir sualina cavab verir. MappedBy olmasa JPA dushunur ki her iki teref "men forignKey saxlayiram" deyir, neticede 2 elaqe 2 FK veya istenilmeyen schema yarana biler. MappedBy FK-ni yalniz bir terefde saxlayir (one source of truth). DB schemanin yalniz (1cedvelde FK) yaranmasina komek edir. JPA-ya elaqeni harada update edeceyini bildirir. 


2. CascadeTypes

Cascade type harada qoyulur?

Həmişə relation field-ının üzərində, adətən owning side-da (FK-ni saxlayan tərəfdə).

Səndə:

  • Owning side: Profile.student (çünki @JoinColumn var, FK buradadır)
  • Inverse side: Student.profile (mappedBy var)

Ona görə cascade-i əsasən Profile.student-ə qoymaq məntiqlidir:


Cascade nə işə yarayır?

Cascade dediyi budur: sən Profile-i save edəndə (və ya persist/merge/delete edəndə) Hibernate də bağlı Student-ə uyğun əməliyyat aparacaq.

Əməliyyat növləri (ən çox işlənənlər):

  • PERSIST / ALL → yaratma zamanı avtomatik save/persist
  • REMOVE → silmə zamanı avtomatik delete
  • MERGE → update zamanı avtomatik merge
  • DETACHREFRESH → daha spesifik


2.1 CascadeType.Persist

Root entity-ni persist(save) edende onunla bagli olan entity-de DB yaz.


package guru.springframework.cruddemo;

import guru.springframework.cruddemo.entity.Profile;
import guru.springframework.cruddemo.entity.Student;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@RequiredArgsConstructor
public class CrudDemoApplication implements CommandLineRunner {

private final EntityManagerFactory entityManagerFactory;

public static void main(String[] args) {
SpringApplication.run(CrudDemoApplication.class, args);
}

@Override
public void run(String... args) throws Exception {
// --- PERSIST testi: bir EntityManager, əl ilə transaksiya, @Transactional yox ---
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();

// Addım 1: Yeni Student yarat (transient)
Student student = new Student();
student.setName("persist-name");
student.setSurname("persist-surname");
student.setPin("persist-pin");
student.setAge(25);

// Addım 2: Yeni Profile yarat (transient)
Profile profile = new Profile();
profile.setBio("persist-bio");
profile.setCountry("Azerbaijan");

// Addım 3: əlaqəni qur (hər iki tərəf)
student.setProfile(profile);
profile.setStudent(student);

// Addım 4: persist-dən əvvəl id-lər null olmalıdır
System.out.println("--- persist-dən əvvəl ---");
System.out.println("student.id = " + student.getId());
System.out.println("profile.id = " + profile.getId());

// Addım 5: root entity persist olunur; cascade PERSIST profile-a da gedir
em.persist(student);

// Addım 6: flush ilə SQL-ləri dərhal gör
em.flush();

// Addım 7: persist/flush sonrası id-lər dolur
System.out.println("--- persist + flush sonrası ---");
System.out.println("student.id = " + student.getId());
System.out.println("profile.id = " + profile.getId());

tx.commit();
} catch (Exception e) {
if (tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
}
}


2.2 CascadeType.Merge

Root entity-ni ve child entity-de deyishiklik etdikde yalniz root entity-ni save etdikde hem root hem de child save olsun.


package guru.springframework.cruddemo;

import guru.springframework.cruddemo.entity.Profile;
import guru.springframework.cruddemo.entity.Student;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@RequiredArgsConstructor
public class CrudDemoApplication implements CommandLineRunner {

private final EntityManagerFactory entityManagerFactory;

public static void main(String[] args) {
SpringApplication.run(CrudDemoApplication.class, args);
}

@Override
public void run(String... args) throws Exception {
// --- MERGE testi: bir EntityManager, əl ilə transaksiya, @Transactional yox ---
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();

// Addım 1: DB-dən mövcud Student tap
Student student = em.find(Student.class, 4L);
Profile profile = student.getProfile();

// Addım 2: Hazırkı DB dəyərlərini göstər
System.out.println("--- merge-dən əvvəl (managed) ---");
System.out.println("student.name = " + student.getName());
System.out.println("profile.bio = " + profile.getBio());

// Addım 3: Entity-ləri detached etmək üçün context-i təmizlə
em.clear();
System.out.println("em.contains(student) after clear = " + em.contains(student));

// Addım 4: Detached obyektlərdə dəyişiklik et
student.setName("merge-name-updated");
profile.setBio("merge-bio-updated");

System.out.println("--- detached obyektlərdə dəyişiklik ---");
System.out.println("student.name = " + student.getName());
System.out.println("profile.bio = " + profile.getBio());

// Addım 5: merge et; geri dönən obyekt managed olur
Student managedStudent = em.merge(student);
Profile managedProfile = managedStudent.getProfile();

System.out.println("--- merge sonrası (managed copy) ---");
System.out.println("managedStudent == detachedStudent ? " + (managedStudent == student));
System.out.println("managedStudent.name = " + managedStudent.getName());
System.out.println("managedProfile.bio = " + (managedProfile != null ? managedProfile.getBio() : "null"));

// Addım 6: flush ilə SQL (update) görünsün
em.flush();

tx.commit();
} catch (Exception e) {
if (tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
}
}


2.3 CascadeType.Refresh

Memory-de obyekti deyishdikde, lakin DB hele yazilmayib. Refresh dedikde DB-de ne varsa yeniden memory-ye yukleyir, yeni memory-deki deyishiklikleri atir.


package guru.springframework.cruddemo.entity;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToOne;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.proxy.HibernateProxy;

import java.util.Objects;

@Getter
@Setter
@Entity
@ToString
@RequiredArgsConstructor
public class Student {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String surname;
private String pin;
private Integer age;

@OneToOne(mappedBy = "student", cascade = {CascadeType.PERSIST, CascadeType.MERGE})
private Profile profile;

@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) return false;
Student student = (Student) o;
return getId() != null && Objects.equals(getId(), student.getId());
}

@Override
public final int hashCode() {
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
}

}


package guru.springframework.cruddemo.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.proxy.HibernateProxy;

import java.util.Objects;

@Getter
@Setter
@Entity
@ToString
@RequiredArgsConstructor
public class Profile {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String bio;
private String country;

@OneToOne
@ToString.Exclude
@JoinColumn(name = "student_id", unique = true)
private Student student;

@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) return false;
Profile profile = (Profile) o;
return getId() != null && Objects.equals(getId(), profile.getId());
}

@Override
public final int hashCode() {
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
}
}


package guru.springframework.cruddemo;

import guru.springframework.cruddemo.entity.Profile;
import guru.springframework.cruddemo.entity.Student;
import jakarta.persistence.EntityManager;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.EntityTransaction;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@RequiredArgsConstructor
public class CrudDemoApplication implements CommandLineRunner {

private final EntityManagerFactory entityManagerFactory;

public static void main(String[] args) {
SpringApplication.run(CrudDemoApplication.class, args);
}

@Override
public void run(String... args) throws Exception {
// --- REFRESH testi: bir EntityManager, əl ilə transaksiya, @Transactional yox ---
EntityManager em = entityManagerFactory.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();

// Addım 1: DB-dən oxu (eyni EM ilə)
Student student = em.find(Student.class, 1L);
Profile profile = student.getProfile();

// Addım 2: İlk dəyərlər (DB-də nə varsa)
System.out.println("--- DB-dən gələn (ilk) ---");
System.out.println("student.name = " + student.getName());
System.out.println("profile.bio = " + (profile != null ? profile.getBio() : "null"));

// Addım 3: Memory-də dəyişiklik et (DB-ə yazmırıq)
student.setName("Parvin");
profile.setBio("Senior Software Engineer");

// Addım 4: Dəyişdikdən sonra (memory)
System.out.println("--- Dəyişdikdən sonra (memory) ---");
System.out.println("student.name = " + student.getName());
System.out.println("profile.bio = " + profile.getBio());

// Addım 5: REFRESH — DB-dən yenidən yüklə (eyni EM)
em.refresh(student);

// Addım 6: Refresh sonrası (DB-dəki dəyərlər geri gəldi)
System.out.println("--- refresh() sonrası (DB-dən yenidən yüklənən) ---");
System.out.println("student.name = " + student.getName());
System.out.println("profile.bio = " + student.getProfile().getBio());

tx.commit();
} catch (Exception e) {
if (tx.isActive()) {
tx.rollback();
}
throw e;
} finally {
em.close();
}
}
}










































Комментарии

Популярные сообщения из этого блога

Interview questions

Lesson1: JDK, JVM, JRE

Lesson_2: Operations in Java