# كيفية بناء نظام استيراد ملفات Excel قوي في Spring Boot باستخدام Apache POI

> تعلم كيفية استيراد ملفات Excel إلى قاعدة البيانات باستخدام إطار عمل Spring Boot ومكتبة Apache POI. اكتشف طرق قراءة الخلايا، والتحقق من البيانات، والإدراج المجمع.

- Canonical URL: https://coreiten.com/article/كيفية-بناء-نظام-استيراد-ملفات-excel-قوي-في-spring-boot-باستخدام-apache-poi
- Language: ar
- Section: اكسل
- Author: Sami
- Published: 2026-10-04T19:04:12+03:00
- Modified: 2026-10-04T19:04:12+03:00
- Publisher: CoreITen (https://coreiten.com)
- Keywords: Spring Boot Excel import, Apache POI, XSSFWorkbook, SAX event API, DataFormatter, Spring Data JPA, MultipartFile

## ملخص

بناء نظام قوي لاستيراد ملفات Excel في Spring Boot باستخدام Apache POI للتعامل مع معالجة الملفات والتحقق من البيانات والإدراج المجمع بكفاءة.

- يعتمد المشروع على Spring Boot 4.1.1، ولغة Java 25، ومكتبة Apache POI 5.5.1، وإطار عمل Hibernate 7.4.5.
- توفر مكتبة Apache POI طريقتين هما XSSFWorkbook للملفات الصغيرة وواجهة أحداث XSSF باستخدام SAX للملفات الكبيرة.
- تُعد وحدة poi-ooxml ضرورية في ملفات Maven للتعامل مع صيغة xlsx وتجلب التبعيات الأساسية تلقائياً.
- تُستخدم فئة DataFormatter لتحويل قيم الخلايا إلى نصوص دقيقة بغض النظر عن نوع الخلية الأساسي لتفادي الانهيارات.
- يتم التحقق من الصفوف عبر خطوتين تشملان تحويل القيم والتحقق من قيود Bean Validator قبل حفظ البيانات المجمعة.

**لماذا يهم:** يساعد هذا النظام المطورين على تجنب انهيار الخادم أو تلف البيانات عند معالجة ملفات المؤسسات الكبيرة.

---

يواجه المطورون الذين يبنون تطبيقات مؤسسية تحدياً متكرراً يتمثل في استيراد ملفات Excel إلى قاعدة البيانات (**Database**) دون التسبب في انهيار الخادم أو تلف البيانات. يتطلب التنفيذ القياسي عبر إطار عمل Spring Boot التعامل مع رفع الملفات، وقراءة تنسيقات الخلايا المعقدة، والتحقق من صحة كل صف على حدة، وحفظ البيانات السليمة مع إرسال تقرير دقيق بالأخطاء إلى المستخدم.

تتطلب هذه العملية بنية قوية قادرة على معالجة ملف بصيغة xlsx، والذي يُعد في الأساس أرشيفاً مضغوطاً لملفات XML، عبر طبقات تحقق متعددة. ومن خلال الاستفادة من مكتبة Apache POI، يمكن للمطورين استخراج البيانات بكفاءة، وتعيين العناوين ديناميكياً، وتنفيذ عمليات الإدراج المجمع باستخدام Spring Data JPA.

### كيف تعمل عملية استيراد Excel في Spring Boot؟

تعالج نقطة النهاية (**Endpoint**) الخاصة بالاستيراد الملف عبر أربع خطوات متميزة: قبول الملف المرفوع، وقراءة الخلايا، والتحقق من البيانات، وحفظ الصفوف الصالحة. أي مشكلة في الملف نفسه توقف عملية الاستيراد بالكامل، وتُرجع حالة HTTP من فئة 4xx. ومع ذلك، فإن أي مشكلة داخل صف واحد تُولد تقرير خطأ فقط، مما يسمح بحفظ باقي الصفوف السليمة.

توفر مكتبة Apache POI طريقتين أساسيتين لقراءة ورقة العمل. تبني الطريقة القياسية XSSFWorkbook كائناً لكل صف وخلية في الذاكرة، وهي مثالية للملفات الصغيرة التي تتطلب وصولاً عشوائياً أو تقييم المعادلات. في المقابل، تقوم واجهة برمجة التطبيقات (**API**) الخاصة بأحداث XSSF بتحليل ملف XML لورقة العمل باستخدام واجهة SAX، مع الاحتفاظ بالصف الحالي فقط في الذاكرة، مما يجعلها ضرورية للملفات الكبيرة.

### إعداد تبعيات Maven

لا يدير إطار عمل Spring Boot إصدار مكتبة Apache POI تلقائياً، لذا يجب تحديده صراحةً في تكوين المشروع. تُعد وحدة poi-ooxml ضرورية للتعامل مع ملفات بصيغة xlsx، وتقوم تلقائياً بجلب التبعيات الأساسية لمكتبة POI.

```xml
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-webmvc</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
  <groupId>org.apache.poi</groupId>
  <artifactId>poi-ooxml</artifactId>
  <version>5.5.1</version>
</dependency>
```

يستخدم [المشروع الكامل](https://github.com/lokeshgupta1981/Spring-Boot-Examples/tree/master/spring-boot-excel-import) إصدار Spring Boot 4.1.1، ولغة Java 25، ومكتبة Apache POI 5.5.1، وإطار عمل Hibernate 7.4.5. تجدر الإشارة إلى أنه في إصدار Spring Boot 4، يُسمى مشغل الويب رسمياً spring-boot-starter-webmvc.

### الكيان وتعيين العناوين

يجب هيكلة كيان قاعدة البيانات للتعامل مع عمليات الإدراج المجمع بكفاءة. يُعد استخدام مولد تسلسل (**Sequence Generator**) للمعرف أمراً بالغ الأهمية، حيث إن الاعتماد على عمود الهوية يعطل قدرات الإدراج المجمع في إطار عمل Hibernate.

```java
@Entity
public class Student {
  @Id
  @GeneratedValue(strategy = GenerationType.SEQUENCE)
  @SequenceGenerator(sequenceName = "student_seq", allocationSize = 50)
  private Long id;

  @Column(unique = true, nullable = false)
  private String rollNumber;
  private String name;
  private String email;
}
```

بدلاً من التحقق من الكيان مباشرة، يحتفظ سجل منفصل بالصف المحول جنباً إلى جنب مع رقم صف Excel الخاص به. يتيح ذلك تطبيق قيود التحقق من البيانات (**Data Validation**) قبل أن تصل البيانات إلى كيان قاعدة البيانات.

```java
public record StudentRow(
    int rowNumber,
    @NotBlank @Pattern(regexp = "\\d{1,6}", message = "must be a number with up to 6 digits") String rollNumber,
    @NotBlank @Email String email) {
}
```

### رفع الملف باستخدام MultipartFile

يتعامل نظام Spring مع رفع الملفات عبر واجهة MultipartFile. يجب على وحدة التحكم التحقق من امتداد الملف، ونسخ الملف المرفوع إلى ملف مؤقت، وتمرير هذا الملف الفعلي إلى طبقة الخدمة.

```java
@PostMapping(value = "/students/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public ImportReport importStudents(@RequestParam("file") MultipartFile file,
    @RequestParam(defaultValue = "false") boolean streaming) {

  String fileName = file.getOriginalFilename();
  if (file.isEmpty() || fileName == null || !fileName.toLowerCase().endsWith(".xlsx")) {
    throw new InvalidFileException("Upload a non-empty .xlsx file");
  }
  Path upload = null;
  try {
    upload = Files.createTempFile("students-", ".xlsx");
    file.transferTo(upload);
    return importService.importStudents(upload, reader, false);
  } catch (IOException | UnsupportedFileFormatException e) {
    throw new InvalidFileException("Not a readable .xlsx file");
  } finally {
    deleteQuietly(upload);
  }
}
```

من الضروري تزويد مكتبة Apache POI بملف فعلي بدلاً من تدفق إدخال (**InputStream**). يؤدي تمرير تدفق إدخال إلى إجبار مكتبة POI على تخزين الملف بأكمله في الذاكرة، مما يزيد من استهلاك الذاكرة بشكل كبير.

### قراءة الخلايا باستخدام DataFormatter

تُعد قراءة خلية باستخدام طريقة جلب خاطئة السبب الأكثر شيوعاً لانهيار عمليات استيراد Excel. على سبيل المثال، يؤدي استدعاء getStringCellValue() على خلية رقمية إلى طرح استثناء IllegalStateException.

```java
DataFormatter formatter = new DataFormatter();
formatter.formatCellValue(roll);           // "101"
formatter.formatCellValue(marks);          // "85.5"
formatter.formatCellValue(date);           // "6/15/24"
```

تحل فئة DataFormatter هذه المشكلة عن طريق إرجاع النص الدقيق الذي يعرضه برنامج Excel، بغض النظر عن نوع الخلية الأساسي. بالنسبة للتواريخ، يضمن توسيع DataFormatter لإرجاع تنسيقات ISO القياسية (مثل "2024-06-15") تحليلاً متسقاً في وقت لاحق من العملية.

### التحقق من الصفوف والإبلاغ عن الأخطاء

يخضع كل صف لعملية تحقق من خطوتين. أولاً، يتم تحويل القيم النصية إلى أنواعها المستهدفة (مثل Integer أو LocalDate). إذا فشل التحويل، يتم تسجيل الخطأ. بعد ذلك، تتحقق أداة Bean Validator من القيود المفروضة على السجل.

```java
for (ConstraintViolation<StudentRow> violation : validator.validate(student)) {
  String column = StudentColumn.headerOf(violation.getPropertyPath().toString());
  if (!badColumns.contains(column)) {
    errors.add(new RowError(row.rowNumber(), column, violation.getMessage()));
  }
}
```

المخرجات النهائية عبارة عن تقرير JSON يفصل إجمالي الصفوف المعالجة، وعدد عمليات الاستيراد الناجحة، ومصفوفة محددة من الأخطاء المعينة لأرقام صفوف وعناوين أعمدة دقيقة.

### التعامل مع التكرارات والإدراج المجمع

لمنع انتهاكات قيود قاعدة البيانات، يجب على النظام التحقق من المعرفات المكررة سواء داخل الملف المرفوع أو مقابل سجلات قاعدة البيانات الحالية. يمكن لاستعلام واحد لكل كتلة مكونة من 500 صف استرداد المعرفات الحالية بكفاءة.

```properties
spring.jpa.properties.hibernate.jdbc.batch_size=50
spring.jpa.properties.hibernate.order_inserts=true
```

من خلال تمكين الإدراج المجمع عبر JDBC في خصائص التطبيق، يجمع إطار عمل Hibernate عمليات إدراج متعددة في استدعاء واحد لقاعدة البيانات. تحفظ الخدمة الصفوف الصالحة في كتل، وتستدعي flush() لتنفيذ الدفعة و clear() لفصل الكيانات، مما يمنع تضخم الذاكرة.

### قراءة الملفات الكبيرة باستخدام واجهة SAX

بالنسبة لمجموعات البيانات الضخمة، تستهلك فئة XSSFWorkbook القياسية قدراً كبيراً من الذاكرة. تقوم واجهة برمجة التطبيقات (**API**) الخاصة بأحداث XSSF بتدفق محتوى XML، ومعالجة خلية واحدة في كل مرة من خلال SheetContentsHandler.

```java
XSSFReader reader = new XSSFReader(pkg);
ReadOnlySharedStringsTable strings = new ReadOnlySharedStringsTable(pkg);
try (InputStream sheet = reader.getSheetIterator().next()) {
  XMLReader parser = XMLHelper.newXMLReader();
  parser.setContentHandler(new XSSFSheetXMLHandler(
      reader.getStylesTable(), strings, new RowCollector(rows), new IsoDateFormatter(), false));
  parser.parse(new InputSource(sheet));
}
```

يقلل نهج التدفق هذا بشكل كبير من استهلاك الذاكرة، مما يجعل من الممكن معالجة مئات الآلاف من الصفوف على بيئات الخوادم المقيدة.

### الحد من حجم الرفع والاختبار

يقيد نظام Spring Boot عمليات الرفع بـ 1 ميجابايت لكل ملف افتراضياً. لاستيعاب أوراق Excel الأكبر حجماً، يجب زيادة هذه الحدود في خصائص التكوين.

```properties
spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=6MB
```

يتم التعامل مع اختبار منطق الاستيراد عبر MockMvc. بدلاً من تخزين الملفات الثنائية في المستودع، تنشئ مجموعة الاختبار بايتات Excel ديناميكياً باستخدام مكتبة POI، مما يضمن بقاء الاختبارات سريعة وموثوقة.

### فخ الذاكرة الذي يدمر عمليات النشر السحابية

الاكتشاف الأكثر أهمية في هذه البنية هو التفاوت الهائل في الذاكرة بين قراءة ملف Excel عبر تدفق إدخال (**InputStream**) مقابل ملف فعلي (**File**). عند معالجة ملف بحجم 3 ميجابايت يحتوي على 100,000 صف، تتطلب فئة XSSFWorkbook القياسية 768 ميجابايت ضخمة من ذاكرة التخزين المؤقت. يُعد هذا قاتلاً صامتاً لتطبيقات Spring Boot المعبأة في حاويات والتي تعمل في بيئات Kubernetes، حيث يتم فرض قيود الذاكرة بصرامة ويؤدي تجاوزها إلى إنهاء فوري للوحدات بسبب نفاد الذاكرة (OOM).

حتى عندما ينتقل المطورون إلى واجهة SAX عالية الكفاءة، فإنهم غالباً ما يقعون في فخ ثانوي: تمرير تدفق إدخال إلى OPCPackage. يؤدي القيام بذلك إلى إجبار مكتبة Apache POI على فك ضغط كل جزء XML إلى مصفوفات بايت أولاً، مما يرفع استهلاك الذاكرة إلى 96 ميجابايت. من خلال كتابة الملف المرفوع إلى ملف فعلي مؤقت على القرص وتمرير مرجع هذا الملف إلى محلل SAX، ينخفض استهلاك الذاكرة إلى 16 ميجابايت فقط. هذا الانخفاض بنسبة 97% في استهلاك الذاكرة هو الفارق بين تطبيق مؤسسي مستقر وتطبيق ينهار في كل مرة يرفع فيها قسم الموارد البشرية تقريراً ربع سنوي.

## المصادر

- [howtodoinjava.com](https://howtodoinjava.com/java/spring-boot-import-excel-to-database/)
