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

الفصل 5: مقدمة في مستودعات Spring Data JPA

مقدمة​

الآن بعد أن أتقنت أساسيات Spring Boot (الهيكل، التكوين، التعليقات التوضيحية)، حان الوقت لتعلم كيفية تخزين البيانات في قاعدة بيانات. Spring Data JPA يبسط هذه المهمة بشكل كبير من خلال السماح لك بالتفاعل مع قاعدة البيانات دون كتابة كود SQL.

أهداف الفصل​

في نهاية هذا الفصل، ستكون قادرًا على:

  • فهم دور مستودع Spring Data JPA
  • استخدام 4 طرق أساسية لـ CRUD
  • إنشاء طرق بحث بسيطة باستخدام قواعد التسمية
  • فهم كيفية توليد Spring للاستعلامات تلقائيًا

ما هو المستودع؟​

تعريف​

المستودع هو واجهة تتيح لك التفاعل مع قاعدة البيانات دون كتابة كود SQL. يقوم Spring Data JPA بتوليد التنفيذ تلقائيًا.

تشبيه: المستودع مثل أمين المكتبة الذي يدير كتبك (البيانات) لك.

الهيكل الكامل​

المسؤوليات:

الطبقةالدورمثال
Controllerإدارة طلبات HTTP@RestController
Serviceالمنطق التجاريUserService
Repositoryالوصول إلى البياناتUserRepository
DatabaseالتخزينPostgreSQL, H2

إنشاء كيان بسيط​

مثال: كيان المستخدم​

قبل إنشاء مستودع، نحتاج إلى كيان - فئة Java تمثل جدولًا في قاعدة البيانات.

package com.example.demo.model;

import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {

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

private String name;
private String email;
private int age;

// Constructeurs
public User() {
}

public User(String name, String email, int age) {
this.name = name;
this.email = email;
this.age = age;
}

// Getters et Setters
public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}

التعليقات التوضيحية الأساسية​

التعليق التوضيحيالدورمثال
@Entityتمييز الفئة ككيان JPAإنشاء جدول
@Tableتحديد اسم الجدولname = "users"
@Idتحديد المفتاح الأساسيحقل id
@GeneratedValueتوليد تلقائي للمعرفزيادة تلقائية

الطرق الأساسية الأربعة​

واجهة JpaRepository​

يوفر Spring Data JPA الواجهة JpaRepository التي تحتوي بالفعل على جميع طرق CRUD الأساسية.

package com.example.demo.repository;

import com.example.demo.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// Pas besoin d'écrire de code!
// Les méthodes sont héritées automatiquement
}

توضيح:

  • JpaRepository<User, Long>:
    • User: نوع الكيان
    • Long: نوع المعرف

الطرق الموروثة تلقائيًا​

إليك الطرق الأساسية الأربعة التي يمكنك استخدامها فورًا:

1. findById() - البحث بواسطة المعرف​

التوقيع:

Optional<User> findById(Long id)

الاستخدام:

@Service
public class UserService {

@Autowired
private UserRepository userRepository;

public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found"));
}
}

SQL المولد:

SELECT * FROM users WHERE id = ?

2. findAll() - البحث عن الجميع​

التوقيع:

List<User> findAll()

الاستخدام:

public List<User> getAllUsers() {
return userRepository.findAll();
}

SQL المولد:

SELECT * FROM users

3. save() - إنشاء أو تحديث​

التوقيع:

User save(User user)

الاستخدام (إنشاء):

public User createUser(User user) {
return userRepository.save(user);
}

SQL المولد (إذا كان المعرف null):

INSERT INTO users (name, email, age) VALUES (?, ?, ?)

SQL المولد (إذا كان المعرف موجودًا):

UPDATE users SET name = ?, email = ?, age = ? WHERE id = ?

4. deleteById() - الحذف بواسطة المعرف​

التوقيع:

void deleteById(Long id)

الاستخدام:

public void deleteUser(Long id) {
userRepository.deleteById(id);
}

SQL المولد:

DELETE FROM users WHERE id = ?

جدول ملخص​

الطريقةالإجراءالمعاملالعائدSQL
findById(id)قراءة واحدةمعرفOptional<User>SELECT ... WHERE id = ?
findAll()قراءة الجميعلا شيءList<User>SELECT *
save(user)إنشاء/تعديلكيانUserINSERT أو UPDATE
deleteById(id)حذفمعرفvoidDELETE ... WHERE id = ?

قواعد التسمية: الأسس الثلاثة​

المبدأ العام​

يمكن لـ Spring Data JPA توليد طرق البحث تلقائيًا بناءً على اسم الطريقة.

الصيغة:

findBy[Attribut]

1. findBy (بحث بسيط)​

الصيغة:

findBy + NomAttribut

أمثلة:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

// البحث بواسطة الاسم
User findByName(String name);

// البحث بواسطة البريد الإلكتروني
User findByEmail(String email);

// البحث بواسطة العمر
List<User> findByAge(int age);
}

SQL المولد:

الطريقةSQL
findByName("John")SELECT * FROM users WHERE name = 'John'
findByEmail("john@example.com")SELECT * FROM users WHERE email = 'john@example.com'
findByAge(25)SELECT * FROM users WHERE age = 25

2. And (دمج معيارين)​

الصيغة:

findBy + Attribut1 + And + Attribut2

أمثلة:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

// البحث بواسطة الاسم والعمر
List<User> findByNameAndAge(String name, int age);

// البحث بواسطة البريد الإلكتروني والعمر
User findByEmailAndAge(String email, int age);
}

SQL المولد:

الطريقةSQL
findByNameAndAge("John", 25)SELECT * FROM users WHERE name = 'John' AND age = 25
findByEmailAndAge("john@example.com", 25)SELECT * FROM users WHERE email = 'john@example.com' AND age = 25

3. Or (معيار بديل)​

الصيغة:

findBy + Attribut1 + Or + Attribut2

أمثلة:

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

// البحث بواسطة الاسم أو البريد الإلكتروني
List<User> findByNameOrEmail(String name, String email);

// البحث بواسطة العمر 20 أو 30
List<User> findByAgeOrAge(int age1, int age2);
}

SQL المولد:

الطريقةSQL
findByNameOrEmail("John", "john@example.com")SELECT * FROM users WHERE name = 'John' OR email = 'john@example.com'

مثال كامل: هيكل ثلاثي الطبقات​

1. الكيان (النموذج)​

@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
private int age;

// Constructeurs, Getters, Setters
}

2. المستودع​

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

// Méthodes héritées automatiquement:
// - findById(Long id)
// - findAll()
// - save(User user)
// - deleteById(Long id)

// Méthodes personnalisées:
User findByEmail(String email);
List<User> findByAge(int age);
List<User> findByNameAndAge(String name, int age);
}

3. الخدمة​

@Service
public class UserService {

private final UserRepository userRepository;

public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

// CREATE
public User createUser(User user) {
return userRepository.save(user);
}

// READ - tous
public List<User> getAllUsers() {
return userRepository.findAll();
}

// READ - par ID
public User getUserById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("User not found with id: " + id));
}

// READ - par email
public User getUserByEmail(String email) {
return userRepository.findByEmail(email);
}

// READ - par age
public List<User> getUsersByAge(int age) {
return userRepository.findByAge(age);
}

// UPDATE
public User updateUser(Long id, User userDetails) {
User user = getUserById(id);
user.setName(userDetails.getName());
user.setEmail(userDetails.getEmail());
user.setAge(userDetails.getAge());
return userRepository.save(user);
}

// DELETE
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}

4. المتحكم​

@RestController
@RequestMapping("/api/users")
public class UserController {

private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

// POST /api/users
@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user);
}

// GET /api/users
@GetMapping
public List<User> getAllUsers() {
return userService.getAllUsers();
}

// GET /api/users/123
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}

// GET /api/users/email/john@example.com
@GetMapping("/email/{email}")
public User getUserByEmail(@PathVariable String email) {
return userService.getUserByEmail(email);
}

// GET /api/users/age/25
@GetMapping("/age/{age}")
public List<User> getUsersByAge(@PathVariable int age) {
return userService.getUsersByAge(age);
}

// PUT /api/users/123
@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.updateUser(id, user);
}

// DELETE /api/users/123
@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.deleteUser(id);
}
}

إعداد H2 للاختبارات​

application.yml​

spring:
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:

h2:
console:
enabled: true
path: /h2-console

jpa:
hibernate:
ddl-auto: create-drop
show-sql: true
properties:
hibernate:
format_sql: true

الوصول إلى وحدة التحكم H2:

http://localhost:8080/h2-console

تمارين عملية​

التمرين 1: مستودع أساسي​

الهدف: إنشاء مستودع بسيط

