JavaRush /จาวาบล็อก /Random-TH /การเพิ่มฐานข้อมูล PostgreSQL ให้กับบริการ RESTful บน Spri...
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 Framework จะจัดเตรียมให้เรา คุณเพียงแค่ต้องสร้างอินเทอร์เฟซดังกล่าวและคุณสามารถใช้ "เวทย์มนตร์" นี้ได้แล้ว ขั้นตอนต่อไปคือการแก้ไขคลาส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 จะเพิ่มการพึ่งพานี้ให้กับคลาสของเราโดยอัตโนมัติ นอกจากนี้เรายังมอบหมายงานทั้งหมดให้กับอินเทอร์เฟซนี้หรือเป็นการนำไปปฏิบัติซึ่ง 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"
    }
]
สิ่งนี้ชี้ให้เห็นว่าโปรแกรมของเรากำลังเพิกเฉยต่อความจริงที่ว่าตารางนี้ได้รับการเติมไว้ล่วงหน้าแล้ว และกำลังกำหนด ID อีกครั้งโดยเริ่มจากหนึ่ง แมลงคือช่วงเวลาแห่งการทำงาน อย่าเพิ่งหมดหวัง สิ่งนี้เกิดขึ้นบ่อยครั้ง ดังนั้น ฉันจะขอความช่วยเหลือจากเพื่อนร่วมงานที่มีประสบการณ์มากกว่า: “ถึงเพื่อนร่วมงาน โปรดแนะนำวิธีแก้ไขปัญหานี้ในความคิดเห็นเพื่อให้โปรแกรมทำงานได้ตามปกติ” ความช่วยเหลือใช้เวลาไม่นานก็มาถึง และStas Pasinkovบอกฉันในความคิดเห็นว่าฉันต้องมองไปในทิศทางใด ขอขอบคุณเขาเป็นพิเศษสำหรับสิ่งนี้! แต่ประเด็นก็คือในชั้นเรียน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 ของตารางของเรามีการเปลี่ยนแปลง แต่จะมีการเปลี่ยนแปลงเพิ่มเติมในภายหลัง เราได้เพิ่มบรรทัดด้านล่างซึ่งเราสร้างลำดับใหม่ client_id_seq ระบุว่าควรเริ่มต้นด้วยสาม (เนื่องจาก id สุดท้ายในไฟล์ populateDB.sql คือ 2) และระบุว่าการเพิ่มขึ้นควรเกิดขึ้นทีละหนึ่ง กลับไปที่ประเภทคอลัมน์ id กัน ที่นี่เราระบุ INTEGER เพราะถ้าเราปล่อยให้ 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 เขียน เกี่ยวกับสิ่งนี้ในความคิดเห็น: allocationSize มีจุดประสงค์หลักเพื่อลดการเดินทางไปยังฐานข้อมูลในโหมดไฮเบอร์เนตสำหรับ "รหัสใหม่" หากค่า == 1 ให้ไฮเบอร์เนตสำหรับแต่ละเอนทิตีใหม่ ก่อนที่จะบันทึกลงในฐานข้อมูล "รัน" ไปยังฐานข้อมูลสำหรับ id หากค่าเป็น > 1 (เช่น 5) ไฮเบอร์เนตจะติดต่อกับฐานข้อมูลสำหรับรหัส "ใหม่" น้อยกว่า (เช่น 5 ครั้ง) และเมื่อมีการติดต่อ ไฮเบอร์เนตจะขอให้ฐานข้อมูลจองหมายเลขนี้ (ในของเรา กรณี 5) ค่า ข้อผิดพลาดที่คุณอธิบายระบุว่าไฮเบอร์เนตต้องการรับรหัสเริ่มต้น 50 รหัส แต่ในฐานข้อมูลคุณระบุว่าคุณพร้อมที่จะออกรหัสสำหรับเอนทิตีนี้ตามรหัสที่ 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"
}
ข้อผิดพลาดนี้เกี่ยวข้องกับการเริ่มต้นแบบ Lazy ของ 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 ผ่านทางบุรุษไปรษณีย์กัน แล้วเราจะเห็นว่าจุดบกพร่องหายไปแล้วและทุกอย่างทำงานได้ดี แค่นั้นแหละ งานเสร็จแล้ว ตอนนี้เราสามารถสรุปสั้นๆ และพิจารณาสิ่งที่เราได้เรียนรู้:
  • ติดตั้ง PostgreSQL บนคอมพิวเตอร์ของคุณ
  • สร้างฐานข้อมูลใน pgAdmin
  • สร้างและลบตารางด้วยตนเองและทางโปรแกรม
  • เติมข้อมูลตารางด้วยไฟล์ .sql
  • เราได้เรียนรู้เล็กน้อยเกี่ยวกับอินเทอร์เฟซ JpaRepository “วิเศษ” ของ Spring Framework
  • เราได้เรียนรู้เกี่ยวกับข้อบกพร่องบางอย่างที่อาจเกิดขึ้นเมื่อสร้างโปรแกรมดังกล่าว
  • เราตระหนักว่าเราไม่ควรอายที่จะขอคำแนะนำจากเพื่อนร่วมงาน
  • เราได้รับการยืนยันแล้วว่าชุมชน JavaRush เป็นพลังที่จะช่วยเหลือเสมอ ;)
เราสามารถจบที่นี่ได้ในตอนนี้ การเพิ่มฐานข้อมูล PostgreSQL ให้กับบริการ RESTful บน Spring Boot  ส่วนที่ 2 - 4ขอขอบคุณทุกคนที่สละเวลาอ่านเนื้อหานี้ ฉันยินดีที่จะเห็นความคิดเห็น ข้อสังเกต เพิ่มเติม และคำวิจารณ์ที่สร้างสรรค์ของคุณ บางทีคุณอาจเสนอวิธีแก้ปัญหาที่หรูหรากว่านี้สำหรับปัญหานี้ ซึ่งฉันสัญญาว่าจะเพิ่มลงในบทความนี้ผ่าน "คำหลัก" ของ UPD โดยแน่นอนว่าจะกล่าวถึงคุณในฐานะผู้เขียน โดยทั่วไปแล้ว เขียนว่าคุณชอบบทความนี้และรูปแบบการนำเสนอเนื้อหานี้หรือไม่ และโดยทั่วไปแล้ว ฉันควรจะเขียนบทความเกี่ยวกับ JR ต่อไปหรือไม่ ข้อมูลเพิ่มเติมต่อไปนี้: UPD1: ผู้ใช้Justinianแนะนำอย่างยิ่งให้ฉันเปลี่ยนชื่อแพ็กเกจcom.javarush.lectures.rest_exampleเป็นcom.javarush.lectures.rest.exampleและชื่อของโปรเจ็กต์ เพื่อไม่ให้ละเมิดหลักการตั้งชื่อใน Java ผู้ใช้ UPD2 Alexander Pyanovแนะนำว่าการเริ่มต้นฟิลด์ClientRepositoryในคลาสClientServiceImplจะดีกว่าถ้าใช้ Constructor มากกว่าคำอธิบาย@Autowiredประกอบ สิ่งนี้อธิบายได้จากข้อเท็จจริงที่ว่า ในบางกรณีที่เกิดขึ้นไม่บ่อยนัก คุณจะได้รับNullPointerExceptionและโดยทั่วไป นี่เป็นแนวปฏิบัติที่ดีที่สุด และฉันเห็นด้วยกับมัน ตามตรรกะ หากจำเป็นต้องมีฟิลด์สำหรับฟังก์ชันเริ่มต้นของอ็อบเจ็กต์ ก็ควรเริ่มต้นในคอนสตรัคเตอร์จะดีกว่า เพราะคลาสที่ไม่มีคอนสตรัคเตอร์จะไม่ถูกรวมเข้ากับอ็อบเจ็กต์ ดังนั้น ฟิลด์นี้จะถูกเตรียมใช้งานในสเตจ ของการสร้างวัตถุ ฉันจะเพิ่มส่วนของโค้ดพร้อมการแก้ไข (สิ่งที่ต้องแทนที่ด้วยอะไร):
@Autowired
private ClientRepository clientRepository;

private final ClientRepository clientRepository;

public ClientServiceImpl(ClientRepository clientRepository) {
   this.clientRepository = clientRepository;
}
ลิงก์ไปยังส่วนแรก: การเพิ่มฐานข้อมูล PostgreSQL ให้กับบริการ RESTful บน Spring Boot ส่วนที่ 1 ป.ล. หากคุณต้องการพัฒนาแอปพลิเคชันเพื่อการศึกษานี้ต่อไป ฉันยินดีที่จะเพิ่มลิงก์ไปยังคำแนะนำของคุณในบทความนี้ บางทีสักวันหนึ่งโปรแกรมนี้อาจเติบโตเป็นสิ่งที่คล้ายกับแอปพลิเคชันทางธุรกิจจริง ๆ ที่คุณสามารถเพิ่มงานลงในพอร์ตโฟลิโอของคุณได้ PPS เกี่ยวกับบทความที่เรียบง่ายนี้ ฉันตัดสินใจอุทิศการทดสอบปากกานี้ให้กับเด็กผู้หญิง ผู้หญิง และสุภาพสตรีที่รักของเรา ใครจะรู้ บางทีตอนนี้อาจจะไม่มี Java ไม่มี JavaRush ไม่มีการเขียนโปรแกรมโดยธรรมชาติ ถ้าไม่ใช่สำหรับผู้หญิงคน นี้ ขอแสดงความยินดีในวันหยุดของคุณคนฉลาดที่รักของเรา! สุขสันต์วันที่ 8 มีนาคม! มีความสุขและสวยงาม!
ความคิดเห็น
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION