انتقل إلى المحتوى الرئيسي

الفصل 3: Annotations Data وJPA

1. Annotations الكيان​

@Entity​

تُعلّم الفئة ككيان JPA (جدول في قاعدة البيانات).

@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
}

@Table​

تخصيص اسم الجدول وقيوده.

@Entity
@Table(
name = "products",
schema = "ecommerce",
uniqueConstraints = @UniqueConstraint(columnNames = {"sku"}),
indexes = @Index(name = "idx_product_name", columnList = "name")
)
public class Product {
// ...
}

@Id​

تُعلّم الحقل كمفتاح أساسي.

@Id
private Long id;

@GeneratedValue​

استراتيجية توليد المفتاح الأساسي.

// زيادة تلقائية (PostgreSQL SERIAL, MySQL AUTO_INCREMENT)
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

// تسلسل (موصى به لـ PostgreSQL)
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "product_seq")
@SequenceGenerator(name = "product_seq", sequenceName = "product_sequence", allocationSize = 1)
private Long id;

// UUID
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;

// قاعدة البيانات تختار الاستراتيجية
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
استراتيجيةالوصفقاعدة البيانات النموذجية
IDENTITYزيادة تلقائيةMySQL, PostgreSQL
SEQUENCEتسلسل قاعدة البياناتPostgreSQL, Oracle
TABLEجدول التسلسلاتجميعها
UUIDمعرف UUIDجميعها
AUTOاختيار تلقائيجميعها

@Column​

تخصيص الربط لعمود.

@Column(name = "product_name", nullable = false, length = 100)
private String name;

@Column(unique = true)
private String sku;

@Column(precision = 10, scale = 2)
private BigDecimal price;

@Column(columnDefinition = "TEXT")
private String description;

@Column(updatable = false) // لا يمكن تعديله بعد الإنشاء
private LocalDateTime createdAt;

@Column(insertable = false) // غير مدرج في INSERT
private LocalDateTime updatedAt;

@Enumerated​

ربط enum Java بقاعدة البيانات.

public enum Role {
USER, ADMIN, MANAGER
}

@Entity
public class User {
@Enumerated(EnumType.STRING) // يخزن "USER", "ADMIN" (موصى به)
private Role role;

@Enumerated(EnumType.ORDINAL) // يخزن 0, 1, 2 (غير موصى به)
private Role roleOrdinal;
}

@Temporal​

تحديد نوع التاريخ (فقط لـ java.util.Date, غير ضروري مع java.time).

@Temporal(TemporalType.DATE)       // تاريخ فقط
private Date birthDate;

@Temporal(TemporalType.TIMESTAMP) // تاريخ + وقت
private Date createdAt;
تلميح

مع Java 8+، استخدم LocalDate, LocalDateTime, Instant مباشرة. @Temporal غير ضروري.

@Lob​

يشير إلى حقل Large Object (BLOB/CLOB).

@Lob
private String longDescription; // CLOB (نص طويل)

@Lob
private byte[] profileImage; // BLOB (ثنائي)

@Transient​

يستبعد حقل من الاستمرارية (لا يوجد عمود في قاعدة البيانات).

@Entity
public class User {
private String firstName;
private String lastName;

@Transient
private String fullName; // محسوب، غير مخزن في قاعدة البيانات

public String getFullName() {
return firstName + " " + lastName;
}
}

2. Annotations العلاقات​

@OneToOne​

علاقة واحد لواحد.

@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "address_id")
private Address address;
}

@Entity
public class Address {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String street;
private String city;

@OneToOne(mappedBy = "address")
private User user;
}

@OneToMany / @ManyToOne​

علاقة واحد لعدة.

@Entity
public class Department {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

@OneToMany(mappedBy = "department", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Employee> employees = new ArrayList<>();
}

@Entity
public class Employee {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "department_id")
private Department department;
}

@ManyToMany​

علاقة عدة لعدة.

@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;

@ManyToMany
@JoinTable(
name = "student_course",
joinColumns = @JoinColumn(name = "student_id"),
inverseJoinColumns = @JoinColumn(name = "course_id")
)
private Set<Course> courses = new HashSet<>();
}

@Entity
public class Course {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;

@ManyToMany(mappedBy = "courses")
private Set<Student> students = new HashSet<>();
}

ملخص معلمات العلاقة​

المعلمةالوصف
mappedByالجانب العكسي للعلاقة (لا يوجد عمود FK)
cascadeانتشار العمليات (PERSIST, MERGE, REMOVE, ALL)
fetchLAZY (تحميل مؤجل) أو EAGER (تحميل فوري)
orphanRemovalإزالة الأيتام تلقائيًا
optionalيمكن أن تكون العلاقة null

3. Annotations الاستعلامات​

@Query​

تحديد استعلام JPQL أو SQL مخصص.

public interface UserRepository extends JpaRepository<User, Long> {

// JPQL
@Query("SELECT u FROM User u WHERE u.email = :email")
Optional<User> findByEmail(@Param("email") String email);

// JPQL مع JOIN
@Query("SELECT u FROM User u JOIN u.orders o WHERE o.total > :amount")
List<User> findUsersWithOrdersAbove(@Param("amount") BigDecimal amount);

// استعلام SQL أصلي
@Query(value = "SELECT * FROM users WHERE created_at > :date", nativeQuery = true)
List<User> findRecentUsers(@Param("date") LocalDate date);

// إسقاط (إرجاع جزء من البيانات)
@Query("SELECT new com.example.dto.UserSummary(u.id, u.name, u.email) FROM User u")
List<UserSummary> findAllSummaries();
}

@Param​

تسمية معلمة في استعلام @Query.

@Query("SELECT u FROM User u WHERE u.name = :name AND u.role = :role")
List<User> findByNameAndRole(@Param("name") String name, @Param("role") Role role);

@Modifying​

يشير إلى استعلام تعديل (UPDATE, DELETE).

@Modifying
@Transactional
@Query("UPDATE User u SET u.active = false WHERE u.lastLogin < :date")
int deactivateInactiveUsers(@Param("date") LocalDateTime date);

@Modifying
@Transactional
@Query("DELETE FROM User u WHERE u.active = false")
void deleteInactiveUsers();

@NamedQuery​

تحديد استعلام مسمى على الكيان.

@Entity
@NamedQuery(name = "User.findActive", query = "SELECT u FROM User u WHERE u.active = true")
@NamedQuery(name = "User.countByRole", query = "SELECT COUNT(u) FROM User u WHERE u.role = :role")
public class User {
// ...
}

4. Annotations المعاملات والمستودع​

@Transactional​

إدارة معاملات قاعدة البيانات.

@Service
@Transactional(readOnly = true) // افتراضيًا للقراءة فقط
public class OrderService {

public List<Order> findAll() {
return orderRepository.findAll(); // معاملة للقراءة فقط
}

@Transactional // معاملة للكتابة (تجاوز readOnly)
public Order createOrder(OrderDto dto) {
Order order = new Order();
// ... منطق الإنشاء
return orderRepository.save(order);
}

@Transactional(
propagation = Propagation.REQUIRED, // استخدام المعاملة الحالية أو إنشاء واحدة جديدة
isolation = Isolation.READ_COMMITTED, // مستوى العزل
timeout = 30, // المهلة بالثواني
rollbackFor = Exception.class // التراجع عن أي استثناء
)
public void processPayment(Long orderId) {
// إذا تم إطلاق استثناء، يتم التراجع عن كل شيء
}
}
المعلمةالوصف
readOnlyتحسين للقراءات
propagationسلوك المعاملة (REQUIRED, REQUIRES_NEW, إلخ.)
isolationمستوى العزل (READ_COMMITTED, SERIALIZABLE, إلخ.)
timeoutالمدة القصوى بالثواني
rollbackForالاستثناءات التي تؤدي إلى التراجع
noRollbackForالاستثناءات التي لا تؤدي إلى التراجع

@EnableJpaRepositories​

تفعيل المستودعات Spring Data JPA (عادة ما يتم تكوينها تلقائيًا بواسطة Spring Boot).

@Configuration
@EnableJpaRepositories(basePackages = "com.example.repository")
public class JpaConfig {
}

@EnableJpaAuditing​

تفعيل التدقيق التلقائي (تواريخ الإنشاء/التعديل).

@Configuration
@EnableJpaAuditing
public class JpaAuditConfig {
}

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Article {

@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;

@LastModifiedDate
private LocalDateTime updatedAt;

@CreatedBy
@Column(updatable = false)
private String createdBy;

@LastModifiedBy
private String updatedBy;
}

5. Annotations الوراثة JPA​

@Inheritance​

استراتيجية الوراثة بين الكيانات.

// SINGLE_TABLE : جميع الفئات الفرعية في جدول واحد (افتراضي)
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "type")
public abstract class Payment {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private BigDecimal amount;
}

@Entity
@DiscriminatorValue("CARD")
public class CardPayment extends Payment {
private String cardNumber;
}

@Entity
@DiscriminatorValue("PAYPAL")
public class PaypalPayment extends Payment {
private String paypalEmail;
}
استراتيجيةالوصفالأداء
SINGLE_TABLEجدول واحد لكل الهيكلسريع (لا يوجد JOIN)
TABLE_PER_CLASSجدول لكل فئة فرعيةمتوسط
JOINEDجدول أساسي + جداول لكل فئة فرعية مع JOINمنظم ولكنه أبطأ

6. Annotations Hibernate الخاصة​

@Formula​

عمود محسوب على جانب SQL (لا يوجد عمود فعلي).

@Formula("(SELECT COUNT(*) FROM orders o WHERE o.user_id = id)")
private int orderCount;

@Where​

تصفية SQL افتراضية على مجموعة.

@OneToMany(mappedBy = "user")
@Where(clause = "active = true")
private List<Order> activeOrders;

@BatchSize​

تحسين تحميل المجموعات (منع مشكلة N+1).

@OneToMany(mappedBy = "department")
@BatchSize(size = 20)
private List<Employee> employees;

ملخص سريع​

Annotationالدور
@Entityالفئة = جدول قاعدة البيانات
@Tableتكوين الجدول
@Idمفتاح أساسي
@GeneratedValueتوليد تلقائي للمعرف
@Columnتكوين عمود
@Enumeratedربط enum
@OneToOneعلاقة 1:1
@OneToManyعلاقة 1:N
@ManyToOneعلاقة N:1
@ManyToManyعلاقة N:N
@JoinColumnعمود الربط (FK)
@JoinTableجدول الربط
@Queryاستعلام مخصص
@Modifyingاستعلام UPDATE/DELETE
@Transactionalإدارة المعاملات
@Transientحقل غير مستمر

➡️ الفصل 4: Annotations الأمان، الاختبار والمتقدمة


⬅️ العودة إلى الوحدة 17