TP1 - إعداد مصدر البيانات ومجموعة الاتصالات
أهداف التمرين
في هذا التمرين، ستقوم بـ:
- ✅ إعداد مصدر بيانات H2 للتطوير
- ✅ إعداد مصدر بيانات PostgreSQL للإنتاج
- ✅ تحسين إعدادات HikariCP
- ✅ مراقبة مجموعة الاتصالات باستخدام Actuator
- ✅ محاكاة تشبع المجموعة
المدة المقدرة: 1 ساعة
المتطلبات المسبقة
- إكمال الوحدات من 1 إلى 6
- IntelliJ IDEA أو VS Code
- تثبيت PostgreSQL (أو Docker)
- Maven
الجزء 1: إنشاء المشروع
1.1 إنشاء المشروع باستخدام Spring Initializr
قم بإنشاء مشروع جديد مع:
- Artifact:
datasource-demo - Dependencies:
- Spring Web
- Spring Data JPA
- H2 Database
- PostgreSQL Driver
- Spring Boot Actuator
- Lombok
1.2 هيكل المشروع
datasource-demo/
├── src/main/java/com/example/datasourcedemo/
│ ├── DatasourceDemoApplication.java
│ ├── model/
│ │ └── Product.java
│ ├── repository/
│ │ └── ProductRepository.java
│ ├── service/
│ │ └── ProductService.java
│ └── controller/
│ └── ProductController.java
└── src/main/resources/
├── application.properties
├── application-dev.properties
└── application-prod.properties
الجزء 2: الكيان والمستودع
2.1 إنشاء الكيان Product
// src/main/java/com/example/datasourcedemo/model/Product.java
package com.example.datasourcedemo.model;
import jakarta.persistence.*;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "products")
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String description;
@Column(nullable = false, precision = 10, scale = 2)
private BigDecimal price;
@Column(nullable = false)
private Integer stock;
@Column(name = "created_at")
private LocalDateTime createdAt = LocalDateTime.now();
}
2.2 إنشاء المستودع
// src/main/java/com/example/datasourcedemo/repository/ProductRepository.java
package com.example.datasourcedemo.repository;
import com.example.datasourcedemo.model.Product;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
List<Product> findByStockLessThan(Integer threshold);
}
2.3 إنشاء الخدمة
// src/main/java/com/example/datasourcedemo/service/ProductService.java
package com.example.datasourcedemo.service;
import com.example.datasourcedemo.model.Product;
import com.example.datasourcedemo.repository.ProductRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
@Slf4j
public class ProductService {
private final ProductRepository productRepository;
public List<Product> getAllProducts() {
log.info("استرجاع جميع المنتجات");
return productRepository.findAll();
}
public Product createProduct(Product product) {
log.info("إنشاء المنتج: {}", product.getName());
return productRepository.save(product);
}
public Product getProduct(Long id) {
return productRepository.findById(id)
.orElseThrow(() -> new RuntimeException("المنتج غير موجود: " + id));
}
// طريقة لمحاكاة طلب بطيء
public List<Product> getAllProductsSlow() throws InterruptedException {
log.info("بدء الطلب البطيء...");
Thread.sleep(5000); // 5 ثواني
log.info("انتهاء الطلب البطيء");
return productRepository.findAll();
}
}
2.4 إنشاء المتحكم
// src/main/java/com/example/datasourcedemo/controller/ProductController.java
package com.example.datasourcedemo.controller;
import com.example.datasourcedemo.model.Product;
import com.example.datasourcedemo.service.ProductService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/products")
@RequiredArgsConstructor
public class ProductController {
private final ProductService productService;
@GetMapping
public List<Product> getAllProducts() {
return productService.getAllProducts();
}
@GetMapping("/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.getProduct(id);
}
@PostMapping
public Product createProduct(@RequestBody Product product) {
return productService.createProduct(product);
}
// نقطة نهاية لاختبار الاتصالات
@GetMapping("/slow")
public List<Product> getProductsSlow() throws InterruptedException {
return productService.getAllProductsSlow();
}
}
الجزء 3: إعداد التطوير (H2)
3.1 الإعداد الأساسي
# application.properties (إعداد مشترك)
# الملف الشخصي النشط افتراضيًا
spring.profiles.active=dev
# Actuator
management.endpoints.web.exposure.include=health,metrics,info
management.endpoint.health.show-details=always
# تسجيل الدخول
logging.level.com.zaxxer.hikari=DEBUG
logging.level.org.hibernate.SQL=DEBUG
3.2 إعداد H2 (التطوير)
# application-dev.properties
# ===== مصدر البيانات H2 =====
spring.datasource.url=jdbc:h2:mem:devdb;DB_CLOSE_DELAY=-1
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
# وحدة التحكم H2
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# ===== JPA =====
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
# ===== HIKARI (مجموعة الاتصالات) =====
spring.datasource.hikari.pool-name=DevPool
spring.datasource.hikari.minimum-idle=2
spring.datasource.hikari.maximum-pool-size=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=300000
spring.datasource.hikari.max-lifetime=600000
# اكتشاف التسريبات (30 ثانية)
spring.datasource.hikari.leak-detection-threshold=30000
3.3 اختبار
# بدء التطبيق
mvn spring-boot:run
# أو مع الملف الشخصي الصريح
mvn spring-boot:run -Dspring-boot.run.profiles=dev
التحقق:
- افتح http://localhost:8080/h2-console
- JDBC URL:
jdbc:h2:mem:devdb - الاتصال مع sa / (فارغ)
الجزء 4: إعداد الإنتاج (PostgreSQL)
4.1 بدء PostgreSQL مع Docker
# إنشاء وبدء PostgreSQL
docker run --name postgres-demo \
-e POSTGRES_USER=demo \
-e POSTGRES_PASSWORD=demo123 \
-e POSTGRES_DB=productdb \
-p 5432:5432 \
-d postgres:15
4.2 إعداد PostgreSQL
# application-prod.properties
# ===== مصدر البيانات POSTGRESQL =====
spring.datasource.url=jdbc:postgresql://localhost:5432/productdb
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=demo
spring.datasource.password=demo123
# وحدة التحكم H2 معطلة
spring.h2.console.enabled=false
# ===== JPA =====
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=false
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect
# ===== HIKARI (مجموعة محسنة للإنتاج) =====
spring.datasource.hikari.pool-name=ProdPool
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
spring.datasource.hikari.max-lifetime=1800000
# التحقق من الاتصالات
spring.datasource.hikari.connection-test-query=SELECT 1
# اكتشاف التسريبات (60 ثانية)
spring.datasource.hikari.leak-detection-threshold=60000
4.3 بدء التشغيل في وضع الإنتاج
# مع الملف الشخصي prod
mvn spring-boot:run -Dspring-boot.run.profiles=prod
الجزء 5: المراقبة باستخدام Actuator
5.1 نقاط النهاية HikariCP
ابدأ التطبيق واختبر هذه الروابط:
# الصحة العامة
curl http://localhost:8080/actuator/health
# مقاييس HikariCP
curl http://localhost:8080/actuator/metrics/hikaricp.connections
# الاتصالات النشطة
curl http://localhost:8080/actuator/metrics/hikaricp.connections.active
# الاتصالات غير النشطة
curl http://localhost:8080/actuator/metrics/hikaricp.connections.idle
# الاتصالات المعلقة
curl http://localhost:8080/actuator/metrics/hikaricp.connections.pending
# وقت الحصول على الاتصال
curl http://localhost:8080/actuator/metrics/hikaricp.connections.acquire
5.2 إنشاء نقطة نهاية للتشخيص
// src/main/java/com/example/datasourcedemo/controller/PoolInfoController.java
package com.example.datasourcedemo.controller;
import com.zaxxer.hikari.HikariDataSource;
import com.zaxxer.hikari.HikariPoolMXBean;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/pool")
@RequiredArgsConstructor
public class PoolInfoController {
private final DataSource dataSource;
@GetMapping("/info")
public Map<String, Object> getPoolInfo() {
HikariDataSource hikariDS = (HikariDataSource) dataSource;
HikariPoolMXBean poolMXBean = hikariDS.getHikariPoolMXBean();
Map<String, Object> info = new HashMap<>();
info.put("poolName", hikariDS.getPoolName());
info.put("maximumPoolSize", hikariDS.getMaximumPoolSize());
info.put("minimumIdle", hikariDS.getMinimumIdle());
info.put("activeConnections", poolMXBean.getActiveConnections());
info.put("idleConnections", poolMXBean.getIdleConnections());
info.put("totalConnections", poolMXBean.getTotalConnections());
info.put("threadsAwaitingConnection", poolMXBean.getThreadsAwaitingConnection());
return info;
}
}
اختبار:
curl http://localhost:8080/api/pool/info
النتيجة المتوقعة:
{
"poolName": "DevPool",
"maximumPoolSize": 5,
"minimumIdle": 2,
"activeConnections": 0,
"idleConnections": 2,
"totalConnections": 2,
"threadsAwaitingConnection": 0
}
الجزء 6: محاكاة تشبع المجموعة
6.1 الإعداد للاختبار
قم بتعديل application-dev.properties مؤقتًا:
# مجموعة صغيرة جدًا للاختبار
spring.datasource.hikari.maximum-pool-size=2
spring.datasource.hikari.connection-timeout=5000 # 5 ثواني
6.2 سكربت الاختبار
قم بإنشاء سكربت لإرسال عدة طلبات في وقت واحد:
# test-pool.sh (Linux/Mac)
#!/bin/bash
echo "إرسال 5 طلبات متزامنة..."
for i in {1..5}; do
curl -s http://localhost:8080/api/products/slow &
done
wait
echo "تم الانتهاء!"
أو باستخدام PowerShell (Windows):
# test-pool.ps1
Write-Host "إرسال 5 طلبات متزامنة..."
$jobs = @()
for ($i = 1; $i -le 5; $i++) {
$jobs += Start-Job -ScriptBlock {
Invoke-RestMethod -Uri "http://localhost:8080/api/products/slow"
}
}
$jobs | Wait-Job | Receive-Job
Write-Host "تم الانتهاء!"
6.3 مراقبة السجلات
HikariPool-1 - Pool stats (total=2, active=2, idle=0, waiting=3)
HikariPool-1 - Connection is not available, request timed out after 5001ms.
6.4 التصحيح
# زيادة المجموعة
spring.datasource.hikari.maximum-pool-size=10
# أو زيادة المهلة
spring.datasource.hikari.connection-timeout=60000
الجزء 7: بيانات الاختبار
7.1 إنشاء data.sql
-- src/main/resources/data.sql
INSERT INTO products (name, description, price, stock, created_at) VALUES
('Laptop Pro', 'حاسوب محمول عالي الأداء', 1299.99, 50, CURRENT_TIMESTAMP),
('Souris Sans Fil', 'فأرة بلوتوث مريحة', 49.99, 200, CURRENT_TIMESTAMP),
('Clavier Mécanique', 'لوحة مفاتيح للألعاب RGB', 129.99, 75, CURRENT_TIMESTAMP),
('Écran 27 pouces', 'شاشة 4K IPS', 399.99, 30, CURRENT_TIMESTAMP),
('Webcam HD', 'كاميرا 1080p مع ميكروفون', 79.99, 100, CURRENT_TIMESTAMP);
7.2 تفعيل التهيئة
# application-dev.properties
spring.jpa.defer-datasource-initialization=true
spring.sql.init.mode=always
التحقق النهائي
قائمة التحقق
- التطبيق يبدأ مع ملف التعريف
dev(H2) - وحدة التحكم H2 متاحة
- نقاط النهاية
/api/productsتعمل - مقاييس Actuator متاحة
-
/api/pool/infoيعيد إحصائيات المجموعة - اختبار التشبع يظهر انتهاء المهلة
- التطبيق يبدأ مع ملف التعريف
prod(PostgreSQL)
الاختبارات التي يجب إجراؤها
# 1. إنشاء منتج
curl -X POST http://localhost:8080/api/products \
-H "Content-Type: application/json" \
-d '{"name":"Test","price":99.99,"stock":10}'
# 2. سرد المنتجات
curl http://localhost:8080/api/products
# 3. التحقق من المجموعة
curl http://localhost:8080/api/pool/info
# 4. التحقق من الصحة
curl http://localhost:8080/actuator/health
ملخص
في هذا التمرين، تعلمت:
| المهارة | الوصف |
|---|---|
| إعداد متعدد ملفات التعريف | dev (H2) مقابل prod (PostgreSQL) |
| HikariCP | إعدادات مجموعة الاتصالات |
| Actuator | مراقبة المقاييس |
| التشخيص | نقطة نهاية مخصصة للمجموعة |
| اختبارات التحميل | محاكاة التشبع |
🎯 الخطوة التالية
الآن بعد أن أتقنت إعداد مصادر البيانات، تعلم كيفية إدارة المعاملات في التمرين التالي.