JavaRush /مدونة جافا /Random-AR /إضافة قاعدة بيانات PostgreSQL إلى خدمة RESTful على Spring...
Artur
مستوى
Tallinn

إضافة قاعدة بيانات PostgreSQL إلى خدمة RESTful على Spring Boot. الجزء 2

نشرت في المجموعة
إضافة قاعدة بيانات PostgreSQL إلى خدمة RESTful على Spring Boot. الجزء الأول إضافة قاعدة بيانات PostgreSQL إلى خدمة RESTful على Spring Boot.  الجزء 2 - 1 لذا، تعلمنا في الجزء الأخير كيفية تثبيت قاعدة بيانات PostgresSQL على جهاز الكمبيوتر، وإنشاء قاعدة بيانات في pgAdmin، وكذلك إنشاء الجداول وحذفها يدويًا وبرمجيًا. سنقوم في هذا الجزء بإعادة كتابة برنامجنا بحيث يتعلم كيفية العمل مع قاعدة البيانات والجداول هذه. لماذا نحن؟ لأني بنفسي أتعلم معكم من هذه المادة. وبعد ذلك، لن نقوم بحل المهمة المطروحة فحسب، بل سنقوم أيضًا بتصحيح الأخطاء التي تنشأ أثناء التنقل، وذلك بمساعدة نصيحة المبرمجين الأكثر خبرة. إذا جاز التعبير، سوف نتعلم العمل ضمن فريق؛) أولاً، لنقم بإنشاء com.javarush.lectures.rest_exampleحزمة جديدة في مجلد ونطلق عليها اسم repository. في هذه الحزمة سوف نقوم بإنشاء واجهة جديدة ClientRepository:
package com.javarush.lectures.rest_example.repository;

import com.javarush.lectures.rest_example.model.Client;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ClientRepository extends JpaRepository<Client, Integer>  {
}
سوف تتفاعل هذه الواجهة "بطريقة سحرية" مع قواعد البيانات والجداول الخاصة بنا. لماذا بطريقة سحرية؟ لأننا لن نحتاج إلى كتابة تنفيذه، وسيوفره لنا إطار عمل الربيع. تحتاج فقط إلى إنشاء مثل هذه الواجهة، ويمكنك بالفعل استخدام هذا "السحر". والخطوة التالية هي تحرير الفصل Clientمثل هذا:
package com.javarush.lectures.rest_example.model;

import javax.persistence.*;

@Entity
@Table(name = "clients")
public class Client {
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

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

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

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

    //… getters and setters
}
كل ما فعلناه في هذا الفصل هو مجرد إضافة بعض التعليقات التوضيحية. دعنا نذهب من خلالهم:
  • @Entity - يشير إلى أن هذه الحبة (الفئة) هي كيان.
  • @Table - يشير إلى اسم الجدول الذي سيتم عرضه في هذا الكيان.
  • @Id - معرف العمود (المفتاح الأساسي - القيمة التي سيتم استخدامها لضمان تفرد البيانات في الجدول الحالي. ملاحظة: Andrei )
  • @Column - يشير إلى اسم العمود الذي تم تعيينه لخاصية الكيان.
  • @GeneratedValue - يشير إلى أنه سيتم إنشاء هذه الخاصية وفقًا للاستراتيجية المحددة.
ليس من الضروري أن تتطابق أسماء حقول الجدول مع أسماء المتغيرات الموجودة في الفصل. على سبيل المثال، إذا كان لدينا متغير firstName، فسوف نقوم بتسمية الحقل في الجدول first_name. يمكن تعيين هذه التعليقات التوضيحية مباشرة في الحقول وعلى الحروف الخاصة بها. ولكن إذا اخترت إحدى هذه الأساليب، فحاول الحفاظ على هذا النمط طوال برنامجك. لقد استخدمت الطريقة الأولى فقط لتقصير القوائم. يمكن العثور هنا على قائمة أكثر اكتمالاً من التعليقات التوضيحية للعمل مع قواعد البيانات . الآن دعنا نذهب إلى الفصل ClientServiceImplونعيد كتابته على النحو التالي:
package com.javarush.lectures.rest_example.service;

