Spring Data JPA

مقدمة إلى Spring Data JPA

18 دقيقة الدرس 1 من 13

مقدمة إلى Spring Data JPA

كل تطبيق يحتفظ بالبيانات يجب أن يجيب على نفس الأسئلة المملّة: كيف أفتح اتصالًا، وكيف أحوّل نتيجة الاستعلام إلى كائن، وكيف أتعامل مع المعاملات، وكيف أغلق كل شيء بأمان؟ قبل ظهور Spring Data JPA، كان مطوّرو Java إما يكتبون هذا الكود الأساسي يدويًا باستخدام JDBC (كما فعلت في درس JDBC) أو يتعاملون مع EntityManager مباشرةً عبر JPA. يُزيل Spring Data JPA هذا الكود المتكرر تقريبًا بالكامل؛ فأنت تُعلن ماذا تريد الاستعلام عنه، وتتكفّل إطار العمل بكتابة كيفية تنفيذه.

المشكلة التي يحلّها Spring Data JPA

فكّر في تطبيق JPA بسيط بدون Spring Data. للبحث عن Order بناءً على معرّف العميل ستكتب شيئًا كهذا:

// JPA بدون Spring Data EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPU"); EntityManager em = emf.createEntityManager(); TypedQuery<Order> q = em.createQuery( "SELECT o FROM Order o WHERE o.customerId = :cid", Order.class); q.setParameter("cid", customerId); List<Order> orders = q.getResultList(); em.close();

تخيّل الآن أنك ستكتب هذا لكل كيان ولكل متغيّر من الاستعلامات في طبقة الخدمة: البحث حسب الحالة، والبحث حسب نطاق التاريخ، والعدّ حسب العميل، والتنقل عبر الصفحات. النتيجة مئات الأسطر من الكود المتطابق هيكليًا. يُختزل كل ذلك في Spring Data JPA إلى تصريح واحد بتوقيع الدالة في واجهة.

موقع Spring Data JPA في الطبقات التقنية

Spring Data JPA ليس بديلًا عن JPA أو Hibernate — بل هو طبقة تجريد رفيعة فوق كليهما. تبدو مجموعة طبقات وقت التشغيل الكاملة كالتالي:

  • كودك — يستدعي واجهة المستودع (repository) الخاصة بـ Spring Data.
  • Spring Data JPA — يولّد التنفيذ عند بدء التشغيل باستخدام وكلاء JDK الديناميكيين.
  • JPA (jakarta.persistence API) — المواصفة القياسية التي تُعرّف EntityManager و@Entity و JPQL وغيرها.
  • Hibernate 6 — موفّر JPA الذي ينفّذ SQL فعليًا ضد قاعدة البيانات.
  • JDBC / HikariCP — تجمّع الاتصالات في الطبقة السفلى.
Spring Data JPA مقابل Hibernate: تُهيّئ Hibernate كموفّر JPA لكنك نادرًا ما تتعامل معه مباشرةً عند استخدام Spring Data. يتولّى Spring Data إدارة دورة حياة EntityManager، لذا تقتصر معظم المعرفة الخاصة بـ Hibernate التي تحتاجها على تعليقات التعيين وتلميحات الاستعلام.

إضافة Spring Data JPA إلى مشروع Spring Boot 3

مع Spring Boot، يجلب starter واحد Spring Data JPA وHibernose 6 وHikariCP دفعةً واحدة:

<!-- pom.xml --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <!-- مشغّل JDBC، مثل PostgreSQL --> <dependency> <groupId>org.postgresql</groupId> <artifactId>postgresql</artifactId> <scope>runtime</scope> </dependency>

ثم قدّم إعداد مصدر البيانات في application.properties:

spring.datasource.url=jdbc:postgresql://localhost:5432/shop spring.datasource.username=${DB_USER} spring.datasource.password=${DB_PASS} # DDL الخاص بـ Hibernate: validate | update | create | create-drop | none spring.jpa.hibernate.ddl-auto=validate # تسجيل SQL الذي يرسله Hibernate فعليًا (مفيد أثناء التطوير) spring.jpa.show-sql=true spring.jpa.properties.hibernate.format_sql=true
استخدم ddl-auto=validate في بيئة الإنتاج. يطلب هذا الإعداد من Hibernate التحقق من أن المخطط يطابق كياناتك دون أي تعديل. احتفظ بـ create-drop لاختبارات التكامل فقط — فهو يحذف الجداول عند إيقاف التطبيق، وهو أمر كارثي إذا وُجّه نحو قاعدة البيانات الخطأ.

أول مستودع لك — بدون أي كود متكرر

فيما يلي مثال عملي كامل لكيان ومستودعه:

// Product.java package com.example.shop.domain; import jakarta.persistence.*; @Entity @Table(name = "products") public class Product { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(nullable = false, length = 200) private String name; private java.math.BigDecimal price; protected Product() {} // مطلوب من JPA public Product(String name, java.math.BigDecimal price) { this.name = name; this.price = price; } // getters محذوفة للإيجاز }
// ProductRepository.java package com.example.shop.repository; import com.example.shop.domain.Product; import org.springframework.data.jpa.repository.JpaRepository; public interface ProductRepository extends JpaRepository<Product, Long> { // لا حاجة لأجسام الدوال — Spring Data يولّدها عند بدء التشغيل }

هذا كل شيء. بامتداد JpaRepository<Product, Long> تحصل على ثمانية عشر دالة جاهزة للاستخدام تشمل save وfindById وfindAll وdeleteById ومتغيّرات مُرقَّمة. يكتشف Spring Boot الواجهة في مسار الفئات ويوصّل تنفيذًا كاملًا في سياق التطبيق تلقائيًا.

// ProductService.java @Service @RequiredArgsConstructor public class ProductService { private final ProductRepository products; // تحقن من قِبل Spring public Product create(String name, BigDecimal price) { return products.save(new Product(name, price)); } public Optional<Product> findById(Long id) { return products.findById(id); // تُعيد Optional — وليس null أبدًا } public List<Product> all() { return products.findAll(); } }

ما يحدث وقت التشغيل

عند بدء تشغيل تطبيق Spring Boot، يفحص Spring Data بحثًا عن الواجهات التي تمتد من علامة Repository. لكل منها يولّد وكيلًا ديناميكيًا من JDK مدعومًا بـ SimpleJpaRepository — وهو فئة ملموسة تفوّض كل استدعاء إلى EntityManager مُحقَن. يُسجَّل هذا الوكيل كـ bean في Spring، فيشارك في إدارة المعاملات وحقن التبعيات مثل أي مكوّن آخر.

مشكلة استعلام N+1 حاضرة منذ البداية. استدعاء findAll() على كيان يحتوي على مجموعة محمّلة بشكل كسول (مثل @OneToMany) سيُطلق استعلام SELECT واحدًا لتحميل الصفوف الأب ثم استعلامًا إضافيًا لكل صف لتحميل الأبناء. على جدول بـ 1000 صف، هذا 1001 استعلامًا. تتناول دروس لاحقة الوصلات الجلبية (fetch joins) و@EntityGraph والإسقاطات للتخفيف من هذه المشكلة — لكن عليك أن تكون واعيًا بها منذ لحظة كتابة أول findAll().

المفاضلات الأدائية بنظرة سريعة

Spring Data JPA منتج ومريح، لكنه ليس سحرًا. فيما يلي نظرة صادقة على المفاضلات مقارنةً بـ JDBC المكتوب يدويًا:

  • كود أقل وأمان أكثر: تُتحقَّق من صحة دوال الاستعلام المشتقة مقابل نموذج الكيان عند بدء التشغيل لا وقت التنفيذ، لذا تفشل الأخطاء المطبعية مبكرًا.
  • تكلفة التعيين الكائني-العلائقي: يتتبع Hibernate حالة الكيان (الفحص القذر)، ويدير ذاكرة التخزين المؤقت للمستوى الأول، ويترجم بين الرسوم البيانية للكائنات والصفوف العلائقية. للإدخالات الجماعية أو استعلامات التحليل، غالبًا ما يكون JDBC البسيط أو SQL الأصلية (المغطاة في الدرس السادس) أسرع.
  • تكلفة التجريد: فهم SQL التي يولّدها Hibernate فعليًا ضروري لتجنب الاستعلامات البطيئة. مكّن show-sql دائمًا أثناء التطوير وراجع المخرجات بمحلّل استعلامات قبل النشر.

الخلاصة

يقع Spring Data JPA بين منطق أعمالك وطبقة JPA/Hibernate. يولّد تنفيذات المستودع عند بدء التشغيل، ويُعرض واجهة برمجية نظيفة مبنية على الواجهات، ويشارك بالكامل في آليات المعاملات وحقن التبعيات في Spring. النتيجة كود متكرر أقل بكثير — لكن الاستخدام الفعّال يتطلب فهم SQL المولّدة تحت الغطاء. في الدرس القادم ستعيّن فئات المجال إلى جداول قاعدة البيانات باستخدام @Entity وتعليقات jakarta.persistence.