JavaRush /Java Blogu /Random-AZ /Spring Boot-da RESTful xidmətinə PostgreSQL verilənlər ba...
Artur
Səviyyə
Tallinn

Spring Boot-da RESTful xidmətinə PostgreSQL verilənlər bazası əlavə etmək. 2-ci hissə

Qrupda dərc edilmişdir
Spring Boot-da RESTful xidmətinə PostgreSQL verilənlər bazası əlavə etmək. 1-ci hissə Spring Boot-da RESTful xidmətinə PostgreSQL verilənlər bazası əlavə etmək.  Hissə 2 - 1 Beləliklə, son hissədə PostgresSQL verilənlər bazasını kompüterə quraşdırmağı, pgAdmin-də verilənlər bazası yaratmağı, həmçinin əl ilə və proqramlı şəkildə orada cədvəllər yaratmağı və silməyi öyrəndik. Bu hissədə proqramımızı yenidən yazacağıq ki, o, bu verilənlər bazası və cədvəllərlə işləməyi öyrənsin. Niyə biz? Çünki mən özüm sizinlə bu materialdan öyrənirəm. Və sonra biz daha təcrübəli proqramçıların məsləhətləri ilə nəinki qarşıda duran vəzifəni həll edəcəyik, həm də yolda yaranan səhvləri düzəldəcəyik. Belə desək, komandada işləməyi öyrənəcəyik ;) Əvvəlcə com.javarush.lectures.rest_exampleqovluqda yeni paket yaradaq və ona zəng edək repository. Bu paketdə biz yeni interfeys yaradacağıq 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>  {
}
Bu interfeys verilənlər bazamız və cədvəllərimizlə "sehrli" qarşılıqlı əlaqədə olacaq. Niyə sehrli? Çünki onun həyata keçirilməsini yazmağa ehtiyacımız olmayacaq və Bahar çərçivəsi bunu bizə təqdim edəcək. Sadəcə belə bir interfeys yaratmalısınız və siz artıq bu "sehrindən" istifadə edə bilərsiniz. Növbəti addım sinfi Clientbelə redaktə etməkdir:
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
}
Bu sinifdə etdiyimiz tək şey bəzi annotasiyalar əlavə etmək idi. Gəlin onlardan keçək:
  • @Entity - bu paxlanın (sinif) bir varlıq olduğunu göstərir.
  • @Cədvəl - bu obyektdə göstəriləcək cədvəlin adını göstərir.
  • @Id - sütun id (əsas açar - cari cədvəldə məlumatların unikallığını təmin etmək üçün istifadə ediləcək dəyər. Qeyd: Andrei )
  • @Column - obyekt xassəsinə uyğunlaşdırılan sütunun adını göstərir.
  • @GeneratedValue - bu xüsusiyyətin göstərilən strategiyaya uyğun olaraq yaradılacağını göstərir.
Cədvəl sahələrinin adları sinifdəki dəyişənlərin adlarına uyğun gəlməməlidir. Məsələn, bir dəyişənimiz varsa firstName, o zaman cədvəldəki sahənin adını verəcəyik first_name. Bu annotasiyalar həm birbaşa sahələrə, həm də onların alıcılarına təyin edilə bilər. Ancaq bu üsullardan birini seçsəniz, bütün proqramınız boyunca bu üslubu qorumağa çalışın. Mən yalnız siyahıları qısaltmaq üçün birinci üsuldan istifadə etdim. Verilənlər bazası ilə işləmək üçün annotasiyaların daha tam siyahısını burada tapa bilərsiniz . İndi sinfə keçək ClientServiceImplvə onu aşağıdakı kimi yenidən yazaq:
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;
    }
}
Siyahıdan da gördüyünüz kimi, sadəcə olaraq ehtiyacımız olmayan sətirləri sildik:
// Хранorще клиентов
private static final Map<Integer, Client>  CLIENT_REPOSITORY_MAP = new HashMap<>();

