الفصل 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) |
fetch | LAZY (تحميل مؤجل) أو 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 الأمان، الاختبار والمتقدمة