TP4.1 - دمج PostgreSQL مع Jakarta Persistence
الانتقال إلى قاعدة بيانات الإنتاج
الأهداف
سيمكنك هذا العمل العملي من:
- دمج PostgreSQL مع Spring Boot
- استخدام Jakarta Persistence (JPA) لحفظ البيانات
- تكوين Hibernate كـ ORM
- إنشاء وإدارة قاعدة بيانات علائقية
- فهم الفرق بين Jakarta و Javax Persistence
- إتقان التكوين باستخدام application.properties و application.yml
جدول المحتويات
- تذكير سريع
- إضافة قاعدة البيانات
- اختبار مع PostgreSQL
- تمرين 1: Javax Persistence
- تمرين 2: تكوين YAML
- تمرين 3: Lombok
- تمرين 4: إعادة الإنشاء التلقائي
- تمرين 5: قاعدة بيانات أخرى
1. تذكير سريع
السياق
في الأعمال العملية السابقة، قمنا بإنشاء تطبيق CRUD كامل مع H2. الآن، سننتقل إلى PostgreSQL، وهي قاعدة بيانات علائقية احترافية مناسبة للإنتاج.
أهداف هذا العمل العملي
- الانتقال من H2 إلى PostgreSQL - الانتقال من قاعدة مدمجة إلى قاعدة إنتاج
- استخدام Jakarta Persistence (JPA) - ربط كائنات Java بالجداول SQL
- تكوين PostgreSQL - تثبيت وتكوين قاعدة بيانات احترافية
المتطلبات المسبقة
- إكمال TP3 (هيكلية CRUD)
- تثبيت PostgreSQL (الإصدار 12 أو أعلى)
- pgAdmin أو DBeaver لإدارة PostgreSQL
- Visual Studio Code مع ملحقات Java
2. إضافة قاعدة البيانات
2.1 إضافة التبعيات في pom.xml
افتح ملف pom.xml وأضف التبعيات التالية في قسم <dependencies>:
<!-- Driver PostgreSQL -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Spring Data JPA (inclut Jakarta Persistence) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
التفسير:
| التبعية | الدور |
|---|---|
postgresql | برنامج تشغيل JDBC للاتصال بـ PostgreSQL |
spring-boot-starter-data-jpa | يتضمن JPA و Hibernate و Spring Data |
ملاحظة: بعد الإضافة، سيقترح عليك VS Code إعادة تحميل مشروع Maven. اقبل أو نفذ:
mvn clean install -DskipTests
2.2 تكوين application.properties
قم بإنشاء أو تعديل ملف src/main/resources/application.properties:
# URL de connexion à la base de données
spring.datasource.url=jdbc:postgresql://localhost:5432/greeting_db?useSSL=false
# Credentials de connexion
spring.datasource.username=postgres
spring.datasource.password=postgres
# Configuration Hibernate
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# Stratégie de génération du schéma
spring.jpa.hibernate.ddl-auto=create
# Afficher les requêtes SQL (utile pour le développement)
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
تفسير الخصائص:
| الخاصية | الوصف | القيم الممكنة |
|---|---|---|
spring.datasource.url | URL اتصال JDBC | الصيغة: jdbc:postgresql://host:port/database |
spring.datasource.username | اسم مستخدم PostgreSQL | الافتراضي: postgres |
spring.datasource.password | كلمة مرور PostgreSQL | يجب تعديلها حسب التثبيت الخاص بك |
spring.jpa.hibernate.ddl-auto | استراتيجية إنشاء المخطط | none, validate, update, create, create-drop |
spring.jpa.show-sql | عرض استعلامات SQL في وحدة التحكم | true أو false |
قيم ddl-auto:
مهم: قم بتغيير greeting_db إلى اسم قاعدة البيانات الخاصة بك إذا لزم الأمر.
2.3 توضيح الكيان Greeting.java
قم بتعديل فئة Greeting.java لجعلها كيان JPA:
package com.example.demo;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "greeting")
public class Greeting {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "content", nullable = false)
private String content;
// Constructeur par défaut (requis par JPA)
public Greeting() {
}
// Constructeur avec paramètres
public Greeting(Long id, String content) {
this.id = id;
this.content = content;
}
// Getters et Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
تفسير التعليقات التوضيحية:
| التعليق التوضيحي | الدور |
|---|---|
@Entity | يحدد الفئة ككيان JPA (مرتبط بجدول) |
@Table(name = "greeting") | يحدد اسم الجدول SQL |
@Id | يحدد الحقل كمفتاح أساسي |
@GeneratedValue(strategy = GenerationType.IDENTITY) | زيادة تلقائية للمعرف |
@Column(name = "content", nullable = false) | تكوين العمود SQL |
هيكلية JPA:
2.4 إنشاء المستودع
قم بإنشاء واجهة GreetingRepository.java في الحزمة com.example.demo:
package com.example.demo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface GreetingRepository extends JpaRepository<Greeting, Long> {
// Spring Data JPA génère automatiquement l'implémentation
// Méthodes disponibles: save(), findAll(), findById(), delete(), etc.
}
الطرق المولدة تلقائيًا:
| الطريقة | الوصف |
|---|---|
save(Greeting g) | حفظ أو تحديث |
findAll() | استرجاع جميع الإدخالات |
findById(Long id) | استرجاع بواسطة المعرف |
deleteById(Long id) | حذف بواسطة المعرف |
count() | عد الإدخالات |
2.5 تعديل وحدة التحكم
قم بتعديل GreetingController.java لاستخدام المستودع:
package com.example.demo;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
private final GreetingRepository greetingRepository;
public GreetingController(GreetingRepository greetingRepository) {
this.greetingRepository = greetingRepository;
}
@GetMapping("/greeting")
public Greeting greeting(@RequestParam(value="name") String name) {
Greeting greeting = new Greeting(
counter.incrementAndGet(),
String.format(template, name)
);
// Sauvegarder dans la base de données
return greetingRepository.save(greeting);
}
}
تدفق البيانات:
2.6 إنشاء قاعدة بيانات PostgreSQL
الخيار أ: باستخدام pgAdmin
-
فتح pgAdmin
-
إنشاء مستخدم (اختياري):
- انقر بزر الماوس الأيمن على "Login/Group Roles"
- "Create" → "Login/Group Role"
- علامة التبويب "General": Name =
postgres - علامة التبويب "Definition": Password =
postgres - علامة التبويب "Privileges": تحديد "Can login?" و "Create databases?"
- حفظ
-
إنشاء قاعدة البيانات:
- انقر بزر الماوس الأيمن على "Databases"
- "Create" → "Database"
- Database name:
greeting_db - Owner:
postgres - حفظ
الخيار ب: باستخدام SQL (سطر الأوامر)
-- Créer la base de données
CREATE DATABASE greeting_db;
-- Créer l'utilisateur (si nécessaire)
CREATE USER postgres WITH PASSWORD 'postgres';
-- Accorder les privilèges
GRANT ALL PRIVILEGES ON DATABASE greeting_db TO postgres;
لتنفيذ هذه الأوامر:
# Windows
psql -U postgres
# macOS/Linux
sudo -u postgres psql
3. اختبار مع PostgreSQL
3.1 التحقق من التكوين
قبل تشغيل التطبيق، تحقق من:
قائمة التحقق:
- PostgreSQL قيد التشغيل
- قاعدة البيانات
greeting_dbموجودة - المستخدم
postgresلديه الصلاحيات الصحيحة - ملف
application.propertiesصحيح - الفئة
Greetingتحتوي على التعليق التوضيحي@Entity - الواجهة
GreetingRepositoryتم إنشاؤها - وحدة التحكم
GreetingControllerتستخدم المستودع
3.2 تشغيل التطبيق
في VS Code:
الطريقة 1: عبر الطرفية
# Nettoyer et compiler
mvn clean install
# Lancer l'application
mvn spring-boot:run
الطريقة 2: عبر الواجهة
- فتح
GreetingApplication.java - انقر على "Run" فوق طريقة
main - أو اضغط على F5
السجلات المتوقعة:
Hibernate: drop table if exists greeting cascade
Hibernate: create table greeting (id bigserial not null, content varchar(255) not null, primary key (id))
Started GreetingApplication in X.XXX seconds
3.3 اختبار النقاط النهائية
اختبار 1: إنشاء تحية
http://localhost:8080/greeting?name=John
الاستجابة المتوقعة:
{
"id": 1,
"content": "Hello, John!"
}
اختبار 2: إنشاء تحيات متعددة
http://localhost:8080/greeting?name=Alice
http://localhost:8080/greeting?name=Bob
http://localhost:8080/greeting?name=Charlie
3.4 التحقق في PostgreSQL
باستخدام pgAdmin:
- فتح pgAdmin
- التنقل: Servers → PostgreSQL → Databases → greeting_db → Schemas → public → Tables
- انقر بزر الماوس الأيمن على "greeting" → "View/Edit Data" → "All Rows"
باستخدام SQL:
-- Se connecter à la base
\c greeting_db
-- Afficher toutes les entrées
SELECT * FROM greeting;
-- Résultat attendu:
-- id | content
-- ---+----------------
-- 1 | Hello, John!
-- 2 | Hello, Alice!
-- 3 | Hello, Bob!
-- 4 | Hello, Charlie!
3.5 سجلات SQL في وحدة التحكم
بفضل spring.jpa.show-sql=true، سترى استعلامات SQL في وحدة التحكم:
Hibernate: insert into greeting (content) values (?)
4. تمرين 1: إعادة التنفيذ مع Javax Persistence
البيان
أعد تنفيذ المشروع باستخدام Javax Persistence بدلاً من Jakarta Persistence (الإصدار القديم من JPA).
الهدف
فهم الفرق بين Jakarta و Javax Persistence.
السياق التاريخي
التعليمات
الخطوة 1: تعديل pom.xml
أضف تبعية Javax Persistence:
<!-- Ajout de Javax Persistence -->
<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
<version>2.2</version>
</dependency>
الخطوة 2: تعديل الواردات في Greeting.java
استبدل:
import jakarta.persistence.*;
بـ:
import javax.persistence.*;
الخطوة 3: الفئة الكاملة مع Javax
package com.example.demo;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
@Entity
@Table(name = "greeting")
public class Greeting {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "content", nullable = false)
private String content;
public Greeting() {}
public Greeting(Long id, String content) {
this.id = id;
this.content = content;
}
// Getters et Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
الخطوة 4: الاختبار
- إعادة الترجمة:
mvn clean install -DskipTests - إعادة تشغيل التطبيق
- التحقق من أن كل شيء يعمل
أسئلة للتفكير
- ما هو الفرق الرئيسي بين Javax و Jakarta Persistence؟
- لماذا يوصى باستخدام Jakarta للمشاريع الجديدة؟
- هل يمكن خلط الاثنين في نفس المشروع؟
5. تمرين 2: التكوين مع application.yml
البيان
قم بتحويل تكوين application.properties إلى صيغة YAML.
الهدف
إتقان صيغة YAML لتكوين Spring Boot.
التعليمات
الخطوة 1: حذف application.properties
احذف أو أعد تسمية ملف application.properties.
الخطوة 2: إنشاء application.yml
قم بإنشاء src/main/resources/application.yml:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/greeting_db?useSSL=false
username: postgres
password: postgres
jpa:
hibernate:
ddl-auto: create
show-sql: true
properties:
hibernate:
jdbc:
lob:
non_contextual_creation: true
dialect: org.hibernate.dialect.PostgreSQLDialect
format_sql: true
الخطوة 3: الاختبار
أعد تشغيل التطبيق وتحقق من أن كل شيء يعمل.
المقارنة
application.properties:
spring.datasource.url=jdbc:postgresql://localhost:5432/greeting_db
spring.datasource.username=postgres
spring.jpa.hibernate.ddl-auto=create
application.yml:
spring:
datasource:
url: jdbc:postgresql://localhost:5432/greeting_db
username: postgres
jpa:
hibernate:
ddl-auto: create
مزايا YAML:
- أكثر قابلية للقراءة للهياكل الهرمية
- أقل تكرار
- صيغة حديثة
6. تمرين 3: التبسيط مع Lombok
البيان
قم بإزالة getters و setters والمنشئين اليدويين باستخدام Lombok.
الهدف
تقليل الكود المتكرر باستخدام Lombok.
المتطلبات المسبقة
الخطوة 1: إضافة تبعية Lombok في pom.xml
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
الخطوة 2: تثبيت ملحق Lombok في VS Code
- الملحقات → البحث عن "Lombok"
- تثبيت "Lombok Annotations Support for VS Code"
التعليمات
تعديل Greeting.java:
package com.example.demo;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Table(name = "greeting")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Greeting {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "content", nullable = false)
private String content;
// Lombok génère automatiquement:
// - Getters
// - Setters
// - toString()
// - equals() et hashCode()
// - Constructeur vide
// - Constructeur avec tous les arguments
}
التعليقات التوضيحية Lombok:
| التعليق التوضيحي | يولد |
|---|---|
@Data | Getters, Setters, toString, equals, hashCode |
@NoArgsConstructor | منشئ فارغ |
@AllArgsConstructor | منشئ مع جميع الحقول |
@Getter | فقط getters |
@Setter | فقط setters |
الاختبار
- إعادة الترجمة
- التحقق من أن التطبيق يعمل
- يتم توليد الطرق عند الترجمة
7. تمرين 4: إعادة الإنشاء التلقائي للقاعدة
البيان
قم بتكوين التطبيق ليعيد إنشاء قاعدة البيانات تلقائيًا عند كل بدء تشغيل.
الهدف
فهم استراتيجيات إدارة المخطط المختلفة.
التعليمات
في application.properties (أو application.yml):
spring.jpa.hibernate.ddl-auto=create-drop
أو
spring:
jpa:
hibernate:
ddl-auto: create-drop
مقارنة الاستراتيجيات
| الاستراتيجية | السلوك | الاستخدام |
|---|---|---|
none | لا يوجد إجراء | الإنتاج |
validate | التحقق من المخطط دون تعديل | الإنتاج |
update | تحديث المخطط | التطوير |
create | إنشاء المخطط عند البدء | الاختبارات |
create-drop | إنشاء عند البدء، حذف عند الإيقاف | الاختبارات الوحدوية |
الانتباه
create-drop يحذف جميع البيانات عند إيقاف التطبيق!
الاستخدام الموصى به:
# التطوير
spring:
jpa:
hibernate:
ddl-auto: update
# الإنتاج
spring:
jpa:
hibernate:
ddl-auto: validate
8. تمرين 5: الانتقال إلى MySQL، SQL Server أو Oracle
البيان
أعد تنفيذ الدمج الكامل مع قاعدة بيانات أخرى.
اختيار قاعدة البيانات
الخيار أ: MySQL
التبعية في pom.xml:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
تكوين application.properties:
spring.datasource.url=jdbc:mysql://localhost:3306/greeting_db?useSSL=false
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto=update
إنشاء القاعدة في MySQL:
CREATE DATABASE greeting_db;
CREATE USER 'root'@'localhost' IDENTIFIED BY 'root';
GRANT ALL PRIVILEGES ON greeting_db.* TO 'root'@'localhost';
FLUSH PRIVILEGES;
الخيار ب: SQL Server
التبعية في pom.xml:
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<scope>runtime</scope>
</dependency>
تكوين application.properties:
spring.datasource.url=jdbc:sqlserver://localhost:1433;databaseName=greeting_db
spring.datasource.username=sa
spring.datasource.password=YourStrong@Password
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.SQLServerDialect
spring.jpa.hibernate.ddl-auto=update
إنشاء القاعدة في SQL Server:
CREATE DATABASE greeting_db;
GO
ALTER LOGIN sa WITH PASSWORD = 'YourStrong@Password';
GO
USE greeting_db;
GO
الخيار ج: Oracle
التبعية في pom.xml:
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<scope>runtime</scope>
</dependency>
تكوين application.properties:
spring.datasource.url=jdbc:oracle:thin:@localhost:1521:xe
spring.datasource.username=system
spring.datasource.password=oracle
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.Oracle12cDialect
spring.jpa.hibernate.ddl-auto=update
إنشاء القاعدة في Oracle:
CREATE USER greeting_user IDENTIFIED BY oracle;
GRANT ALL PRIVILEGES TO greeting_user;
جدول المقارنة
| قاعدة البيانات | المنفذ الافتراضي | لهجة Hibernate | الصعوبة |
|---|---|---|---|
| PostgreSQL | 5432 | PostgreSQLDialect | سهل |
| MySQL | 3306 | MySQLDialect | سهل |
| SQL Server | 1433 | SQLServerDialect | متوسط |
| Oracle | 1521 | Oracle12cDialect | صعب |
التعليمات العامة
- تثبيت قاعدة البيانات المختارة
- تعديل pom.xml مع برنامج التشغيل الصحيح
- تعديل application.properties مع التكوين الصحيح
- إنشاء قاعدة البيانات والمستخدم
- إعادة الترجمة والاختبار
حل المشاكل
المشكلة 1: Connection Refused
الأعراض:
org.postgresql.util.PSQLException: Connection to localhost:5432 refused
الحلول:
- التحقق من أن PostgreSQL قيد التشغيل:
Windows:
# Dans Services, chercher PostgreSQL
services.msc
Linux:
sudo systemctl status postgresql
sudo systemctl start postgresql
- التحقق من المنفذ في
postgresql.conf
المشكلة 2: Authentication Failed
الأعراض:
PSQLException: FATAL: password authentication failed for user "postgres"
الحلول:
- التحقق من كلمة المرور في
application.properties - إعادة تعيين كلمة المرور:
ALTER USER postgres WITH PASSWORD 'nouveau_mot_de_passe';
المشكلة 3: Database Does Not Exist
الأعراض:
PSQLException: FATAL: database "greeting_db" does not exist
الحل:
إنشاء قاعدة البيانات:
CREATE DATABASE greeting_db;
المشكلة 4: Table Already Exists
الأعراض:
Table "greeting" already exists
الحل:
تغيير استراتيجية DDL:
spring.jpa.hibernate.ddl-auto=update
المشكلة 5: Lombok Ne Fonctionne Pas
الأعراض:
error: cannot find symbol
method getId()
الحلول:
- تثبيت ملحق Lombok في VS Code
- إعادة تشغيل VS Code
- التحقق من أن التبعية موجودة في pom.xml
- إعادة الترجمة:
mvn clean install -DskipTests spring-boot:run
أسئلة المراجعة
-
ما هو الفرق بين Jakarta و Javax Persistence؟
-
ماذا تفعل التعليمة @Entity؟
-
ما هو الفرق بين create و update و validate لـ ddl-auto؟
-
كيف يقوم Spring Data JPA بتوليد تنفيذ المستودع؟
-
ماذا تفعل التعليمة @GeneratedValue(strategy = GenerationType.IDENTITY)؟
-
لماذا يجب أن يكون لدينا منشئ فارغ في كيان JPA؟
-
ماذا تولد التعليمة @Data من Lombok؟
-
ما هو التكوين المستخدم في الإنتاج لـ ddl-auto؟
التسليمات
يجب تسليم:
- الكود المصدري الكامل مع PostgreSQL يعمل
- لقطات شاشة تظهر:
- التطبيق قيد التشغيل
- البيانات في pgAdmin
- استعلامات SQL في وحدة التحكم
- إكمال على الأقل 2 من التمارين
- وثيقة إجابات لأسئلة المراجعة
معايير التقييم
| المعيار | النقاط |
|---|---|
| دمج PostgreSQL يعمل | 25% |
| كيان Greeting معلق بشكل صحيح | 20% |
| المستودع تم إنشاؤه واستخدامه | 15% |
| البيانات محفوظة في PostgreSQL | 20% |
| التمارين مكتملة | 10% |
| أسئلة المراجعة مجاب عنها | 10% |
| المجموع | 100% |
الخطوات التالية
في العمل العملي القادم، سنرى:
- TP3: إنشاء API CRUD كامل
- العمليات: إنشاء، قراءة، تحديث، حذف
- إدارة الأخطاء
- التحقق من البيانات