الفصل 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) | إنشاء/تعديل | كيان | User | INSERT أو UPDATE |
deleteById(id) | حذف | معرف | void | DELETE ... 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: مستودع أساسي
الهدف: إنشاء مستودع بسيط
التعليمات:
- أنشئ كيان
Product:Long idString nameDouble priceint quantity
- أنشئ
ProductRepositoryالذي يمتدJpaRepository - اختبر الطرق الأساسية الأربعة
الأسئلة:
- أي طريقة تستخدم لإنشاء منتج؟
- كيف تسترجع جميع المنتجات؟
- كيف تحذف منتجًا بواسطة معرفه؟
التمرين 2: طرق البحث
الهدف: استخدام قواعد التسمية
التعليمات:
- في
ProductRepository، أضف:- البحث بواسطة الاسم
- البحث بواسطة السعر
- البحث بواسطة الاسم والسعر
- أنشئ خدمة
ProductService - اختبر في متحكم
الأسئلة:
- ما هو SQL المولد لـ
findByName؟ - كيف تبحث عن المنتجات بسعر معين؟
التمرين 3: خدمة كاملة
الهدف: إنشاء هيكل كامل
التعليمات:
- أنشئ كيان
Student:Long idString firstNameString lastNameString emailint grade
- أنشئ
StudentRepositoryمع:- البحث بواسطة البريد الإلكتروني
- البحث بواسطة الدرجة
- البحث بواسطة firstName و lastName
- أنشئ
StudentServiceمع جميع عمليات CRUD - أنشئ
StudentControllerمع جميع النقاط النهائية
أسئلة المراجعة
-
ما هو المستودع في Spring Data JPA؟
-
ما هي الطرق الأربعة الموروثة تلقائيًا من JpaRepository؟
-
ما الفرق بين
save()لإنشاء وتحديث؟ -
كيف يعرف Spring أي استعلام SQL يجب توليده لـ
findByName؟ -
ما الفرق بين
AndوOrفي أسماء الطرق؟ -
لماذا نستخدم
Optional<User>لـfindById()؟ -
ماذا يحدث إذا استدعينا
findById()بمعرف غير موجود؟ -
هل يمكن دمج أكثر من معيارين باستخدام
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:
- TP2.2: قاعدة بيانات H2 - استخدم H2 والطرق الأساسية الأربعة
للمزيد من التعمق (الوحدات التالية):
- الوحدة 3: TP3.1: هيكل CRUD - تطبيق CRUD كامل مع التحقق
- الوحدة 4: TP4.1: PostgreSQL - الانتقال إلى PostgreSQL في الإنتاج
للمزيد من التعمق
مهتم باستعلامات أكثر تقدمًا؟ تحقق من:
الوحدة 6: Spring Data JPA المتقدم (قريبًا)
- 15+ كلمة رئيسية للبحث
- الربط بين الجداول
- JPQL واستعلامات مخصصة
- تحسين الأداء