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ı”.
Connectionaçılır.Transactionbaş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
EntityManageryaratmağ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:
- Identity Map (ilk cache)
- “Eyni
Student(id=1)-ə bir daha baxsam, həmin object-i yenidən yaratmaram.”
- “Eyni
- 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:
- New (Transient): DB-də yoxdur, Hibernate onu izləməyə başlamayıb.
- Managed: persistence context içindədir, hər dəyişiklik izlənir (dirty checking işləyir).
- Detached: persistence context-dən çıxıb, artıq izlənmir (dirty checking işləməyə bilər).
- 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@JoinColumnvar, FK buradadır) - Inverse side:
Student.profile(mappedByvar)
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/persistREMOVE→ silmə zamanı avtomatik deleteMERGE→ update zamanı avtomatik mergeDETACH,REFRESH→ 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();
}
}
}
Комментарии
Отправить комментарий