// Переменная для генерации ID клиента
private static final AtomicInteger CLIENT_ID_HOLDER = new AtomicInteger();
Bunun əvəzinə interfeysimizi elan etdik ClientRepositoryvə həmçinin @Autowired annotasiyasını onun üzərinə yerləşdirdik ki, Spring avtomatik olaraq bu asılılığı sinfimizə əlavə etsin. Biz də bütün işləri bu interfeysə, daha doğrusu, Baharın əlavə edəcəyi həyata keçirilməsinə həvalə etdik. Gəlin son və ən maraqlı mərhələyə - ərizəmizi sınaqdan keçirməyə keçək. Gəlin Postman proqramını açaq (onu buradan necə istifadə edəcəyinə baxın ) və bu ünvana GET sorğusu göndərək: http://localhost:8080/clients. Bu cavabı alırıq:
[
    {
        "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 sorğusu göndəririk:
{
  "name" : "Amigo",
  "email" : "amigo@jr.com",
  "phone" : "+7 (191) 746-43-23"
}
Və... proqramda ilk səhvimizi tutduq:
{
    "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"
}
Spring Boot-da RESTful xidmətinə PostgreSQL verilənlər bazası əlavə etmək.  2-2 hissə Günlüklərə baxırıq və aşağıdakı xətanı tapırıq:

org.postgresql.util.PSQLException: ОШИБКА: повторяющееся meaning ключа нарушает ограничение уникальности "clients_pkey"
  Detail: Ключ "(id)=(1)" уже существует.
Eyni POST sorğusunu yenidən göndəririk, nəticə eynidir, lakin bu fərqlə: Ключ "(id)=(2)" уже существует. Eyni sorğunu üçüncü dəfə göndəririk və Status: 201 Created alırıq. GET sorğusunu yenidən göndəririk və cavab alırıq:
[
    {
        "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"
    }
]
Bu onu göstərir ki, proqramımız bu cədvəlin artıq əvvəlcədən doldurulmasına məhəl qoymur və birindən başlayaraq yenidən id təyin edir. Yaxşı, səhv bir iş anıdır, ümidsiz olmayın, bu tez-tez olur. Buna görə də kömək üçün daha təcrübəli həmkarlara müraciət edəcəyəm: "Hörmətli həmkarlar, proqramın normal işləməsi üçün bunu necə düzəltməyi şərhlərdə məsləhət verin." Yardımın gəlməsi çox çəkmədi və Stas Pasinkov şərhlərdə mənə hansı istiqamətə baxmaq lazım olduğunu söylədi. Bunun üçün ona xüsusi təşəkkürlər! Amma iş onda idi ki, sinifdə sahə üçün Clientannotasiya strategiyasını səhv müəyyən etmişəm . Bu strategiya MySQL üçün uyğundur. Oracle və ya PostrgeSQL ilə işləyiriksə, o zaman fərqli strategiya təyin etməliyik. Siz burada əsas açarlar üçün strategiyalar haqqında ətraflı oxuya bilərsiniz . Mən GenerationType.SEQUENCE strategiyasını seçdim. Onu həyata keçirmək üçün initDB.sql faylını və təbii ki, Client sinfinin id sahəsinin annotasiyalarını bir qədər yenidən yazmalıyıq. initDB.sql-i yenidən yazın: @GeneratedValue(strategy = GenerationType.IDENTITY)idSpring Boot-da RESTful xidmətinə PostgreSQL verilənlər bazası əlavə etmək.  2-3 hissə
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;
Nə dəyişdi: cədvəlimizin id sütununun növü dəyişdi, lakin bu barədə daha sonra. Aşağıda yeni clients_id_seq ardıcıllığı yaratdığımız sətir əlavə etdik, onun üç ilə başlamalı olduğunu göstərdik (çünki populateDB.sql faylında sonuncu id 2-dir) və artımın bir baş verməli olduğunu göstərdik. İd sütun növünə qayıdaq. Burada biz İNTEGER-i göstərdik, çünki SERIAL-dan çıxsaq, ardıcıllıq avtomatik olaraq eyni adlı clients_id_seq ilə yaradılacaq, lakin birindən başlayacaq (bu, proqramın səhvinə səbəb olub). Bununla belə, indi cədvəli silmək istəyirsinizsə, bu ardıcıllığı ya pgAdmin interfeysi vasitəsilə əl ilə, ya da aşağıdakı əmrlərdən istifadə edərək .sql faylı vasitəsilə əlavə olaraq silməlisiniz:
DROP TABLE IF EXISTS clients;
DROP SEQUENCE IF EXISTS clients_id_seq
Lakin cədvəli ilkin olaraq doldurmaq üçün populateDB.sql kimi fayldan istifadə etmirsinizsə, onda siz əsas açar üçün SERIAL və ya BIGSERIAL növlərindən istifadə edə bilərsiniz və ardıcıllığı əl ilə yaratmağa ehtiyac yoxdur və buna görə də silmək lazım deyil. ayrıdır. Ardıcıllıqlar haqqında ətraflı məlumatı internet saytında oxuya bilərsiniz. PostgreSQL Sənədləri . idGəlin sinif sahəsinin annotasiyalarına keçək Clientvə onları aşağıdakı kimi formatlaşdıraq:
@Id
@Column(name = "id")
@SequenceGenerator(name = "clientsIdSeq", sequenceName = "clients_id_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "clientsIdSeq")
private Integer id;
Nə etdik: ardıcıllıq generatoru yaratmaq üçün yeni annotasiya quraşdırdıq @SequenceGenerator, ona ad təyin etdik clientsIdSeq, bunun ardıcıllıq üçün generator olduğunu bildirdik clients_id_seqvə atribut əlavə etdik. allocationSize = 1 Bu isteğe bağlı atributdur, lakin bunu etməsək, proqramı işə saldıqda aşağıdakı xətanı alacağıq:

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]
Budur, Andrei istifadəçisi şərhlərdə bu barədə yazır: allocationSize, ilk növbədə, "yeni identifikator" üçün qışlama rejiminin verilənlər bazasına səfərini azaltmaq üçün nəzərdə tutulub. Əgər dəyər == 1 olarsa, onu verilənlər bazasında saxlamazdan əvvəl hər bir yeni obyekt üçün qışlama rejiminə keçin, id üçün verilənlər bazasına “çalışır”. Dəyər > 1 olarsa (məsələn, 5), hibernate "yeni" id üçün verilənlər bazası ilə daha az əlaqə saxlayacaq (məsələn, 5 dəfə) və əlaqə saxlayarkən hibernate məlumat bazasından bu nömrəni saxlamağı xahiş edəcək (bizim səhifəmizdə halda, 5) dəyərlər. Təsvir etdiyiniz xəta qış rejiminin 50 defolt identifikatoru qəbul etmək istədiyini göstərir, lakin verilənlər bazasında siz bu obyekt üçün yalnız 1-ci id-yə uyğun olaraq id verməyə hazır olduğunuzu qeyd etdiniz . Başqa bir səhv istifadəçi Nikolya Kudryashov tərəfindən tutuldu : http://localhost:8080/clients/1 orijinal məqaləsindən sorğu göndərsəniz, xəta geri qaytarılacaq:
{
    "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"
}
Bu xəta Hibernate-in tənbəl işə salınması ilə əlaqədardır və ondan xilas olmaq üçün Client sinfinə əlavə annotasiya əlavə etməliyik:
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
Bu cür:
@Entity
@Table(name = "clients")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Client {.....}
İndi proqramımızı işə salaq (müştərilər cədvəlini verilənlər bazasından sildikdən sonra, əgər sonuncu dəfə orada qalırsa) və application.properties faylından 3 sətri şərh edək:
#spring.datasource.initialization-mode=ALWAYS
#spring.datasource.schema=classpath*:database/initDB.sql
#spring.datasource.data=classpath*:database/populateDB.sql
Keçən dəfə yalnız sonuncu sətri şərh etdik, amma... Cədvəli artıq yaradıb doldurduğumuz üçün bu, hazırda mənə daha məntiqli göründü. Testə keçək, GET, POST, PUT və DELETE sorğularını Postman vasitəsilə yerinə yetirək və biz baxacağıq ki, xətalar aradan qalxıb və hər şey qaydasındadır. Budur, iş bitdi. İndi qısaca ümumiləşdirə və öyrəndiklərimizi nəzərdən keçirə bilərik:
  • PostgreSQL-i kompüterinizə quraşdırın
  • pgAdmin-də verilənlər bazası yaradın
  • Cədvəlləri əl ilə və proqramlı şəkildə yaradın və silin
  • Cədvəlləri .sql faylları vasitəsilə doldurun
  • Bahar çərçivəsinin “sehrli” JpaRepository interfeysi haqqında bir az öyrəndik
  • Belə bir proqram yaratarkən yarana biləcək bəzi səhvlər haqqında öyrəndik
  • Anladıq ki, həmkarlarımızdan məsləhət almaqdan utanmamalıyıq
  • JavaRush icmasının həmişə köməyə gələcək bir qüvvə olduğunu təsdiq etdik;)
Hələlik burada bitirə bilərik. Spring Boot-da RESTful xidmətinə PostgreSQL verilənlər bazası əlavə etmək.  2-4 hissəBu materialı oxumağa vaxt ayıran hər kəsə təşəkkür edirik. Şərhlərinizi, müşahidələrinizi, əlavələrinizi və konstruktiv tənqidlərinizi görməyə şad olaram. Yəqin ki, UPD “açar söz” vasitəsilə bu məqaləyə əlavə edəcəyimə söz verdiyim bu problemə daha zərif həllər təklif edəcəksiniz, əlbəttə ki, müəllif kimi sizi qeyd etməklə. Yaxşı, ümumiyyətlə, bu məqaləni və materialı təqdim etmə tərzini bəyəndinizmi və ümumiyyətlə JR-də məqalələr yazmağa davam etməliyəmmi yazın. Əlavələr bunlardır: UPD1: istifadəçi Justinian Java-da adlandırma konvensiyalarını pozmamaq üçün paketin adını com.javarush.lectures.rest_examplecom.javarush.lectures.rest.examplelayihənin adını dəyişməyi şiddətlə tövsiyə etdi. UPD2 istifadəçisi Alexander PyanovClientRepository təklif etdi ki, sinifdə sahəni işə salmaq üçün ClientServiceImplannotasiyadansa konstruktordan istifadə etmək daha yaxşıdır @Autowired. Bu, nadir hallarda əldə edə biləcəyiniz ilə izah olunur NullPointerExceptionvə ümumiyyətlə, bu ən yaxşı təcrübədir və mən bununla razıyam. Məntiqi olaraq obyektin ilkin funksionallığı üçün sahə tələb olunursa, onu konstruktorda işə salmaq daha yaxşıdır, çünki konstruktoru olmayan sinif obyektə yığılmayacaq, ona görə də bu sahə mərhələdə işə salınacaq. obyektin yaradılması. Mən düzəlişləri olan bir kod parçası əlavə edəcəyəm (nəyi nə ilə əvəz etmək lazımdır):
@Autowired
private ClientRepository clientRepository;

private final ClientRepository clientRepository;

public ClientServiceImpl(ClientRepository clientRepository) {
   this.clientRepository = clientRepository;
}
Birinci hissəyə keçid: Spring Boot-da RESTful xidmətinə PostgreSQL verilənlər bazası əlavə etmək. 1-ci hissə P.S. Əgər hər hansı biriniz bu təhsil proqramını inkişaf etdirməyə davam etmək istəyirsinizsə, onda mən bu məqaləyə bələdçilərinizə keçid əlavə etməkdən məmnun olaram. Ola bilsin ki, nə vaxtsa bu proqram portfelinizə iş əlavə edə biləcəyiniz real iş proqramına bənzəyən bir şeyə çevriləcək. PPS Bu təvazökar məqalə ilə əlaqədar olaraq, qələmin bu sınağını əziz qızlarımıza, qadınlarımıza və xanımlarımıza həsr etmək qərarına gəldim. Kim bilir, bəlkə indi bu qadın olmasaydı, təbiətdə Java, JavaRush, proqramlaşdırma olmazdı . Bayramınızı təbrik edirik, əziz ağıllı insanlarımız! 8 Mart bayramınız mübarək! Xoşbəxt və gözəl olun!
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION