JavaRush /Java-Blog /Random-DE /Hinzufügen einer PostgreSQL-Datenbank zu einem RESTful-Di...
Artur
Level 40
Tallinn

Hinzufügen einer PostgreSQL-Datenbank zu einem RESTful-Dienst auf Spring Boot. Teil 2

Veröffentlicht in der Gruppe Random-DE
Hinzufügen einer PostgreSQL-Datenbank zu einem RESTful-Dienst auf Spring Boot. Teil 1 Hinzufügen einer PostgreSQL-Datenbank zu einem RESTful-Dienst auf Spring Boot.  Teil 2 - 1 Im letzten Teil haben wir also gelernt, wie man eine PostgresSQL-Datenbank auf einem Computer installiert, eine Datenbank in pgAdmin erstellt und auch Tabellen darin manuell und programmgesteuert erstellt und löscht. In diesem Teil werden wir unser Programm so umschreiben, dass es lernt, mit dieser Datenbank und diesen Tabellen zu arbeiten. Warum wir? Denn ich selbst lerne mit Ihnen aus diesem Material. Und dann lösen wir nicht nur die gestellte Aufgabe, sondern korrigieren mithilfe der Ratschläge erfahrenerer Programmierer auch unterwegs auftretende Fehler. Wir werden sozusagen lernen, im Team zu arbeiten ;) Zuerst erstellen wir com.javarush.lectures.rest_exampleein neues Paket in einem Ordner und nennen es repository. In diesem Paket erstellen wir eine neue Schnittstelle 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>  {
}
Diese Schnittstelle wird „magisch“ mit unseren Datenbanken und Tabellen interagieren. Warum magisch? Weil wir die Implementierung nicht schreiben müssen und das Spring-Framework sie uns zur Verfügung stellt. Sie müssen nur eine solche Schnittstelle erstellen und schon können Sie diese „Magie“ nutzen. Der nächste Schritt besteht darin, die Klasse Clientwie folgt zu bearbeiten:
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
}
In diesem Kurs haben wir lediglich einige Anmerkungen hinzugefügt. Gehen wir sie durch:
  • @Entity – gibt an, dass diese Bean (Klasse) eine Entität ist.
  • @Table – gibt den Namen der Tabelle an, die in dieser Entität angezeigt wird.
  • @Id – Spalten-ID (Primärschlüssel – der Wert, der verwendet wird, um die Eindeutigkeit der Daten in der aktuellen Tabelle sicherzustellen. Hinweis: Andrei )
  • @Column – gibt den Namen der Spalte an, die der Entity-Eigenschaft zugeordnet ist.
  • @GeneratedValue – gibt an, dass diese Eigenschaft gemäß der angegebenen Strategie generiert wird.
Die Namen der Tabellenfelder müssen nicht mit den Namen der Variablen in der Klasse übereinstimmen. Wenn wir beispielsweise eine Variable haben firstName, benennen wir das Feld in der Tabelle first_name. Diese Annotationen können sowohl direkt auf Felder als auch auf deren Getter gesetzt werden. Wenn Sie sich jedoch für eine dieser Methoden entscheiden, versuchen Sie, diesen Stil während Ihres gesamten Programms beizubehalten. Ich habe die erste Methode nur verwendet, um die Auflistungen zu kürzen. Eine vollständigere Liste der Anmerkungen für die Arbeit mit Datenbanken finden Sie hier . Gehen wir nun zur Klasse ClientServiceImplund schreiben sie wie folgt um:
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;
    }
}
Wie Sie der Auflistung entnehmen können, haben wir lediglich die Zeilen gelöscht, die wir nicht mehr benötigten:
// Хранoderще клиентов
private static final Map<Integer, Client>  CLIENT_REPOSITORY_MAP = new HashMap<>();

// Переменная для генерации ID клиента
private static final AtomicInteger CLIENT_ID_HOLDER = new AtomicInteger();
Stattdessen haben wir unsere Schnittstelle deklariert ClientRepositoryund außerdem die Annotation @Autowired darüber platziert , damit Spring diese Abhängigkeit automatisch zu unserer Klasse hinzufügt. Wir haben auch die gesamte Arbeit an diese Schnittstelle bzw. deren Implementierung delegiert, die Spring hinzufügen wird. Kommen wir zum letzten und interessantesten Schritt – dem Testen unserer Anwendung. Öffnen wir das Postman-Programm (siehe hier , wie man es verwendet ) und senden wir eine GET-Anfrage an diese Adresse: http://localhost:8080/clients. Wir bekommen diese Antwort:
[
    {
        "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)"
    }
]
Wir senden eine POST-Anfrage:
{
  "name" : "Amigo",
  "email" : "amigo@jr.com",
  "phone" : "+7 (191) 746-43-23"
}
Und... wir entdecken unseren ersten Fehler im Programm:
{
    "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"
}
Hinzufügen einer PostgreSQL-Datenbank zu einem RESTful-Dienst auf Spring Boot.  Teil 2 - 2 Wir schauen uns die Protokolle an und finden den folgenden Fehler:

org.postgresql.util.PSQLException: ОШИБКА: повторяющееся Bedeutung ключа нарушает ограничение уникальности "clients_pkey"
  Detail: Ключ "(id)=(1)" уже существует.
Wir senden die gleiche POST-Anfrage noch einmal, das Ergebnis ist das gleiche, aber mit diesem Unterschied: Ключ "(id)=(2)" уже существует. Wir senden die gleiche Anfrage zum dritten Mal und erhalten Status: 201 Erstellt. Wir senden die GET-Anfrage erneut und erhalten eine Antwort:
[
    {
        "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"
    }
]
Dies deutet darauf hin, dass unser Programm die Tatsache ignoriert, dass diese Tabelle bereits vorab ausgefüllt wurde, und erneut eine ID zuweist, beginnend mit eins. Nun, ein Fehler ist ein Arbeitsmoment, verzweifeln Sie nicht, das kommt oft vor. Deshalb werde ich mich an erfahrenere Kollegen wenden, um Hilfe zu erhalten: „Liebe Kolleginnen und Kollegen, bitte geben Sie in den Kommentaren an, wie Sie das Problem beheben können, damit das Programm normal funktioniert.“ Die Hilfe ließ nicht lange auf sich warten und Stas Pasinkov teilte mir in den Kommentaren mit, in welche Richtung ich suchen musste. Dafür gebührt ihm ein besonderer Dank! Aber die Sache war, dass ich in der Klasse die Strategie für die Annotation für das Feld Clientfalsch angegeben habe . Diese Strategie ist für MySQL geeignet. Wenn wir mit Oracle oder PostgeSQL arbeiten, müssen wir eine andere Strategie festlegen. Mehr über Strategien für Primärschlüssel können Sie hier lesen . Ich habe mich für die GenerationType.SEQUENCE-Strategie entschieden. Um es zu implementieren, müssen wir die Datei initDB.sql und natürlich die Anmerkungen des ID-Felds der Client-Klasse leicht umschreiben. Schreiben Sie initDB.sql neu: @GeneratedValue(strategy = GenerationType.IDENTITY)idHinzufügen einer PostgreSQL-Datenbank zu einem RESTful-Dienst auf Spring Boot.  Teil 2 - 3
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;
Was sich geändert hat: Der Typ der ID-Spalte unserer Tabelle hat sich geändert, aber dazu später mehr. Wir haben unten eine Zeile hinzugefügt, in der wir eine neue Sequenz „clients_id_seq“ erstellen, angeben, dass sie mit einer Drei beginnen soll (da die letzte ID in der Datei „populateDB.sql“ 2 ist) und angeben, dass die Erhöhung um eins erfolgen soll. Kehren wir zum Spaltentyp „id“ zurück. Hier haben wir INTEGER angegeben, denn wenn wir SERIAL verlassen, wird die Sequenz automatisch mit dem gleichen Namen „clients_id_seq“ erstellt, beginnt aber bei eins (was zum Programmfehler führte). Wenn Sie nun jedoch eine Tabelle löschen möchten, müssen Sie diese Sequenz zusätzlich entweder manuell über die pgAdmin-Schnittstelle oder über eine .sql-Datei mit den folgenden Befehlen löschen:
DROP TABLE IF EXISTS clients;
DROP SEQUENCE IF EXISTS clients_id_seq
Wenn Sie jedoch nicht eine Datei wie populateDB.sql verwenden, um die Tabelle zunächst zu füllen, können Sie die Typen SERIAL oder BIGSERIAL für den Primärschlüssel verwenden und müssen die Sequenz nicht manuell erstellen und daher nicht löschen es separat. Weitere Informationen zu den Sequenzen finden Sie auf der Website von. PostgreSQL-Dokumentation . Kommen wir nun zu den idKlassenfeldanmerkungen Clientund formatieren sie wie folgt:
@Id
@Column(name = "id")
@SequenceGenerator(name = "clientsIdSeq", sequenceName = "clients_id_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "clientsIdSeq")
private Integer id;
Was wir getan haben: Wir haben eine neue Annotation @SequenceGeneratorzum Erstellen eines Sequenzgenerators installiert, ihr einen Namen zugewiesen clientsIdSeq, angegeben, dass es sich um einen Generator für eine Sequenz handelt clients_id_seq, und ein Attribut hinzugefügt. allocationSize = 1 Dies ist ein optionales Attribut, aber wenn wir dies nicht tun, Wenn wir das Programm ausführen, erhalten wir die folgende Fehlermeldung:

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]
Hier ist, was Benutzer Andrei in den Kommentaren dazu schreibt: „ allocationSize“ soll in erster Linie den Aufwand für Hibernate in der Datenbank für eine „neue ID“ reduzieren. Wenn der Wert == 1 ist, wird für jede neue Entität der Ruhezustand aktiviert, bevor sie in der Datenbank gespeichert wird, und die Datenbank wird für die ID „ausgeführt“. Wenn der Wert > 1 ist (z. B. 5), fragt Hibernate die Datenbank seltener nach einer „neuen“ ID an (z. B. 5 Mal), und bei der Kontaktaufnahme fordert Hibernate die Datenbank auf, diese Nummer zu reservieren (in unserem Fall). Fall, 5) Werte. Der von Ihnen beschriebene Fehler deutet darauf hin, dass Hibernate 50 Standard-IDs erhalten möchte, aber in der Datenbank haben Sie angegeben, dass Sie bereit sind, für diese Entität nur IDs gemäß der ersten auszugeben . Ein weiterer Fehler wurde vom Benutzer Nikolya Kudryashov entdeckt : Wenn Sie eine Anfrage vom Originalartikel http://localhost:8080/clients/1 ausführen, wird der Fehler zurückgegeben:
{
    "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"
}
Dieser Fehler hängt mit der verzögerten Initialisierung von Hibernate zusammen. Um ihn zu beseitigen, müssen wir der Client-Klasse eine zusätzliche Anmerkung hinzufügen:
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
Auf diese Weise:
@Entity
@Table(name = "clients")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Client {.....}
Lassen Sie uns nun unser Programm ausführen (nachdem wir die Clients-Tabelle aus der Datenbank entfernt haben, falls sie beim letzten Mal dort verblieben ist) und drei Zeilen aus der Datei application.properties auskommentieren:
#spring.datasource.initialization-mode=ALWAYS
#spring.datasource.schema=classpath*:database/initDB.sql
#spring.datasource.data=classpath*:database/populateDB.sql
Beim letzten Mal haben wir nur die letzte Zeile kommentiert, aber... Da wir die Tabelle bereits erstellt und ausgefüllt haben, erschien mir dies im Moment logischer. Fahren wir mit dem Testen fort, führen Sie GET-, POST-, PUT- und DELETE-Anfragen über Postman durch und wir werden sehen, dass die Fehler verschwunden sind und alles gut funktioniert. Das war's, die Arbeit ist erledigt. Jetzt können wir kurz zusammenfassen und betrachten, was wir gelernt haben:
  • Installieren Sie PostgreSQL auf Ihrem Computer
  • Erstellen Sie Datenbanken in pgAdmin
  • Erstellen und löschen Sie Tabellen manuell und programmgesteuert
  • Füllen Sie Tabellen über .sql-Dateien
  • Wir haben ein wenig über die „magische“ JpaRepository-Schnittstelle des Spring-Frameworks gelernt
  • Wir haben von einigen Fehlern erfahren, die beim Erstellen eines solchen Programms auftreten können
  • Uns wurde klar, dass wir uns nicht schämen sollten, den Rat von Kollegen einzuholen
  • Wir haben bestätigt, dass die JavaRush-Community eine Kraft ist, die immer zur Rettung kommt ;)
Damit können wir jetzt fertig werden. Hinzufügen einer PostgreSQL-Datenbank zu einem RESTful-Dienst auf Spring Boot.  Teil 2 - 4Vielen Dank an alle, die sich die Zeit genommen haben, dieses Material zu lesen. Ich freue mich über Ihre Kommentare, Anmerkungen, Ergänzungen und konstruktive Kritik. Vielleicht bieten Sie für dieses Problem elegantere Lösungen an, die ich verspreche, über das UPD-„Stichwort“ in diesen Artikel aufzunehmen, natürlich mit Nennung von Ihnen als Autor. Nun, im Allgemeinen schreiben Sie, ob Ihnen dieser Artikel und diese Art der Präsentation des Materials gefallen haben, und im Allgemeinen, ob ich weiterhin Artikel über JR schreiben soll. Hier sind die Ergänzungen: UPD1: Benutzer Justinian hat mir dringend empfohlen, das Paket com.javarush.lectures.rest_examplein umzubenennen com.javarush.lectures.rest.exampleund den Namen des Projekts anzugeben, um nicht gegen die Namenskonventionen in Java zu verstoßen. UPD2-Benutzer Alexander Pyanov schlug vor, dass es zum Initialisieren eines Felds ClientRepositoryin einer Klasse ClientServiceImplbesser ist, einen Konstruktor als eine Annotation zu verwenden @Autowired. Dies erklärt sich aus der Tatsache, dass man in seltenen Fällen NullPointerException, und im Allgemeinen erhalten kann, dies ist die beste Vorgehensweise, und ich stimme ihr zu. Wenn ein Feld für die anfängliche Funktionalität eines Objekts erforderlich ist, ist es logischerweise besser, es im Konstruktor zu initialisieren, da eine Klasse ohne Konstruktor nicht zu einem Objekt zusammengesetzt wird. Daher wird dieses Feld auf der Stufe initialisiert der Objekterstellung. Ich füge ein Codefragment mit Korrekturen hinzu (was muss durch was ersetzt werden):
@Autowired
private ClientRepository clientRepository;

private final ClientRepository clientRepository;

public ClientServiceImpl(ClientRepository clientRepository) {
   this.clientRepository = clientRepository;
}
Link zum ersten Teil: Hinzufügen einer PostgreSQL-Datenbank zu einem RESTful-Dienst auf Spring Boot. Teil 1 PS: Wenn einer von Ihnen diese Bildungsanwendung weiterentwickeln möchte, füge ich in diesem Artikel gerne einen Link zu Ihren Leitfäden hinzu. Vielleicht entwickelt sich dieses Programm eines Tages zu etwas, das einer echten Geschäftsanwendung ähnelt, an der Sie Ihr Portfolio bearbeiten können. PPS In Bezug auf diesen bescheidenen Artikel habe ich beschlossen, diesen Test des Stifts unseren lieben Mädchen, Frauen und Damen zu widmen. Wer weiß, vielleicht gäbe es heute kein Java, kein JavaRush, keine Programmierung in der Natur, wenn diese Frau nicht gewesen wäre . Herzlichen Glückwunsch zu Ihrem Urlaub, unsere lieben klugen Leute! Schönen 8. März! Sei glücklich und schön!
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION