JavaRush /وبلاگ جاوا /Random-FA /افزودن پایگاه داده PostgreSQL به سرویس RESTful در Spring ...
Artur
مرحله
Tallinn

افزودن پایگاه داده PostgreSQL به سرویس RESTful در Spring Boot. قسمت 2

در گروه منتشر شد
افزودن پایگاه داده PostgreSQL به سرویس RESTful در Spring Boot. قسمت 1 افزودن پایگاه داده 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>  {
}
این رابط به طور "جادویی" با پایگاه داده ها و جداول ما تعامل خواهد داشت. چرا جادویی؟ زیرا نیازی به نوشتن پیاده سازی آن نخواهیم داشت و فریم ورک Spring آن را در اختیار ما قرار می دهد. شما فقط نیاز به ایجاد چنین رابط کاربری دارید و می توانید از این "جادو" استفاده کنید. مرحله بعدی 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 - نشان می دهد که این bean (کلاس) یک موجودیت است.
  • 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 به طور خودکار این وابستگی را به کلاس ما اضافه کند. ما همچنین تمام کارها را به این رابط یا بهتر است بگوییم پیاده سازی آن را واگذار کردیم که اسپرینگ آن را اضافه خواهد کرد. بیایید به آخرین و جالب ترین مرحله برویم - آزمایش برنامه ما. بیایید برنامه 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 Created را دریافت می کنیم. ما دوباره درخواست 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 و البته حاشیه نویسی فیلد id کلاس Client را بازنویسی کنیم. 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;
چه چیزی تغییر کرده است: نوع ستون id جدول ما تغییر کرده است، اما بعداً در مورد آن بیشتر توضیح خواهیم داد. ما یک خط در زیر اضافه کردیم که در آن یک دنباله جدید clients_id_seq ایجاد می کنیم، نشان می دهد که باید با سه شروع شود (زیرا آخرین شناسه در فایل populateDB.sql 2 است)، و نشان می دهد که افزایش باید با یک اتفاق بیفتد. بیایید به نوع ستون id برگردیم. در اینجا ما INTEGER را مشخص کردیم، زیرا اگر از SERIAL خارج شویم، دنباله به طور خودکار با همان نام clients_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]
در اینجا چیزی است که کاربر آندری در این مورد در نظرات می نویسد: allocationSize در درجه اول برای کاهش سفر hibernate به پایگاه داده برای "شناسه جدید" در نظر گرفته شده است. اگر مقدار == ​​1، hibernate برای هر موجودیت جدید، قبل از ذخیره آن در پایگاه داده، "اجرا" به پایگاه داده برای شناسه. اگر مقدار > 1 باشد (مثلاً 5)، hibernate برای شناسه «جدید» کمتر با پایگاه داده تماس می گیرد (مثلاً 5 بار)، و هنگام تماس، hibernate از پایگاه داده می خواهد که این شماره را رزرو کند (در ما مورد، 5) مقادیر. خطایی که توضیح دادید نشان می‌دهد که hibernate می‌خواهد 50 شناسه پیش‌فرض دریافت کند، اما در پایگاه داده نشان دادید که آماده صدور id برای این موجودیت فقط مطابق با 1 هستید . یک اشکال دیگر توسط کاربر Nikolya Kudryashov شناسایی شد : اگر درخواستی را از مقاله اصلی 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 است و برای خلاص شدن از شر آن، باید یک حاشیه اضافی به کلاس Client اضافه کنیم:
@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: کاربر Justinian اکیداً توصیه کرد که نام بسته را com.javarush.lectures.rest_exampleبه com.javarush.lectures.rest.example, و نام پروژه تغییر دهم تا قوانین نامگذاری در جاوا را نقض نکنم. کاربر 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 PS اگر هر یک از شما می خواهید به توسعه این برنامه آموزشی ادامه دهید، خوشحال می شوم که پیوندی به راهنمای شما به این مقاله اضافه کنم. شاید روزی این برنامه به چیزی شبیه یک برنامه تجاری واقعی تبدیل شود که بتوانید کار روی آن را به مجموعه خود اضافه کنید. PPS با توجه به این مقاله ساده، تصمیم گرفتم این تست قلم را به دختران، بانوان و بانوان عزیزمان تقدیم کنم. چه کسی می داند، شاید حالا اگر این زن نبود، نه جاوا، نه JavaRush، و نه برنامه نویسی در طبیعت وجود داشت . عید شما را به مردم باهوش عزیزمان تبریک می گویم! 8 مارس مبارک! شاد و زیبا باشید!
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION