5. مقدمة إلى Spring Data JPA
في هذا الفصل، سندرس البنية التالية:
![]() |
نقوم بإدراج طبقة [JPA] (Java Persistence API) بين الطبقة [DAO] وبرنامج التشغيل JDBC الخاص بـ SGBD. أصبحت الطبقة JPA هي التي تصدر الأوامر SQL الموجهة إلى SGBD. لم تعد الطبقة [DAO] تتعامل مع الأوامر SQL، بل تتعامل فقط مع كائنات تُسمى كيانات JPA، وهي عبارة عن صور للجداول المختلفة في قاعدة البيانات المستخدمة. ترتبط حقول هذه الكيانات بشكل فريد بأعمدة الجداول بفضل تعليقات Java. وهذا ما يسمح للطبقة JPA بترجمة العمليات التي تجريها الطبقة [DAO] على الكيانات JPA إلى لغة SQL.
Spring Data هو فرع من Spring يركز على الوصول إلى البيانات، سواء كانت مخزنة في قاعدة بيانات علائقية SGBDR، أو قاعدة بيانات NOSQL، أو أنواع أخرى من مستودعات البيانات. نحن هنا مهتمون فقط بـ SGBDR والوصول إليها عبر JPA. وفيما بعد، قد نكتب [Spring JPA] للإشارة في الواقع إلى [Spring Data JPA]. في البنية المذكورة أعلاه، توفر الطبقة [Spring Data] المرافق للطبقة [DAO] لإدارة الكيانات JPA.
JPA هي في الواقع مواصفة. سنقوم باختبار ثلاثة من تطبيقاتها:
- Hibernate (http://hibernate.org/)؛
- EclipseLink (http://www.eclipse.org/eclipselink/)؛
- OpenJpa (http://openjpa.apache.org/)؛
5.1. مثال-01
يوجد على موقع Spring العديد من الدروس التعليمية للبدء في استخدام Spring [http://spring.io/guides]. سنستخدم إحداها لتقديم Spring Data. ونستخدم في ذلك Spring Tool Suite (STS).
![]() |
- في [1]، نقوم باستيراد أحد البرامج التعليمية من [spring.io/guides]؛
![]() |
- في [2]، نختار البرنامج التعليمي [Accessing Data Jpa] الذي يوضح كيفية الوصول إلى قاعدة البيانات باستخدام Spring Data؛
- في [3]، نختار مشروعًا تم تكوينه بواسطة Maven؛
- في [4]، يمكن تقديم البرنامج التعليمي في شكلين: [initial] وهي نسخة فارغة يتم ملؤها باتباع البرنامج التعليمي، أو [complete] وهي النسخة النهائية من البرنامج التعليمي. نختار هذه الأخيرة؛
- في [5]، يمكننا اختيار عرض البرنامج التعليمي في متصفح؛
- في [6]، المشروع النهائي.
5.1.1. إعدادات Maven للمشروع
يتم تكوين تبعيات Maven للمشروع في الملف [pom.xml]:
<groupId>org.springframework</groupId>
<artifactId>gs-accessing-data-jpa</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
</dependencies>
<properties>
<!-- استخدام UTF-8 لكل شيء -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<start-class>hello.Application</start-class>
</properties>
- الأسطر 5-9: تحدد مشروع Maven أم. وهو الذي يحدد الجزء الأكبر من تبعيات المشروع. قد تكون هذه التبعيات كافية، وفي هذه الحالة لا نضيف المزيد، أو قد لا تكون كافية، وفي هذه الحالة نضيف التبعيات الناقصة؛
- الأسطر 12-15: تحدد تبعية لـ [spring-boot-starter-data-jpa]. يحتوي هذا الأرتيفاكت على فئات Spring Data؛
- الأسطر 16-19: تحدد تبعية لـ SGBD و H2 التي تسمح بإنشاء وإدارة قواعد البيانات في الذاكرة.
لنلقِ نظرة على الفئات التي توفرها هذه التبعيات:
![]() | ![]() | ![]() |
وهي عديدة جدًا:
- بعضها ينتمي إلى منظومة Spring (تلك التي تبدأ بـ spring)؛
- وبعضها الآخر ينتمي إلى نظام Hibernate (hibernate، jboss) الذي نستخدم هنا تنفيذه JPA؛
- وبعضها الآخر عبارة عن مكتبات اختبار (junit، hamcrest)؛
- وأخرى عبارة عن مكتبات سجلات (log4j، logback، slf4j)؛
سنحتفظ بها جميعًا. أما بالنسبة لتطبيق قيد التشغيل، فيجب الاحتفاظ فقط بالمكتبات الضرورية.
في السطر 26 من الملف [pom.xml]، نجد السطر التالي:
<start-class>hello.Application</start-class>
يرتبط هذا السطر بالسطور التالية:
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
الأسطر 6-9، يتيح المكون الإضافي [spring-boot-maven-plugin] إنشاء ملف jar القابل للتنفيذ للتطبيق. ويشير السطر 26 من الملف [pom.xml] إلى الفئة القابلة للتنفيذ لهذا الملف jar.
5.1.2. الطبقة [JPA]
يتم الوصول إلى قاعدة البيانات من خلال طبقة [JPA]، وJava Persistence API:
![]() |
![]() |
التطبيق بسيط ويقوم بإدارة العملاء [Customer]. الفئة [Customer] هي جزء من الطبقة [JPA] وهي كما يلي:
package hello;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Customer {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
protected Customer() {
}
public Customer(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
@Override
public String toString() {
return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
}
}
يحتوي العميل على معرّف [id]، واسم أول [firstName]، واسم عائلة [lastName]. تمثل كل مثيل [Customer] سطرًا في جدول قاعدة البيانات.
- السطر 8: تعليق توضيحي JPA الذي يجعل من عملية استدامة المثيلات [Customer] (إنشاء، قراءة، تحديث، حذف) تُدار بواسطة تنفيذ JPA. وبناءً على تبعيات Maven، نلاحظ أن التنفيذ JPA / Hibernate هو الذي يتم استخدامه؛
- السطران 11-12: تعليقات توضيحية JPA تربط الحقل [id] بالمفتاح الأساسي لجدول [Customer]. يشير السطر 12 إلى أن التنفيذ JPA سيستخدم طريقة إنشاء المفتاح الأساسي الخاصة بـ SGBD المستخدم، وهو هنا H2؛
لا توجد تعليقات توضيحية أخرى لـ JPA. وبالتالي، سيتم استخدام القيم الافتراضية:
- ستحمل جدول [Customer] اسم الفئة، أي [Customer]؛
- وستحمل أعمدة هذا الجدول أسماء حقول الفئة: [id, firstName, lastName] مع العلم أن الأحرف الكبيرة والصغيرة لا تؤخذ في الاعتبار في اسم عمود الجدول؛
تجدر الإشارة إلى أنه لم يتم ذكر اسم التنفيذ المستخدم JPA في أي مرحلة.
5.1.3. الطبقة [Spring Data]
تقوم الفئة [CustomerRepository] بتنفيذ طبقة الوصول إلى الجدول [Customer]. وفيما يلي شفرة البرمجة الخاصة بها:
![]() |
![]() |
package hello;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
وبالتالي فهي واجهة وليست فئة (السطر 7). وهي تمتد واجهة [CrudRepository]، وهي واجهة تابعة لـ Spring Data (السطر 5). يتم تعريف هذه الواجهة بواسطة نوعين: الأول هو نوع العناصر المدارة، وهو هنا النوع [Customer]، والثاني هو نوع المفتاح الأساسي للعناصر المدارة، وهو هنا النوع [Long]. وتكون واجهة [CrudRepository] كما يلي:
package org.springframework.data.repository;
import java.io.Serializable;
@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> save(Iterable<S> entities);
T findOne(ID id);
boolean exists(ID id);
Iterable<T> findAll();
Iterable<T> findAll(Iterable<ID> ids);
long count();
void delete(ID id);
void delete(T entity);
void delete(Iterable<? extends T> entities);
void deleteAll();
}
تحدد هذه الواجهة العمليات CRUD (إنشاء – قراءة – تحديث – حذف) التي يمكن إجراؤها على النوع JPA T:
- السطر 8: تتيح طريقة save حفظ كيان T في قاعدة البيانات. وهي تحفظ الكيان باستخدام المفتاح الأساسي الذي منحته إياه SGBD. كما تتيح تحديث كيان T المحدد بمفتاحه الأساسي id. ويتم اختيار إحدى العمليتين وفقًا لقيمة المفتاح الأساسي id: إذا كانت قيمته null، تتم عملية التخزين الدائم، وإلا تتم عملية التحديث؛
- السطر 10: نفس الأمر ولكن بالنسبة لقائمة من الكيانات؛
- السطر 12: تسمح الطريقة findOne بالعثور على كيان T محدد بواسطة مفتاحه الأساسي id؛
- السطر 22: تسمح الطريقة delete بحذف كيان T المحدد بواسطة مفتاحه الأساسي id؛
- الأسطر 24-28: أشكال مختلفة من الطريقة [delete]؛
- السطر 16: تسمح الطريقة [findAll] بالبحث عن جميع الكيانات T المخزنة؛
- السطر 18: نفس الشيء، لكن يقتصر على الكيانات التي تم تمرير قائمة معرّفاتها؛
لنعد إلى واجهة [CustomerRepository]:
package hello;
import java.util.List;
import org.springframework.data.repository.CrudRepository;
public interface CustomerRepository extends CrudRepository<Customer, Long> {
List<Customer> findByLastName(String lastName);
}
- تسمح السطر 9 بالعثور على [Customer] من خلال اسمه [lastName]؛
وهذا كل شيء بخصوص الطبقة [DAO]. لا توجد فئة لتنفيذ الواجهة السابقة. يتم إنشاء هذه الواجهة عند التنفيذ بواسطة [Spring Data]. يتم تنفيذ أساليب الواجهة [CrudRepository] تلقائيًا. أما بالنسبة للأساليب المضافة إلى الواجهة [CustomerRepository]، فالأمر يختلف. لنعد إلى تعريف [Customer]:
private long id;
private String firstName;
private String lastName;
يتم تنفيذ الطريقة الموجودة في السطر 9 تلقائيًا بواسطة [Spring Data] لأنها تشير إلى الحقل [lastName] (السطر 3) في [Customer]. عندما يصادف Spring Data طريقة [findBySomething] في الواجهة المطلوب تنفيذها، فإنه ينفذها باستخدام الاستعلام التالي JPQL (لغة استعلامات الاستمرارية في Java):
لذلك يجب أن يحتوي النوع T على حقل باسم [something]. وبذلك فإن الطريقة
سيتم تنفيذها بواسطة كود مشابه لما يلي:
return [em].createQuery("select c from Customer c where c.lastName=:value").setParameter("value",lastName).getResultList()
حيث يشير [em] إلى سياق الاستمرارية JPA. ولا يكون ذلك ممكنًا إلا إذا كانت الفئة [Customer] تحتوي على حقل باسم [lastName]، وهو ما ينطبق هنا.
وختامًا، في الحالات البسيطة، تتيح لنا Spring Data تنفيذ الطبقة [DAO] باستخدام واجهة بسيطة.
5.1.4. الطبقة [console]
![]() |
![]() |
الفئة [Application] هي كما يلي:
package hello;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application implements CommandLineRunner {
@Autowired
CustomerRepository repository;
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
@Override
public void run(String... strings) throws Exception {
// حفظ عدد من العملاء
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
// استدعاء جميع العملاء
System.out.println("Customers found with findAll():");
System.out.println("-------------------------------");
for (Customer customer : repository.findAll()) {
System.out.println(customer);
}
System.out.println();
// استدعاء عميل واحد باستخدام ID
Customer customer = repository.findOne(1L);
System.out.println("Customer found with findOne(1L):");
System.out.println("--------------------------------");
System.out.println(customer);
System.out.println();
// استرداد العملاء حسب الاسم الأخير
System.out.println("Customer found with findByLastName('Bauer'):");
System.out.println("--------------------------------------------");
for (Customer bauer : repository.findByLastName("Bauer")) {
System.out.println(bauer);
}
}
}
- السطر 9: تُنفِّذ الفئة الواجهة [CommandLineRunner] التي هي واجهة [Spring Boot] (السطر 4). لا تحتوي هذه الواجهة إلا على طريقة واحدة، وهي الموجودة في السطر 19؛
- السطر 8: @SpringBootApplication هو تعليق يجمع عدة تعليقات [Spring Boot]:
- @Configuration: تشير إلى أن الفئة هي فئة تكوين؛
- @EnableAutoConfiguration: تطلب من [Spring Boot] أن يقوم بنفسه بإنشاء عدد معين من الفاصوليا بناءً على خصائص متنوعة، ولا سيما محتوى مسار الفئات (Classpath) للمشروع. ونظرًا لوجود مكتبات Hibernate في مسار Classpath، فسيتم تنفيذ bean [entityManagerFactory] باستخدام Hibernate. ونظرًا لوجود مكتبة SGBD H2 في مسار الفئات (Classpath)، فسيتم تنفيذ الكائن [dataSource] باستخدام H2. في bean [dataSource]، يجب أيضًا تعريف المستخدم وكلمة المرور الخاصة به. هنا سيستخدم Spring Boot المسؤول الافتراضي لـ H2، وهو بدون كلمة مرور. ونظرًا لوجود المكتبة [spring-tx] في مسار الفئات (Classpath)، فسيتم استخدام مدير المعاملات الخاص بـ Spring؛
- @EnableWebMvc: إذا كانت المكتبة [spring-mvc] موجودة في مسار الفئات (Classpath). في هذه الحالة، يتم إجراء تكوين تلقائي لتطبيق الويب؛
- @ComponentScan: يحدد لـ Spring أين يبحث عن الفئات (beans) والتكوينات والخدمات الأخرى. هنا يتم البحث عنها افتراضيًا في الحزمة التي تحتوي على الفئة المُعلَّمة، أي الحزمة [hello]. وبالتالي سيتم العثور على الفئتين [Customer] و [CustomerRepository]. ونظرًا لأن الأولى تحمل التعليق التوضيحي [@Entity]، فسيتم تصنيفها ككيان يديره Hibernate. ونظرًا لأن الثانية تمتد واجهة [CrudRepository]، فسيتم تسجيلها كـ bean في Spring؛
- السطران 11-12: يتم حقن bean [CustomerRepository] في كود الفئة الرئيسية؛
- السطر 15: يتم تنفيذ الطريقة الثابتة [run] الخاصة بالفئة [SpringApplication] في مشروع Spring Boot. والمعلمة الخاصة بها هي الفئة التي تحمل تعليقًا توضيحيًا [Configuration] أو [EnableAutoConfiguration]. عندئذٍ سيتم تنفيذ كل ما تم شرحه سابقًا. والنتيجة هي سياق تطبيق Spring، أي مجموعة من الحبوب (beans) التي يديرها Spring؛
- الأسطر 19-48: العمليات التالية لا تفعل سوى استخدام أساليب المكون الذي ينفذ واجهة [CustomerRepository]؛
نتائج وحدة التحكم هي كما يلي:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.2.2.RELEASE)
2015-03-10 15:35:43.661 INFO 5784 --- [ main] hello.Application : Starting Application on Gportpers3 with PID 5784 (started by ST in C:\Users\Serge Tahé\Documents\workspace-sts-3.6.3.RELEASE\gs-accessing-data-jpa-complete)
2015-03-10 15:35:43.708 INFO 5784 --- [ main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d11346a: startup date [Tue Mar 10 15:35:43 CET 2015]; root of context hierarchy
2015-03-10 15:35:45.230 INFO 5784 --- [ main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2015-03-10 15:35:45.254 INFO 5784 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [
name: default
...]
2015-03-10 15:35:45.331 INFO 5784 --- [ main] org.hibernate.Version : HHH000412: Hibernate Core {4.3.8.Final}
2015-03-10 15:35:45.332 INFO 5784 --- [ main] org.hibernate.cfg.Environment : HHH000206: hibernate.properties not found
2015-03-10 15:35:45.334 INFO 5784 --- [ main] org.hibernate.cfg.Environment : HHH000021: Bytecode provider name : javassist
2015-03-10 15:35:45.651 INFO 5784 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
2015-03-10 15:35:45.754 INFO 5784 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2015-03-10 15:35:45.877 INFO 5784 --- [ main] o.h.h.i.ast.ASTQueryTranslatorFactory : HHH000397: Using ASTQueryTranslatorFactory
2015-03-10 15:35:46.154 INFO 5784 --- [ main] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl schema export
2015-03-10 15:35:46.169 INFO 5784 --- [ main] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export complete
2015-03-10 15:35:46.779 INFO 5784 --- [ main] o.s.j.e.a.AnnotationMBeanExporter : Registering beans for JMX exposure on startup
Customers found with findAll():
-------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']
Customer found with findOne(1L):
--------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer found with findByLastName('Bauer'):
--------------------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']
2015-03-10 15:35:47.040 INFO 5784 --- [ main] hello.Application : Started Application in 3.623 seconds (JVM running for 4.324)
2015-03-10 15:35:47.042 INFO 5784 --- [ Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d11346a: startup date [Tue Mar 10 15:35:43 CET 2015]; root of context hierarchy
2015-03-10 15:35:47.044 INFO 5784 --- [ Thread-1] o.s.j.e.a.AnnotationMBeanExporter : Unregistering JMX-exposed beans on shutdown
2015-03-10 15:35:47.046 INFO 5784 --- [ Thread-1] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2015-03-10 15:35:47.047 INFO 5784 --- [ Thread-1] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl schema export
2015-03-10 15:35:47.051 INFO 5784 --- [ Thread-1] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export complete
- الأسطر 1-8: شعار مشروع Spring Boot؛
- السطر 9: يتم تنفيذ الفئة [hello.Application]؛
- السطر 10: [AnnotationConfigApplicationContext] هي فئة تُنفِّذ واجهة [ApplicationContext] الخاصة بـ Spring. وهي عبارة عن حاوية للـ beans؛
- السطر 11: يتم تنفيذ البين [entityManagerFactory] بواسطة الفئة [LocalContainerEntityManagerFactory]، وهي فئة تابعة لـ Spring. وهي تدير الطبقة [JPA]؛
- السطر 12: يظهر [Hibernate]. وقد تم اختيار هذا التنفيذ JPA؛
- السطر 19: لهجة Hibernate هي المتغير SQL المراد استخدامه مع SGBD. هنا، تُظهر اللهجة [H2Dialect] أن Hibernate سيعمل مع SGBD وH2؛
- السطران 21-22: تم إنشاء قاعدة البيانات. يتم إنشاء الجدول [CUSTOMER]. وهذا يعني أن Hibernate قد تم تكوينه لإنشاء الجداول استنادًا إلى التعريفات JPA، وهنا التعريف JPA للفئة [Customer]؛
- الأسطر 26-30: نتيجة طريقة [findAll] الخاصة بالواجهة؛
- السطر 34: نتيجة الطريقة [findOne] الخاصة بالواجهة؛
- الأسطر 38-39: نتائج الطريقة [findByLastName]؛
- السطر 41 وما يليه: سجلات إغلاق سياق Spring.
5.1.5. التكوين اليدوي لمشروع Spring Data
نقوم بنسخ المشروع السابق إلى المشروع [gs-accessing-data-jpa-02]:
![]() |
في هذا المشروع الجديد، لن نعتمد على التكوين التلقائي الذي يقوم به Spring Boot. سنقوم به يدويًا. قد يكون ذلك مفيدًا إذا كانت الإعدادات الافتراضية لا تناسبنا.
أولاً، سنقوم بتحديد التبعيات الضرورية في الملف [pom.xml]:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-accessing-data-jpa-02</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
</parent>
<dependencies>
<!-- Spring Data -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<!-- قاعدة بيانات H2 -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<!-- Tomcat JDBC -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
</dependency>
</dependencies>
<properties>
<!-- استخدم UTF-8 لكل شيء -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</repository>
<repository>
<id>org.jboss.repository.releases</id>
<name>JBoss Maven Release Repository</name>
<url>https://repository.jboss.org/nexus/content/repositories/releases</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
- الأسطر 10-14: مشروع Maven الأصلي الذي سنستخدم المكتبات التي يحددها؛
- الأسطر 18-21: Spring Data المستخدم للوصول إلى قاعدة البيانات؛
- الأسطر 23-26: تنفيذ Hibernate لمواصفة JPA؛
- الأسطر 28-31: SGBD و H2؛
- الأسطر 33-36: غالبًا ما تُستخدم قواعد البيانات مع مجموعات من الاتصالات المفتوحة التي تتجنب فتح وإغلاق الاتصالات بشكل متكرر. هنا، يتم استخدام تنفيذ [tomcat-jdbc]؛
في المشروع الجديد، لا تتغير الكيان [Customer] والواجهة [CustomerRepository]. سنقوم بتعديل الفئة [Application] التي سيتم تقسيمها إلى فئتين:
- [Config]، والتي ستكون فئة التكوين:
- [Main] والتي ستكون فئة الملف القابل للتنفيذ؛
![]() |
أصبحت الفئة القابلة للتنفيذ [Application] كما يلي:
package console;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import repositories.CustomerRepository;
import config.AppConfig;
import entities.Customer;
public class Application {
public static void main(String[] args) {
// إنشاء مثيل لسياق Spring
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
CustomerRepository repository = context.getBean(CustomerRepository.class);
// حفظ عدد من العملاء
repository.save(new Customer("Jack", "Bauer"));
repository.save(new Customer("Chloe", "O'Brian"));
repository.save(new Customer("Kim", "Bauer"));
repository.save(new Customer("David", "Palmer"));
repository.save(new Customer("Michelle", "Dessler"));
...
// إغلاق السياق
context.close();
}
}
- السطر 9: لم تعد الفئة [Application] تحتوي على أي تعليقات توضيحية للتكوين؛
- الأسطر 3-7: تجدر الإشارة إلى أنه لم يعد هناك استيراد لحزم [Spring Boot]؛
- السطر 12: يتم إنشاء مثيلات لـ «بيانات» Spring. نحصل على سياق Spring الذي يحتوي على مرجع «البيانات» التي تم إنشاؤها؛
- السطر 13: نطلب مرجعًا لـ bean من النوع [CustomerRepository]؛
الفئة [Config] التي تقوم بتكوين المشروع هي كما يلي:
package config;
import javax.persistence.EntityManagerFactory;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = { "repositories" })
@Configuration
// @ComponentScan(basePackages={"package1","package2"})
public class AppConfig {
// قاعدة البيانات H2
@Bean
public DataSource dataSource() {
// مصدر البيانات TomcatJdbc
DataSource dataSource = new DataSource();
// تكوين الوصول JDBC
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:./demo");
dataSource.setUsername("sa");
dataSource.setPassword("");
// اتصال مفتوح في البداية
dataSource.setInitialSize(1);
// النتيجة
return dataSource;
}
// المزود JPA
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(false);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.H2);
return hibernateJpaVendorAdapter;
}
// EntityManagerFactory
@Bean
public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(jpaVendorAdapter);
factory.setPackagesToScan("entities");
factory.setDataSource(dataSource);
factory.afterPropertiesSet();
return factory.getObject();
}
// مدير المعاملات
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}
- السطر 17: تشير التعليقة التوضيحية [@EnableTransactionManagement] إلى أنه يجب تفسير التعليقات التوضيحية [@Transactional]. وتحتوي طرق واجهات [CrudRepository] على هذه التعليقات التوضيحية. وبالتالي، يتم تنفيذها داخل معاملة؛
- السطر 18: يسمح التعليق التوضيحي [@EnableJpaRepositories] بتحديد المجلدات التي توجد فيها واجهات Spring Data [CrudRepository]. ستصبح هذه الواجهات مكونات Spring وستكون متاحة في سياقها؛
- السطر 19: تعمل العلامة [@Configuration] على تحويل الفئة [Config] إلى فئة تكوين Spring؛
- السطر 20: تسمح العلامة التوضيحية [@ComponentScan] بإدراج المجلدات التي يجب البحث فيها عن مكونات Spring. مكونات Spring هي فئات مُعلَّمة بعلامات Spring مثل @Service و@Component و@Controller، ... ولا توجد هنا أي مكونات أخرى غير تلك المُعرَّفة داخل الفئة [AppConfig]، ولذلك تم وضع العلامة في التعليقات؛
- الأسطر 24-37: تحدد مصدر البيانات، وهو قاعدة البيانات H2. إن التعليق التوضيحي @Bean في السطر 25 هو الذي يجعل الكائن الذي تم إنشاؤه بواسطة هذه الطريقة مكونًا يديره Spring. يمكن أن يكون اسم الطريقة هنا أي اسم. ومع ذلك، يجب أن يُسمى [dataSource] إذا كانت الطريقة EntityManagerFactory في السطر 51 غير موجودة وتم تعريفها عن طريق التكوين التلقائي؛
- السطر 30: ستسمى قاعدة البيانات [demo] وسيتم إنشاؤها في مجلد المشروع؛
- الأسطر 40-47: تحدد التنفيذ JPA المستخدم، وهو هنا تنفيذ Hibernate. يمكن أن يكون اسم الأسلوب هنا أي اسم؛
- السطر 43: لا توجد سجلات SQL؛
- السطر 44: سيتم إنشاء قاعدة البيانات إذا لم تكن موجودة؛
- الأسطر 50-58: تحدد EntityManagerFactory التي ستدير استمرارية JPA. يجب أن يُسمى الأسلوب بالضرورة [entityManagerFactory]؛
- السطر 51: تتلقى الطريقة معلمتين من النوع الخاص بالفاصوليتين المحددتين سابقًا. سيتم بعد ذلك إنشاء هاتين الفاصوليتين ثم حقنهما بواسطة Spring كمعلمات للطريقة؛
- السطر 53: يحدد التنفيذ JPA المستخدم؛
- السطر 54: يحدد المجلدات التي توجد فيها الكيانات JPA؛
- السطر 55: يحدد مصدر البيانات المراد إدارته؛
- الأسطر 61-66: مدير المعاملات. يجب أن يُسمى الأسلوب بالضرورة [transactionManager]. ويتلقى كمعلمة «البيان» الموجود في الأسطر 51-58؛
- السطر 64: يتم ربط مدير المعاملات بـ EntityManagerFactory؛
يمكن تعريف الطرق السابقة بأي ترتيب.
يؤدي تنفيذ المشروع إلى نفس النتائج. يظهر ملف جديد في مجلد المشروع، وهو ملف قاعدة البيانات H2:
![]() |
5.1.6. إنشاء أرشيف قابل للتنفيذ
لإنشاء أرشيف قابل للتنفيذ للمشروع، يمكن اتباع الخطوات التالية:
![]() |
- في [1]: يتم إنشاء تكوين للتنفيذ؛
- في [2]: من النوع [Java Application]
- في [3]: يحدد المشروع المراد تنفيذه (استخدم الزر Browse)؛
- في [4]: يشير إلى الفئة المراد تنفيذها؛
- في [5]: اسم تكوين التنفيذ – يمكن أن يكون أي اسم؛
![]() |
- في [6]: يتم تصدير المشروع؛
- في [7]: في شكل أرشيف JAR قابل للتنفيذ؛
- في [8]: يحدد المسار واسم الملف القابل للتنفيذ المراد إنشاؤه؛
- في [9]: اسم إعدادات التشغيل التي تم إنشاؤها في [5]؛
10 ![]() |
- في [10]، الأرشيف الذي تم إنشاؤه؛
بعد ذلك، نفتح نافذة الأوامر في المجلد الذي يحتوي على الأرشيف القابل للتنفيذ:
يتم تشغيل الملف المضغوط بالطريقة التالية:
.....\dist>java -jar gs-accessing-data-jpa-02.jar
والنتائج التي تظهر في نافذة الأوامر هي كما يلي:
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder لمزيد من التفاصيل.
mars 10, 2015 5:27:20 PM org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [
name: default
...]
mars 10, 2015 5:27:20 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.3.8.Final}
mars 10, 2015 5:27:20 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
mars 10, 2015 5:27:20 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
mars 10, 2015 5:27:22 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
mars 10, 2015 5:27:22 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
mars 10, 2015 5:27:22 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000228: Running hbm2ddl schema update
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000102: Fetching database metadata
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000396: Updating schema
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000232: Schema update complete
Customers found with findAll():
-------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']
Customer found with findOne(1L):
--------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer found with findByLastName('Bauer'):
--------------------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']
5.1.7. إنشاء مشروع [Spring Data]
لإنشاء هيكل مشروع Spring Data، يمكن اتباع الخطوات التالية:
![]() |
- في [1]، يتم إنشاء مشروع جديد؛
- في [2]: من النوع [Spring Starter Project]؛
- سيكون المشروع الذي تم إنشاؤه مشروعًا من نوع Maven. في [3]، نحدد اسم مجموعة المشروع؛
- في [4]: يُحدد اسم العنصر (ملف jar في هذه الحالة) الذي سيتم إنشاؤه عند بناء المشروع؛
- في [5]: اسم المشروع في Eclipse – يمكن أن يكون أي اسم (لا يجب أن يكون مطابقًا لـ [4])؛
- في [7]: يُشار إلى أنه سيتم إنشاء مشروع يحتوي على طبقة [JPA] مع SGBD وMySQL. وسيتم عندئذٍ تضمين التبعيات اللازمة لمثل هذا المشروع في الملف [pom.xml]؛
![]() |
- في [8]، أدخل اسم مجلد المشروع؛
- في [9]، قم بإنهاء المعالج؛
![]() |
- في [10]: المشروع الذي تم إنشاؤه؛
يضم الملف [pom.xml] التبعيات اللازمة لمشروع JPA:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.springdata</groupId>
<artifactId>intro-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>intro-spring-data-01</name>
<description>démo spring data avec table de produits</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/> <!-- البحث عن العنصر الأصلي من المستودع -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>demo.IntroSpringData01Application</start-class>
<java.version>1.7</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- الأسطر 14-19: مشروع Maven الأصلي؛
- الأسطر 28-31: التبعية اللازمة لـ JPA – ستشمل [Spring Data]؛
- الأسطر 32-36: التبعية على برنامج التشغيل JDBC الخاص بـ MySQL؛
- الأسطر 37-41: التبعيات الضرورية لاختبارات JUnit المدمجة مع Spring؛
الفئة القابلة للتنفيذ [Application] لا تقوم بأي شيء ولكنها مُعدة مسبقًا:
package demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class IntroSpringData01Application {
public static void main(String[] args) {
SpringApplication.run(IntroSpringData01Application.class, args);
}
}
- التعليق التوضيحي [@SpringBootApplication] يجعل الفئة فئة ذاتية التهيئة للمشروع؛
فئة الاختبارات [ApplicationTests] لا تقوم بأي شيء ولكنها مُهيأة مسبقًا:
package demo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = IntroSpringData01Application.class)
public class IntroSpringData01ApplicationTests {
@Test
public void contextLoads() {
}
}
- السطر 9: تسمح العلامة [@SpringApplicationConfiguration] باستخدام ملف التكوين [IntroSpringData01Application]. وبذلك ستستفيد فئة الاختبار من جميع الفاصوليا المحددة في هذا الملف؛
- السطر 8: تسمح العلامة [@RunWith] بدمج Spring مع JUnit: سيتمكن تنفيذ الفئة كاختبار JUnit. [@RunWith] هو تعليق توضيحي لـ JUnit (السطر 4) في حين أن الفئة [SpringJUnit4ClassRunner] هي فئة Spring (السطر 6)؛
والآن بعد أن أصبح لدينا هيكل تطبيق JPA، يمكننا استكماله.




