التعليمات:

  1. أنشئ كيان Product:
    • Long id
    • String name
    • Double price
    • int quantity
  2. أنشئ ProductRepository الذي يمتد JpaRepository
  3. اختبر الطرق الأساسية الأربعة

الأسئلة:

  • أي طريقة تستخدم لإنشاء منتج؟
  • كيف تسترجع جميع المنتجات؟
  • كيف تحذف منتجًا بواسطة معرفه؟

التمرين 2: طرق البحث​

الهدف: استخدام قواعد التسمية

التعليمات:

  1. في ProductRepository، أضف:
    • البحث بواسطة الاسم
    • البحث بواسطة السعر
    • البحث بواسطة الاسم والسعر
  2. أنشئ خدمة ProductService
  3. اختبر في متحكم

الأسئلة:

  • ما هو SQL المولد لـ findByName؟
  • كيف تبحث عن المنتجات بسعر معين؟

التمرين 3: خدمة كاملة​

الهدف: إنشاء هيكل كامل

التعليمات:

  1. أنشئ كيان Student:
    • Long id
    • String firstName
    • String lastName
    • String email
    • int grade
  2. أنشئ StudentRepository مع:
    • البحث بواسطة البريد الإلكتروني
    • البحث بواسطة الدرجة
    • البحث بواسطة firstName و lastName
  3. أنشئ StudentService مع جميع عمليات CRUD
  4. أنشئ StudentController مع جميع النقاط النهائية

أسئلة المراجعة​

  1. ما هو المستودع في Spring Data JPA؟

  2. ما هي الطرق الأربعة الموروثة تلقائيًا من JpaRepository؟

  3. ما الفرق بين save() لإنشاء وتحديث؟

  4. كيف يعرف Spring أي استعلام SQL يجب توليده لـ findByName؟

  5. ما الفرق بين And و Or في أسماء الطرق؟

  6. لماذا نستخدم Optional<User> لـ findById()؟

  7. ماذا يحدث إذا استدعينا findById() بمعرف غير موجود؟

  8. هل يمكن دمج أكثر من معيارين باستخدام And؟ أعط مثالاً.


ملخص الفصل​

النقاط الرئيسية​

  • المستودع هو واجهة للوصول إلى البيانات
  • JpaRepository يوفر 4 طرق أساسية: findById, findAll, save, deleteById
  • قواعد التسمية تسمح بإنشاء طرق تلقائيًا
  • 3 كلمات رئيسية أساسية: findBy, And, Or
  • Spring يولد SQL تلقائيًا بناءً على اسم الطريقة
  • الهيكل: Controller → Service → Repository → Database

ما تعرفه الآن​

✅ إنشاء كيان JPA
✅ إنشاء مستودع Spring Data JPA
✅ استخدام 4 طرق CRUD الأساسية
✅ إنشاء طرق بحث بسيطة (findBy, And, Or)
✅ بناء هيكل ثلاثي الطبقات كامل

ما ستتعلمه لاحقًا (الوحدة 6)​

🔜 قواعد التسمية المتقدمة (15+ كلمة رئيسية)
🔜 الربط بين الكيانات
🔜 استعلامات JPQL مخصصة
🔜 الأداء والتحسين
🔜 المعاملات


الممارسات الجيدة​

DO (ما يجب فعله)​

✅ استخدام حقن المنشئ للمستودعات
✅ تسمية الطرق وفقًا لقواعد Spring Data JPA
✅ التعامل مع الحالات التي لا يجد فيها findById() شيئًا باستخدام Optional
✅ فصل المنطق التجاري (Service) عن الوصول إلى البيانات (Repository)

DON'T (ما يجب تجنبه)​

❌ وضع منطق تجاري في المستودعات
❌ استدعاء المستودع مباشرة من المتحكم
❌ تجاهل Optional الذي يعيده findById()
❌ إنشاء أسماء طرق لا تتبع القواعد


الخطوات التالية​

التطبيق العملي​

الآن بعد أن تعرفت على أساسيات المستودعات، حان الوقت للتطبيق!

انتقل إلى التمارين العملية للوحدة 2:

للمزيد من التعمق (الوحدات التالية):

للمزيد من التعمق​

مهتم باستعلامات أكثر تقدمًا؟ تحقق من:

الوحدة 6: Spring Data JPA المتقدم (قريبًا)

  • 15+ كلمة رئيسية للبحث
  • الربط بين الجداول
  • JPQL واستعلامات مخصصة
  • تحسين الأداء

العودة إلى الوحدة 2