import com.javarush.lectures.rest_example.model.Client;
import com.javarush.lectures.rest_example.repository.ClientRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class ClientServiceImpl implements ClientService {

    @Autowired
    private ClientRepository clientRepository;

    @Override
    public void create(Client client) {
        clientRepository.save(client);
    }

    @Override
    public List<Client>  readAll() {
        return clientRepository.findAll();
    }

    @Override
    public Client read(int id) {
        return clientRepository.getOne(id);
    }

    @Override
    public boolean update(Client client, int id) {
        if (clientRepository.existsById(id)) {
            client.setId(id);
            clientRepository.save(client);
            return true;
        }

        return false;
    }

    @Override
    public boolean delete(int id) {
        if (clientRepository.existsById(id)) {
            clientRepository.deleteById(id);
            return true;
        }
        return false;
    }
}
كما ترون من القائمة، كل ما فعلناه هو حذف الأسطر التي لم نعد بحاجة إليها:
// Хранorще клиентов
private static final Map<Integer, Client>  CLIENT_REPOSITORY_MAP = new HashMap<>();

// Переменная для генерации ID клиента
private static final AtomicInteger CLIENT_ID_HOLDER = new AtomicInteger();
لقد أعلنا عن واجهتنا بدلاً من ذلك ClientRepositoryوقمنا أيضًا بوضع التعليق التوضيحي @Autowired فوقها حتى يضيف Spring تلقائيًا هذه التبعية إلى فصلنا. لقد قمنا أيضًا بتفويض كل العمل لهذه الواجهة، أو بالأحرى تنفيذها، وهو ما سيضيفه Spring. دعنا ننتقل إلى المرحلة النهائية والأكثر إثارة للاهتمام - اختبار تطبيقنا. لنفتح برنامج Postman (انظر كيفية استخدامه هنا ) ونرسل طلب GET إلى هذا العنوان: http://localhost:8080/clients. نحصل على هذا الجواب:
[
    {
        "id": 1,
        "name": "Vassily Petrov",
        "email": "vpetrov@jr.com",
        "phone": "+7 (191) 322-22-33)"
    },
    {
        "id": 2,
        "name": "Pjotr Vasechkin",
        "email": "pvasechkin@jr.com",
        "phone": "+7 (191) 223-33-22)"
    }
]
نرسل طلب POST:
{
  "name" : "Amigo",
  "email" : "amigo@jr.com",
  "phone" : "+7 (191) 746-43-23"
}
و... اكتشفنا الخلل الأول في البرنامج:
{
    "timestamp": "2020-03-06T13:21:12.180+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
    "path": "/clients"
}
إضافة قاعدة بيانات PostgreSQL إلى خدمة RESTful على Spring Boot.  الجزء 2 - 2 نحن ننظر إلى السجلات ونجد الخطأ التالي:

org.postgresql.util.PSQLException: ОШИБКА: повторяющееся meaning ключа нарушает ограничение уникальности "clients_pkey"
  Detail: Ключ "(id)=(1)" уже существует.
نرسل نفس طلب POST مرة أخرى، والنتيجة هي نفسها، ولكن مع هذا الاختلاف: Ключ "(id)=(2)" уже существует. نرسل نفس الطلب للمرة الثالثة، ونحصل على الحالة: 201 تم الإنشاء. نرسل طلب GET مرة أخرى ونتلقى الرد:
[
    {
        "id": 1,
        "name": "Vassily Petrov",
        "email": "vpetrov@jr.com",
        "phone": "+7 (191) 322-22-33)"
    },
    {
        "id": 2,
        "name": "Pjotr Vasechkin",
        "email": "pvasechkin@jr.com",
        "phone": "+7 (191) 223-33-22)"
    },
    {
        "id": 3,
        "name": "Amigo",
        "email": "amigo@jr.com",
        "phone": "+7 (191) 746-43-23"
    }
]
يشير هذا إلى أن برنامجنا يتجاهل حقيقة أن هذا الجدول قد تم ملؤه مسبقًا ويقوم بتعيين معرف مرة أخرى بدءًا من واحد. حسنًا، الخطأ هو لحظة عمل، لا تيأس، فهذا يحدث كثيرًا. لذلك، سأطلب المساعدة من الزملاء الأكثر خبرة: "الزملاء الأعزاء، يرجى تقديم المشورة في التعليقات حول كيفية إصلاح ذلك حتى يعمل البرنامج بشكل طبيعي." لم تستغرق المساعدة وقتًا طويلاً للوصول، وأخبرني ستاس باسينكوف في التعليقات عن الاتجاه الذي يجب أن أتوجه إليه. شكر خاص له على هذا! ولكن الشيء هو أنني حددت بشكل غير صحيح في الفصل Clientاستراتيجية التعليق التوضيحي @GeneratedValue(strategy = GenerationType.IDENTITY)للحقل id. إضافة قاعدة بيانات PostgreSQL إلى خدمة RESTful على Spring Boot.  الجزء 2 - 3 هذه الإستراتيجية مناسبة لـ MySQL. إذا كنا نعمل مع Oracle أو PostrgeSQL، فسنحتاج إلى وضع استراتيجية مختلفة. يمكنك قراءة المزيد عن استراتيجيات المفاتيح الأساسية هنا . لقد اخترت استراتيجية GenerationType.SEQUENCE. لتنفيذ ذلك، سنحتاج إلى إعادة كتابة ملف initDB.sql قليلاً، وبالطبع التعليقات التوضيحية لحقل المعرف الخاص بفئة العميل. أعد كتابة initDB.sql:
CREATE TABLE IF NOT EXISTS clients
(
    id    INTEGER PRIMARY KEY ,
    name  VARCHAR(200) NOT NULL ,
    email VARCHAR(254) NOT NULL ,
    phone VARCHAR(50)  NOT NULL
);
CREATE SEQUENCE clients_id_seq START WITH 3 INCREMENT BY 1;
ما الذي تغير: لقد تغير نوع عمود المعرف في جدولنا، ولكن المزيد عن ذلك لاحقًا. أضفنا سطرًا أدناه نقوم فيه بإنشاء تسلسل جديد Client_id_seq، ونشير إلى أنه يجب أن يبدأ بالرقم ثلاثة (لأن المعرف الأخير في ملف populateDB.sql هو 2)، ونشير إلى أن الزيادة يجب أن تحدث بمقدار واحد. دعنا نعود إلى نوع عمود المعرف. لقد حددنا هنا عددًا صحيحًا، لأنه إذا تركنا SERIAL، فسيتم إنشاء التسلسل تلقائيًا بنفس الاسم Client_id_seq، ولكنه سيبدأ من واحد (مما أدى إلى خطأ البرنامج). ومع ذلك، الآن إذا كنت تريد حذف جدول، فستحتاج أيضًا إلى حذف هذا التسلسل إما يدويًا من خلال واجهة pgAdmin أو من خلال ملف .sql باستخدام الأوامر التالية:
DROP TABLE IF EXISTS clients;
DROP SEQUENCE IF EXISTS clients_id_seq
ولكن إذا لم تستخدم ملفًا مثل populateDB.sql لملء الجدول مبدئيًا، فيمكنك استخدام النوع SERIAL أو BIGSERIAL للمفتاح الأساسي، ولا يتعين عليك إنشاء التسلسل يدويًا، وبالتالي لا يتعين عليك حذفه بشكل منفصل. يمكنك قراءة المزيد عن التسلسلات على موقع الويب الخاص بـ. وثائق PostgreSQL . دعنا ننتقل إلى التعليقات التوضيحية لحقول idالفصل Clientونقوم بتنسيقها كما يلي:
@Id
@Column(name = "id")
@SequenceGenerator(name = "clientsIdSeq", sequenceName = "clients_id_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "clientsIdSeq")
private Integer id;
ما فعلناه: قمنا بتثبيت تعليق توضيحي جديد @SequenceGeneratorلإنشاء مولد تسلسل، وخصصنا له اسمًا clientsIdSeq، وأشرنا إلى أن هذا مولد لتسلسل clients_id_seq، وأضفنا سمة. allocationSize = 1 هذه سمة اختيارية، ولكن إذا لم نفعل ذلك، سنحصل على الخطأ التالي عند تشغيل البرنامج:

org.hibernate.MappingException: The increment size of the [clients_id_seq] sequence is set to [50] in the entity mapping while the associated database sequence increment size is [1]
إليك ما كتبه المستخدم Andrei حول هذا الأمر في التعليقات: يهدف تخصيص الحجم في المقام الأول إلى تقليل رحلة السبات إلى قاعدة البيانات للحصول على "معرف جديد". إذا كانت القيمة == 1، قم بوضع السبات لكل كيان جديد، قبل حفظه في قاعدة البيانات، "يتم تشغيله" إلى قاعدة البيانات للحصول على المعرف. إذا كانت القيمة > 1 (على سبيل المثال، 5)، فسوف يتصل السبات بقاعدة البيانات للحصول على معرف "جديد" بشكل أقل (على سبيل المثال، 5 مرات)، وعند الاتصال، سيطلب السبات من قاعدة البيانات حجز هذا الرقم (في موقعنا الحالة، 5) القيم. يشير الخطأ الذي وصفته إلى أن السبات يرغب في تلقي 50 معرفًا افتراضيًا، ولكن في قاعدة البيانات أشرت إلى أنك مستعد لإصدار معرف لهذا الكيان فقط وفقًا للمعرف الأول . تم اكتشاف خطأ آخر بواسطة المستخدم نيكوليا كودرياشوف : إذا قمت بتشغيل طلب من المقالة الأصلية http://localhost:8080/clients/1، فسيتم إرجاع الخطأ:
{
    "timestamp": "2020-04-02T19:20:16.073+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.javarush.lectures.rest_example.model.Client$HibernateProxy$ZA2m7agZ[\"hibernateLazyInitializer\"])",
    "path": "/clients/1"
}
يرتبط هذا الخطأ بالتهيئة البطيئة لـ Hibernate، وللتخلص منه، نحتاج إلى إضافة تعليق توضيحي إضافي إلى فئة العميل:
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
في هذا الطريق:
@Entity
@Table(name = "clients")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Client {.....}
لنقم الآن بتشغيل برنامجنا (بعد إزالة جدول العملاء من قاعدة البيانات إذا بقي هناك منذ آخر مرة)، ونعلق على 3 أسطر من ملف application.properties:
#spring.datasource.initialization-mode=ALWAYS
#spring.datasource.schema=classpath*:database/initDB.sql
#spring.datasource.data=classpath*:database/populateDB.sql
آخر مرة علقنا على السطر الأخير فقط، ولكن... نظرًا لأننا أنشأنا الجدول وملأناه بالفعل، فقد بدا هذا أكثر منطقية بالنسبة لي في الوقت الحالي. دعنا ننتقل إلى الاختبار وتنفيذ طلبات GET وPOST وPUT وDELETE عبر Postman، وسنرى أن الأخطاء قد اختفت وأن كل شيء يسير على ما يرام. هذا كل شيء، المهمة أنجزت. والآن يمكننا أن نلخص بإيجاز ونتأمل ما تعلمناه:
  • قم بتثبيت PostgreSQL على جهاز الكمبيوتر الخاص بك
  • إنشاء قواعد البيانات في pgAdmin
  • إنشاء وحذف الجداول يدوياً وبرمجياً
  • تعبئة الجداول عبر ملفات .sql
  • لقد تعلمنا القليل عن واجهة JpaRepository "السحرية" لإطار عمل Spring
  • لقد تعلمنا عن بعض الأخطاء التي قد تنشأ عند إنشاء مثل هذا البرنامج
  • لقد أدركنا أنه لا ينبغي لنا أن نشعر بالحرج من طلب المشورة من الزملاء
  • لقد أكدنا أن مجتمع JavaRush هو القوة التي ستهب دائمًا للإنقاذ؛)
يمكننا أن ننتهي هنا في الوقت الراهن. إضافة قاعدة بيانات PostgreSQL إلى خدمة RESTful على Spring Boot.  الجزء 2 - 4شكرا لكل من خصص وقتا لقراءة هذه المادة. ويسعدني أن أرى تعليقاتكم وملاحظاتكم وإضافاتكم ونقدكم البناء. وربما ستقدم حلولاً أكثر أناقة لهذه المشكلة، والتي أعدك بإضافتها إلى هذه المقالة عبر "الكلمة الرئيسية" الخاصة بـ UPD، مع الإشارة إليك كمؤلف بالطبع. حسنًا، بشكل عام، اكتب إذا أعجبك هذا المقال وهذا الأسلوب في تقديم المادة، وبشكل عام، ما إذا كان يجب علي الاستمرار في كتابة المقالات عن JR. فيما يلي الإضافات: UPD1: أوصى المستخدم جستنيان بشدة بإعادة تسمية الحزمة com.javarush.lectures.rest_exampleإلى com.javarush.lectures.rest.exampleواسم المشروع، حتى لا تنتهك اصطلاحات التسمية في Java. اقترح مستخدم UPD2 ألكسندر بيانوف أنه لتهيئة حقل ClientRepositoryفي الفصل الدراسي، ClientServiceImplمن الأفضل استخدام المُنشئ بدلاً من التعليق التوضيحي @Autowired. ويفسر ذلك حقيقة أنه في حالات نادرة يمكنك الحصول عليها NullPointerException، وبشكل عام، هذه أفضل الممارسات، وأنا أتفق معها. منطقيًا، إذا كان هناك حاجة إلى حقل للوظيفة الأولية لكائن ما، فمن الأفضل تهيئته في المُنشئ، لأن الفئة التي لا تحتوي على مُنشئ لن يتم تجميعها في كائن، لذلك ستتم تهيئة هذا الحقل في المرحلة من خلق الكائن. سأضيف جزءًا من التعليمات البرمجية مع التصحيحات (ما الذي يجب استبداله بماذا):
@Autowired
private ClientRepository clientRepository;

private final ClientRepository clientRepository;

public ClientServiceImpl(ClientRepository clientRepository) {
   this.clientRepository = clientRepository;
}
رابط الجزء الأول: إضافة قاعدة بيانات PostgreSQL إلى خدمة RESTful على Spring Boot. الجزء 1 ملاحظة: إذا كان أي منكم يريد الاستمرار في تطوير هذا التطبيق التعليمي، فسأكون سعيدًا بإضافة رابط إلى أدلةكم في هذه المقالة. ربما سينمو هذا البرنامج يومًا ما ليصبح شيئًا يشبه تطبيق أعمال حقيقيًا يمكنك إضافة عمل إليه إلى محفظتك. ملاحظة: بخصوص هذه المقالة المتواضعة، قررت أن أهدي هذا الاختبار للقلم إلى فتياتنا ونسائنا وسيداتنا الأعزاء. من يدري، ربما لن يكون هناك جافا الآن، ولا JavaRush، ولا برمجة في الطبيعة، لولا هذه المرأة . مبروك على إجازتك أيها الأذكياء الأعزاء! 8 مارس سعيد! كن سعيدا وجميلا!
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION