CodeGym /مدونة جافا /Random-AR /تطبيق السبات الأول الخاص بك
John Squirrels
مستوى
San Francisco

تطبيق السبات الأول الخاص بك

نشرت في المجموعة
في هذه المقالة، سوف تتعرف على أحد أطر عمل المؤسسات الأكثر شيوعًا لـ Java وستنشئ أول تطبيق Hibernate خاص بك. لم تسمع عن السبات؟ أو ربما سمعت عنه ولم تستخدمه؟ أو ربما حاولت استخدامه ولكنك فشلت؟ في جميع الحالات الثلاث - مرحبًا بكم في الجزء السفلي :) أول تطبيق للإسبات - 1 مرحبًا بالجميع! في هذه المقالة، سأتحدث عن الميزات الرئيسية لإطار عمل السبات وأساعدك على كتابة تطبيقك المصغر الأول. ولهذا نحتاج إلى:
  1. IntelliJ IDEA Ultimate Edition
    قم بتنزيله من الموقع الرسمي وتفعيل النسخة التجريبية لمدة 30 يومًا.
  2. PostgreSQL - أحد أشهر أنظمة إدارة قواعد البيانات الحديثة (DBMS)
  3. Maven (متصل بالفعل بـ IDEA)
  4. القليل من الصبر.
تحسبًا لذلك، قمت بنشر رمز التطبيق على GitHub . تستهدف المقالة في المقام الأول أولئك الذين لم يسبق لهم التعامل مع هذه التقنية من قبل، لذلك قمت بتقليل كمية التعليمات البرمجية. هيا بنا نبدأ!

ما هو السبات؟

إنها واحدة من أكثر تطبيقات رسم الخرائط الارتباطية للكائنات (ORM) شيوعًا. يحدد التعيين العلائقي للكائنات العلاقة بين كائنات البرنامج وسجلات قاعدة البيانات. بالطبع، يتمتع Hibernate بوظائف واسعة جدًا، لكننا سنركز على أبسط الوظائف. هدفنا هو إنشاء تطبيق CRUD (الإنشاء والقراءة والتحديث والحذف) الذي سيكون قادرًا على:
  1. إنشاء مستخدمين (User) والبحث عنهم في قاعدة البيانات عن طريق الهوية وتحديث بياناتهم في قاعدة البيانات وحذفهم من قاعدة البيانات.
  2. تعيين كائنات السيارة (تلقائي) للمستخدمين. إنشاء وتحديث والعثور على وحذف السيارات من قاعدة البيانات.
  3. بالإضافة إلى ذلك، يجب أن يقوم التطبيق تلقائيًا بإزالة السيارات "غير المالكة" من قاعدة البيانات. بمعنى آخر، عند حذف مستخدم، يجب أيضًا حذف جميع السيارات التابعة لذلك المستخدم من قاعدة البيانات.
سيتم تنظيم مشروعنا على النحو التالي: أول تطبيق للإسبات - 2كما ترون، لا يوجد شيء معقد. 6 فئات + ملف واحد مع التكوينات. أولاً، قم بإنشاء مشروع Maven جديد في IntelliJ IDEA. ملف -> مشروع جديد. حدد Maven من بين أنواع المشاريع المقترحة وانتقل إلى الخطوة التالية. أول تطبيق للإسبات - 3Apache Maven هو إطار عمل لبناء المشاريع تلقائيًا استنادًا إلى وصف بنيتها في ملفات POM. سيتم وصف البنية الكاملة لمشروعك في ملف pom.xml، وهو الملف الذي ستقوم IDEA بإنشائه في جذر مشروعك. في إعدادات المشروع، تحتاج إلى تحديد إعدادات Maven التالية: groupId وartifactId. في المشاريع، يكون معرف المجموعة عادةً وصفًا للشركة أو وحدة العمل. يمكن أن يظهر اسم المجال الخاص بالشركة أو موقع الويب هنا. وفي المقابل، artifactId هو اسم المشروع. بالنسبة إلى معرف المجموعة، يمكنك إدخال com.yourNickname.codegym. وهذا لن يكون له أي تأثير على التطبيق. بالنسبة إلى artifactId، اختر أي اسم مشروع تريده. يمكن ترك النسخة دون تغيير. أول تطبيق للإسبات - 4في الشاشة الأخيرة، ما عليك سوى تأكيد البيانات التي تم إدخالها مسبقًا. أول تطبيق للإسبات - 5لذلك أنشأنا المشروع. الآن كل ما تبقى علينا فعله هو كتابة بعض التعليمات البرمجية وتشغيلها :) أول الأشياء أولاً: إذا أردنا إنشاء تطبيق يعمل مع قاعدة بيانات، فلا يمكننا بالتأكيد الاستغناء عن قاعدة البيانات! قم بتنزيل PostgreSQL من هنا (أنا أستخدم الإصدار 9). يحتوي PostgreSQL على المستخدم الافتراضي "postgres" - ستحتاج إلى التفكير في كلمة مرور له عند التثبيت. لا تنسى كلمة المرور. سنحتاجها لاحقًا! (بشكل عام، يعد استخدام قاعدة البيانات الافتراضية في التطبيقات ممارسة سيئة، ولكننا سنفعل ذلك لتقليل عدد أسباب القرحة عن طريق إنشاء قاعدة البيانات الخاصة بك). إذا لم تكن صديقًا لسطر الأوامر واستعلامات SQL، فهناك أخبار جيدة. يوفر IntelliJ IDEA واجهة مستخدم مناسبة تمامًا للعمل مع قاعدة البيانات. يبدو الأمر كما يلي: أول تطبيق للإسبات - 6(موجود في الجزء الأيمن من IDEA، علامة التبويب قاعدة البيانات). لإنشاء اتصال، انقر فوق "+" وحدد مصدر بياناتنا (PostgeSQL). املأ الحقول الخاصة بالمستخدم وقاعدة البيانات ("postgres" لكليهما) وأدخل كلمة المرور التي تم تعيينها أثناء تثبيت PostgreSQL. إذا لزم الأمر، قم بتنزيل برنامج تشغيل Postgres. يمكنك القيام بذلك على نفس الصفحة. انقر فوق "اختبار الاتصال" للتحقق من إنشاء اتصال قاعدة البيانات. إذا رأيت "ناجح"، فانتقل. الآن سنقوم بإنشاء الجداول التي نحتاجها. سيكون هناك إجمالي اثنين: المستخدمين والسيارات. معلمات جدول المستخدمين: أول تطبيق للإسبات - 7لاحظ أن المعرف هو المفتاح الأساسي. إذا كنت لا تعرف ما هو المفتاح الأساسي في SQL، فابحث عنه في Google. هذا مهم. إعدادات جدول السيارات: أول تطبيق للإسبات - 8بالنسبة لجدول السيارات، تحتاج إلى تكوين مفتاح خارجي. سيكون بمثابة ربط جداولنا. أنصحك بقراءة المزيد عنها. ببساطة، فهو يشير إلى جدول خارجي، في حالتنا، المستخدمين. إذا كانت السيارة مملوكة للمستخدم بالمعرف = 1، فسيكون حقل user_id الخاص بالسيارات مساويًا لـ 1. وهذه هي الطريقة التي نربط بها المستخدمين بسياراتهم في تطبيقنا. في جدول السيارات الخاص بنا، سيكون حقل user_id بمثابة المفتاح الخارجي. سيشير إلى حقل المعرف الخاص بجدول المستخدمين. أول تطبيق للإسبات - 9لذلك، قمنا بإنشاء قاعدة بيانات تحتوي على جدولين. ما تبقى هو فهم كيفية إدارتها من كود Java. سنبدأ بملف pom.xml، حيث نحتاج إلى تضمين المكتبات الضرورية (في Maven تسمى التبعيات). يتم تخزين جميع المكتبات في مستودع Maven المركزي. المكتبات التي تحددها في pom.xml متاحة لك لاستخدامها في المشروع. يجب أن يبدو ملف pom.xml الخاص بك بهذا الشكل: أول تطبيق للإسبات - 10لا شيء معقد، كما ترون. أضفنا تبعيتين فقط — لاستخدام PostgreSQL وHbernate. الآن دعنا ننتقل إلى كود جافا. إنشاء كافة الحزم والفئات اللازمة في المشروع. للبدء، نحتاج إلى نموذج بيانات: the Userو Autoclasses.
package models;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table (name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
    @Column(name = "name")
    private String name;
    // You can omit the Column attribute if the name property matches the column name in the table
    private int age;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Auto> autos;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
        autos = new ArrayList<>();
    }

    public void addAuto(Auto auto) {
        auto.setUser(this);
        autos.add(auto);
    }

    public void removeAuto(Auto auto) {
        autos.remove(auto);
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public List<Auto> getAutos() {
        return autos;
    }

    public void setAutos(List<Auto> autos) {
        this.autos = autos;
    }

    @Override
    public String toString() {
        return "models.User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
package models;

import javax.persistence.*;

@Entity
@Table(name = "autos")
public class Auto {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @Column (name = "model")
    private String model;

    // You can omit the Column attribute if the name property matches the column name in the table
    private String color;


    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    public Auto() {
    }

    public Auto(String model, String color) {
        this.model = model;
        this.color = color;
    }

    public int getId() {
        return id;
    }

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public String toString() {
        return color + " " + model;
    }
}
كما ترون، تحتوي الفصول الدراسية على مجموعة من التعليقات التوضيحية الغامضة. دعونا نبدأ في الحفر فيها. بالنسبة لنا، التعليق التوضيحي الرئيسي هو @Entity. اقرأ عنها على ويكيبيديا واحفظها عن ظهر قلب. هذا هو أساس الأساس. يتيح هذا التعليق التوضيحي إمكانية تعيين كائنات فئة Java الخاصة بك إلى قاعدة بيانات. لكي تكون الفئة كيانًا، يجب أن تستوفي المتطلبات التالية:
  • يجب أن يحتوي على مُنشئ فارغ ( public​​أو protected)
  • لا يمكن أن تكون متداخلة، واجهة أوenum
  • لا يمكن أن يكون finalولا يمكن أن يحتوي على finalحقول/خصائص
  • يجب أن يحتوي على حقل @Id واحد على الأقل.
تحقق من فئات الكيانات الخاصة بك: فهي أماكن شائعة جدًا لإطلاق النار على قدمك. من السهل جدًا أن تنسى شيئًا ما. علاوة على ذلك، يمكن للجهة القيام بما يلي:
  • يمكن أن تحتوي على مُنشئات غير فارغة
  • يمكن أن ترث وتكون موروثة
  • يمكن أن يكون لها طرق أخرى وتنفيذ واجهات.
كما ترون، فإن Userالفصل مشابه جدًا لجدول المستخدمين. لديها id, name, والحقول age. لا تحتاج التعليقات التوضيحية الموجودة فوقها إلى أي شرح خاص: من الواضح أن @Id يشير إلى أن الحقل عبارة عن معرف لكائنات من هذه الفئة. يشير التعليق التوضيحي @Table الموجود أعلى الفئة إلى اسم الجدول الذي تمت كتابة الكائنات فيه. لاحظ التعليق الموجود أعلى حقل العمر: إذا كان اسم الحقل الموجود في الفصل هو نفس اسم الجدول، فيمكنك حذف التعليق التوضيحي @Column وسيعمل. أما بالنسبة للجزء المشار إليه بين الأقواس ("الاستراتيجية = GenerationType.IDENTITY"): هناك عدة استراتيجيات لتوليد المعرفات. يمكنك البحث عنها عبر جوجل، لكن بالنسبة لتطبيقنا، لا داعي للقلق. الشيء الرئيسي هو أنه بالنسبة لكائناتنا، سيتم إنشاء قيمة المعرف تلقائيًا. وفقًا لذلك، لا يوجد محدد للمعرف، ولا نقوم بتعيينه في المُنشئ أيضًا. ومع ذلك، هناك بعض الطرق التي Userيبرز بها الفصل. لديها قائمة من السيارات! أول تطبيق للإسبات - 11 التعليق التوضيحي @OneToMany معلق أعلى القائمة. هذا يعني أن عدة سيارات يمكن أن تتوافق مع نفس كائن فئة المستخدم. يشير العنصر "mappedBy" إلى حقل المستخدم الخاص بالفئة Auto. وهكذا، ترتبط السيارات والمستخدمين. يشير العنصر orphanRemoval إلى ما إذا كان سيتم تطبيق عملية الإزالة على الكيانات التي لم تعد لها علاقة. إذا قمنا بحذف مستخدم من قاعدة البيانات، فسيتم أيضًا حذف جميع السيارات المرتبطة به. في المقابل، في Autoالفصل، سترى حقل المستخدم مع التعليق التوضيحي @ManyToOne (يمكن لمستخدم واحد أن يتوافق مع العديد من السيارات) والتعليق التوضيحي @JoinColumn. فهو يشير إلى العمود الموجود في جدول autos الذي يتم استخدامه للإشارة إلى جدول المستخدمين (أي المفتاح الخارجي الذي تحدثنا عنه سابقًا). بعد إنشاء نموذج البيانات، حان الوقت لتعليم برنامجنا كيفية إجراء العمليات على البيانات الموجودة في قاعدة البيانات. لنبدأ بفئة الأداة المساعدة HibernateSessionFactoryUtil. لديها وظيفة واحدة فقط - إنشاء مصنع جلسة لتطبيقنا للعمل مع قاعدة البيانات (قل مرحبًا بنمط تصميم المصنع!). ولا يعرف كيف يفعل أي شيء آخر.
package utils;

import models.Auto;
import models.User;
import org.hibernate.SessionFactory;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.cfg.Configuration;

public class HibernateSessionFactoryUtil {
    private static SessionFactory sessionFactory;

    private HibernateSessionFactoryUtil() {}

    public static SessionFactory getSessionFactory() {
        if (sessionFactory == null) {
            try {
                Configuration configuration = new Configuration().configure();
                configuration.addAnnotatedClass(User.class);
                configuration.addAnnotatedClass(Auto.class);
                StandardServiceRegistryBuilder builder = new StandardServiceRegistryBuilder().applySettings(configuration.getProperties());
                sessionFactory = configuration.buildSessionFactory(builder.build());

            } catch (Exception e) {
                System.out.println("Exception!" + e);
            }
        }
        return sessionFactory;
    }
}
في هذه الفئة، نقوم بإنشاء كائن تكوين جديد، ونمرر إليه الفئات التي يجب أن يعاملها ككيانات: Userو Auto. انتبه إلى configuration.getProperties()الطريقة. ما هي الخصائص الأخرى هناك؟ من أين أتوا؟ الخصائص هي إعدادات السبات المشار إليها في ملف hibernate.cfg.xml الخاص. أول تطبيق للإسبات - 12تتم قراءة Hibernate.cfg.xml هنا: new Configuration().configure(); كما ترون، لا يوجد شيء خاص به: فهو يحتوي على معلمات للاتصال بقاعدة البيانات، بالإضافة إلى المعلمة show_sql. يعد هذا مطلوبًا حتى يتم عرض جميع استعلامات SQL التي يتم تنفيذها بواسطة Hibernate على وحدة التحكم. بهذه الطريقة سترى بالضبط ما يفعله Hibernate في أي لحظة، مما يزيل أي إحساس بـ "السحر". بعد ذلك نحتاج إلى UserDAOالفصل. أفضل الممارسات هي البرمجة من خلال الواجهات — إنشاء UserDAOواجهة منفصلة UserDAOImplوتنفيذها، لكنني سأتخطى ذلك لتقليل كمية التعليمات البرمجية. لا تفعل هذا في المشاريع الحقيقية! يعد نمط تصميم DAO (كائن الوصول إلى البيانات) أحد أكثر الأنماط شيوعًا. الفكرة بسيطة – قم بإنشاء طبقة تطبيق مسؤولة فقط عن الوصول إلى البيانات، لا أكثر. جلب البيانات من قاعدة البيانات، وتحديث البيانات، وحذف البيانات - هذا كل شيء. دراسة المزيد عن DAO. ستستخدم كائنات الوصول إلى البيانات باستمرار في عملك. ماذا يمكن UserDaoلفصلنا أن يفعل؟ حسنًا، مثل جميع المنظمات اللامركزية المستقلة، يمكنها العمل فقط مع البيانات. ابحث عن مستخدم حسب المعرف، أو قم بتحديث بياناته، أو احذفه، أو احصل على قائمة بجميع المستخدمين من قاعدة البيانات، أو احفظ مستخدمًا جديدًا في قاعدة البيانات - هذا هو مجمل وظائفه.
package dao;

import models.Auto;
import models.User;
import org.hibernate.Session;
import org.hibernate.Transaction;
import utils.HibernateSessionFactoryUtil;
import java.util.List;

public class UserDao {

    public User findById(int id) {
        return HibernateSessionFactoryUtil.getSessionFactory().openSession().get(User.class, id);
    }

    public void save(User user) {
        Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
        Transaction tx1 = session.beginTransaction();
        session.save(user);
        tx1.commit();
        session.close();
    }

    public void update(User user) {
        Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
        Transaction tx1 = session.beginTransaction();
        session.update(user);
        tx1.commit();
        session.close();
    }

    public void delete(User user) {
        Session session = HibernateSessionFactoryUtil.getSessionFactory().openSession();
        Transaction tx1 = session.beginTransaction();
        session.delete(user);
        tx1.commit();
        session.close();
    }

    public Auto findAutoById(int id) {
        return HibernateSessionFactoryUtil.getSessionFactory().openSession().get(Auto.class, id);
    }

    public List<User> findAll() {
        List<User> users = (List<User>) HibernateSessionFactoryUtil.getSessionFactory().openSession().createQuery("From User").list();
        return users;
    }
}
UserDaoأساليب متشابهة مع بعضها البعض. في معظمها، نحصل على كائن جلسة (جلسة اتصال بقاعدة البيانات) باستخدام مصنع الجلسة الخاص بنا، وننشئ معاملة واحدة داخل هذه الجلسة، ونجري عمليات معالجة البيانات اللازمة، ونحفظ نتيجة المعاملة في قاعدة البيانات، ثم نغلق الجلسة. الأساليب نفسها، كما ترون، بسيطة للغاية. DAO هو "قلب" طلبنا. ومع ذلك، لن نقوم بإنشاء DAO مباشرة ونستدعي أساليبه في طريقتنا main(). سيتم نقل كل المنطق إلى UserServiceالفصل.
package services;

import dao.UserDao;
import models.Auto;
import models.User;

import java.util.List;

public class UserService {

    private UserDao usersDao = new UserDao();

    public UserService() {
    }

    public User findUser(int id) {
        return usersDao.findById(id);
    }

    public void saveUser(User user) {
        usersDao.save(user);
    }

    public void deleteUser(User user) {
        usersDao.delete(user);
    }

    public void updateUser(User user) {
        usersDao.update(user);
    }

    public List<User> findAllUsers() {
        return usersDao.findAll();
    }

    public Auto findAutoById(int id) {
        return usersDao.findAutoById(id);
    }


}
الخدمة عبارة عن طبقة بيانات تطبيق مسؤولة عن تنفيذ منطق الأعمال. إذا كان برنامجك يحتاج إلى تنفيذ نوع ما من منطق الأعمال، فإنه يفعل ذلك من خلال الخدمات. تحتوي الخدمة على UserDaoأساليب DAO وتستدعيها في أساليبها. قد يبدو أننا نكرر الوظائف هنا (لماذا لا نستدعي الأساليب من كائن DAO فقط؟)، ولكن مع وجود الكثير من الكائنات والمنطق المعقد، فإن طبقات التطبيق توفر مزايا هائلة (القيام بذلك يعد ممارسة جيدة - تذكر ذلك في المستقبل واقرأ عن "طبقات التطبيق"). تتميز خدمتنا بمنطق بسيط، لكن طرق الخدمة في المشاريع الواقعية تحتوي على أكثر من سطر واحد من التعليمات البرمجية :) الآن لدينا كل ما تحتاجه لتشغيل التطبيق! في هذه main()الطريقة، لنقم بإنشاء مستخدم وسيارته، وربط أحدهما بالآخر، وحفظهما في قاعدة البيانات.
import models.Auto;
import models.User;
import services.UserService;

import java.sql.SQLException;

public class Main {
    public static void main(String[] args) throws SQLException {

        UserService userService = new UserService();
        User user = new User ("Jenny", 26);
        userService.saveUser(user);
        Auto ferrari = new Auto("Ferrari", "red");
        ferrari.setUser(user);
        user.addAuto(ferrari);
        Auto ford = new Auto("Ford", "black");
        ford.setUser(user);
        user.addAuto(ford);
        userService.updateUser(user);
    }
}
كما ترون، جدول المستخدمين له سجله الخاص، وجدول السيارات له سجله الخاص. أول تطبيق للإسبات - 13أول تطبيق للإسبات - 14دعونا نحاول إعادة تسمية مستخدمنا. امسح جدول المستخدمين وقم بتنفيذ التعليمات البرمجية
import models.Auto;
import models.User;
import services.UserService;

import java.sql.SQLException;

public class Main {
    public static void main(String[] args) throws SQLException {

        UserService userService = new UserService();
        User user = new User ("Jenny", 26);
        userService.saveUser(user);
        Auto ferrari = new Auto("Ferrari", "red");
        user.addAuto(ferrari);
        Auto ford = new Auto("Ford", "black");
        ford.setUser(user);
        user.addAuto(ford);
        userService.updateUser(user);
        user.setName ("Benny");
        userService.updateUser(user);
    }
}
إنها تعمل! أول تطبيق للإسبات - 15ماذا لو قمت بحذف المستخدم؟ امسح جدول المستخدمين (سوف تقوم السيارات بمسح نفسها) وتنفيذ التعليمات البرمجية
import models.Auto;
import models.User;
import services.UserService;

import java.sql.SQLException;

public class Main {
    public static void main(String[] args) throws SQLException {

        UserService userService = new UserService();
        User user = new User ("Jenny", 26);
        userService.saveUser(user);
        Auto ferrari = new Auto("Ferrari", "red");
        user.addAuto(ferrari);
        Auto ford = new Auto("Ford", "black");
        ford.setUser(user);
        user.addAuto(ford);
        userService.updateUser(user);
        user.setName ("Benny");
        userService.updateUser(user);
        userService.deleteUser(user);
    }
}
وجداولنا فارغة تمامًا (انتبه إلى وحدة التحكم - سيتم عرض جميع الطلبات التي يؤديها Hibernate هناك). يمكنك تجربة التطبيق وتجربة جميع وظائفه. على سبيل المثال، قم بإنشاء مستخدم بسيارات، واحفظه في قاعدة البيانات، وانظر إلى المعرف المخصص للمستخدم، وحاول استخدام هذا المعرف في طريقة main()جلب المستخدم من قاعدة البيانات وعرض قائمة بسياراته على وحدة التحكم . بالطبع، لم نر سوى جزء صغير من وظائف السبات. إمكانياتها واسعة جدًا، وكانت منذ فترة طويلة أداة صناعية قياسية لتطوير Java. إذا كنت ترغب في دراستها بالتفصيل، يمكنني أن أوصي بكتاب "Java Persistence API and Hibernate". لقد راجعت في مقال سابق. آمل أن تكون هذه المقالة مفيدة للقراء. إذا كان لديك أسئلة، اطرحها في التعليقات. سأكون سعيدًا بالإجابة :) ولا تنس أيضًا دعم المؤلف في المسابقة بنشر "أعجبني". أو الأفضل من ذلك - "أحبها" :) حظًا سعيدًا في دراستك!
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION