JavaRush /Java Blog /Random-TW /將 PostgreSQL 資料庫新增至 Spring Boot 上的 RESTful 服務。第2部分
Artur
等級 40
Tallinn

將 PostgreSQL 資料庫新增至 Spring Boot 上的 RESTful 服務。第2部分

在 Random-TW 群組發布
將 PostgreSQL 資料庫新增至 Spring Boot 上的 RESTful 服務。第 1 部分 將 PostgreSQL 資料庫新增至 Spring Boot 上的 RESTful 服務。 第 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 - 列id(主鍵 - 將用於確保目前表中資料的唯一性的值。註:Andrei
  • @Column - 指示對應到實體屬性的欄位的名稱。
  • @GenerateValue - 表示該屬性將根據指定的策略產生。
表格欄位的名稱不必與類別中變數的名稱相符。例如,如果我們有一個變量firstName,那麼我們將在表中命名該字段first_name。這些註釋可以直接在字段及其 getter 上設定。但如果您選擇其中一種方法,請嘗試在整個程序中保持這種風格。我使用第一種方法只是為了縮短清單。可以在此處找到更完整的資料庫註釋清單。現在我們去類別中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 資料庫新增至 Spring Boot 上的 RESTful 服務。 第 2 - 2 部分 我們查看日誌,發現以下錯誤:

org.postgresql.util.PSQLException: ОШИБКА: повторяющееся meaning ключа нарушает ограничение уникальности "clients_pkey"
  Detail: Ключ "(id)=(1)" уже существует.
我們再次發送相同的 POST 請求,結果是相同的,但有一個區別:Ключ "(id)=(2)" уже существует. 我們第三次發送相同的請求,我們得到 Status: 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"
    }
]
這表明我們的程式忽略了這個表已經被預先填入的事實,並再次從 1 開始分配一個 id。好吧,錯誤是一個工作時刻,不要絕望,這種情況經常發生。因此,我會向更有經驗的同事尋求幫助:“親愛的同事,請在評論中指出如何解決這個問題,以便程序正常運行。” 幫助很快就到達了,斯塔斯·帕辛科夫在評論中告訴我需要朝哪個方向看。在此特別感謝他!但問題是,在類別中我錯誤地指定了欄位Client註解的策略。 這種策略適合MySQL。如果我們使用 Oracle 或 PostrgeSQL,那麼我們需要設定不同的策略。您可以在此處閱讀有關主鍵策略的更多資訊。我選擇了 GenerationType.SEQUENCE 策略。為了實現它,我們需要稍微重寫 initDB.sql 文件,當然還有 Client 類別的 id 欄位的註解。重寫initDB.sql: @GeneratedValue(strategy = GenerationType.IDENTITY)id將 PostgreSQL 資料庫新增至 Spring Boot 上的 RESTful 服務。 第 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;
發生了什麼變化:我們表格的 id 列的類型發生了變化,稍後會詳細介紹。我們在下面新增了一行,在其中建立了一個新序列clients_id_seq,指示它應以 3 開頭(因為 populateDB.sql 檔案中的最後一個 id 是 2),並指示增量應為 1。讓我們回到 id 列類型。這裡我們指定了INTEGER,因為如果我們離開SERIAL,序列將自動創建,具有相同的名稱clients_id_seq,但將從1開始(這導致了程式錯誤)。但是,現在如果要刪除表,則需要透過 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在評論中對此的評論: allocationSize 主要是為了減少 hibernate 為「新 id」而存取資料庫的次數。如果值 == 1,則對每個新實體進行休眠,在將其儲存到資料庫之前,「執行」到資料庫中尋找 id。如果該值> 1(例如5),hibernate將較少頻率地聯繫資料庫以取得「新」id(例如5次),並且在聯絡時,hibernate將要求資料庫保留這個號碼(在我們的案例,5)值。您描述的錯誤表明 hibernate 希望接收 50 個預設 id,但在資料庫中,您表示您已準備好僅根據第一個 id 為該實體頒發 id使用者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 {.....}
現在讓我們執行我們的程式(從資料庫中刪除客戶端表(如果上次還存在的話)),並從 application.properties 檔案中註解掉 3 行:
#spring.datasource.initialization-mode=ALWAYS
#spring.datasource.schema=classpath*:database/initDB.sql
#spring.datasource.data=classpath*:database/populateDB.sql
上次我們只評論了最後一行,但是...... 由於我們已經創建並填寫了表格,目前這對我來說似乎更合乎邏輯。讓我們繼續測試,透過 Postman 執行 GET、POST、PUT 和 DELETE 請求,我們將看到錯誤已經消失,一切正常。就這樣,工作完成了。現在我們可以簡要地總結並思考我們所學到的內容:
  • 在您的電腦上安裝 PostgreSQL
  • 在 pgAdmin 中建立資料庫
  • 手動和編程創建和刪除表
  • 透過 .sql 文件填充表
  • 我們了解了 Spring 框架的「神奇」JpaRepository 接口
  • 我們了解了創建此類程式時可能出現的一些錯誤
  • 我們意識到向同事尋求建議不應該感到尷尬
  • 我們已經確認 JavaRush 社群是一支始終能夠提供救援的力量;)
我們現在就可以到這裡結束了。將 PostgreSQL 資料庫新增至 Spring Boot 上的 RESTful 服務。 第 2 - 4 部分感謝所有花時間閱讀本資料的人。我很高興看到您的評論、觀察、補充和建設性批評。也許您會為這個問題提供更優雅的解決方案,我承諾透過 UPD「關鍵字」將其添加到本文中,當然,還會提及您作為作者。好吧,總的來說,寫下你是否喜歡這篇文章和這種呈現材料的風格,以及總的來說,我是否應該繼續寫關於 JR 的文章。以下是補充內容: UPD1:使用者Justinian強烈建議我將套件重新命名com.javarush.lectures.rest_examplecom.javarush.lectures.rest.example,以及專案的名稱,以免違反 Java 中的命名約定。UPD2 使用者Alexander PyanovClientRepository建議,要初始化類別中的字段,ClientServiceImpl最好使用建構子而不是註解@Autowired。這是因為在極少數情況下您可以獲得NullPointerException,並且一般來說,這是最佳實踐,我同意這一點。從邏輯上講,如果一個對象的初始功能需要一個字段,那麼最好在構造函數中初始化它,因為沒有構造函數的類不會被組裝成對象,因此,該字段將在階段初始化的對象創建。我將添加一個帶有更正的程式碼片段(需要用什麼替換):
@Autowired
private ClientRepository clientRepository;

private final ClientRepository clientRepository;

public ClientServiceImpl(ClientRepository clientRepository) {
   this.clientRepository = clientRepository;
}
連結到第一部分:將 PostgreSQL 資料庫新增到 Spring Boot 上的 RESTful 服務。第 1 部分 PS 如果你們中的任何人想繼續開發此教育應用程序,那麼我將很高興在本文中添加指向您的指南的連結。也許有一天,這個程式將發展成為類似於真正的業務應用程式的東西,您可以將工作添加到您的投資組合中。PPS 關於這篇謙虛的文章,我決定將這次筆試獻給我們親愛的女孩、婦女和女士們。誰知道,如果沒有這個女人,也許現在就沒有 Java,沒有 JavaRush,就沒有程式設計本質。恭喜您節日快樂,我們親愛的聰明人!3 月 8 日快樂!快樂又美麗!
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION