JavaRush /Java Blog /Random-KO /Spring Boot의 RESTful 서비스에 PostgreSQL 데이터베이스를 추가합니다. 2 부
Artur
레벨 40
Tallinn

Spring Boot의 RESTful 서비스에 PostgreSQL 데이터베이스를 추가합니다. 2 부

Random-KO 그룹에 게시되었습니다
Spring Boot의 RESTful 서비스에 PostgreSQL 데이터베이스를 추가합니다. 1 Spring Boot의 RESTful 서비스에 PostgreSQL 데이터베이스를 추가합니다.  파트 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 - 엔터티 속성에 매핑되는 열의 이름을 나타냅니다.
  • @GeneratedValue - 이 속성이 지정된 전략에 따라 생성됨을 나타냅니다.
테이블 필드의 이름은 클래스의 변수 이름과 일치할 필요가 없습니다. 예를 들어 변수가 있는 경우 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();
대신 인터페이스를 선언 하고 그 위에 @AutowiredClientRepository 주석을 배치하여 Spring이 자동으로 이 종속성을 클래스에 추가하도록 했습니다. 우리는 또한 모든 작업을 이 인터페이스 또는 오히려 Spring이 추가할 구현에 위임했습니다. 마지막이자 가장 흥미로운 단계인 애플리케이션 테스트로 넘어가겠습니다. Postman 프로그램을 열고(사용 방법은 여기 참조 ) http://localhost:8080/clients 주소로 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)"
    }
]
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"
}
Spring Boot의 RESTful 서비스에 PostgreSQL 데이터베이스를 추가합니다.  파트 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"
    }
]
이는 우리 프로그램이 이 테이블이 이미 미리 채워져 있다는 사실을 무시하고 하나부터 다시 ID를 할당하고 있음을 의미합니다. 글쎄, 버그는 작동하는 순간이므로 절망하지 마십시오. 이런 일이 자주 발생합니다. 따라서 저는 경험이 많은 동료들에게 도움을 요청할 것입니다. "동료 여러분, 프로그램이 정상적으로 작동하도록 이 문제를 해결하는 방법을 댓글로 조언해 주세요." 도움이 도착하는 데 오랜 시간이 걸리지 않았고 Stas Pasinkov는 댓글을 통해 제가 어떤 방향을 봐야 하는지 알려 주었습니다. 이에 대해 그에게 특별히 감사드립니다! 그러나 문제는 클래스에서 필드에 대한 Client주석 전략을 잘못 지정했다는 것입니다 . 이 전략은 MySQL에 적합합니다. Oracle이나 PostrgeSQL을 사용하는 경우에는 다른 전략을 설정해야 합니다. 여기에서 기본 키 전략에 대해 자세히 알아볼 수 있습니다 . 저는 GenerationType.SEQUENCE 전략을 선택했습니다. 이를 구현하려면 initDB.sql 파일과 Client 클래스의 id 필드 주석을 약간 다시 작성해야 합니다. initDB.sql을 다시 작성하십시오. @GeneratedValue(strategy = GenerationType.IDENTITY)idSpring Boot의 RESTful 서비스에 PostgreSQL 데이터베이스를 추가합니다.  파트 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 열 유형이 변경되었지만 이에 대해서는 나중에 자세히 설명합니다. 아래에 새 시퀀스 클라이언트_id_seq를 생성하고 3으로 시작해야 함(populateDB.sql 파일의 마지막 ID가 2이기 때문에)을 나타내고 1씩 증가해야 함을 나타내는 줄을 추가했습니다. 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 사용자가 댓글에서 이에 대해 쓴 내용입니다. 할당 크기는 주로 "새 ID"를 위해 최대 절전 모드가 데이터베이스로 이동하는 것을 줄이기 위한 것입니다. 값 == 1이면 각 새 엔터티에 대해 최대 절전 모드로 전환되고 데이터베이스에 저장되기 전에 해당 ID에 대한 데이터베이스로 "실행"됩니다. 값이 1보다 큰 경우(예: 5), Hibernate는 "새" ID에 대해 데이터베이스에 덜 자주(예: 5회) 접속하고, 접속할 때 Hibernate는 데이터베이스에 이 번호를 예약하도록 요청합니다(우리의 경우). 경우, 5) 가치. 설명하신 오류는 최대 절전 모드가 50개의 기본 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 커뮤니티가 항상 도움을 줄 수 있는 힘임을 확인했습니다 ;)
지금은 여기서 끝내면 됩니다. Spring Boot의 RESTful 서비스에 PostgreSQL 데이터베이스를 추가합니다.  파트 2 - 4시간을 내어 이 자료를 읽어주신 모든 분들께 감사드립니다. 귀하의 의견, 관찰, 추가 및 건설적인 비판을 기쁘게 생각합니다. 아마도 당신은 이 문제에 대한 보다 우아한 해결책을 제시할 것입니다. 물론 나는 UPD "키워드"를 통해 당신을 저자로 언급하면서 이 기사에 추가할 것을 약속합니다. 글쎄, 일반적으로이 기사와 자료 제시 스타일이 마음에 들었고 일반적으로 JR에 대한 기사를 계속 써야할지 여부를 작성하십시오. 추가 사항은 다음과 같습니다. UPD1: 사용자 Justinian은 Java의 명명 규칙을 위반하지 않도록 패키지 이름을 , 프로젝트 이름 com.javarush.lectures.rest_example으로 바꿀 것을 강력히 권장했습니다. com.javarush.lectures.rest.exampleUPD2 사용자 Alexander PyanovClientRepository 는 클래스의 필드를 초기화하려면 ClientServiceImpl주석보다 생성자를 사용하는 것이 더 낫다고 제안했습니다 @Autowired. 이는 드문 경우에 얻을 수 있다는 사실로 설명되며 NullPointerException일반적으로 이것이 모범 사례이며 이에 동의합니다. 논리적으로 객체의 초기 기능에 필드가 필요한 경우 생성자에서 초기화하는 것이 좋습니다. 생성자가 없는 클래스는 객체로 어셈블되지 않으므로 이 필드는 단계에서 초기화됩니다. 객체 생성의. 수정 사항이 포함된 코드 조각을 추가하겠습니다(무엇을 무엇으로 바꿔야 하는지):
@Autowired
private ClientRepository clientRepository;

private final ClientRepository clientRepository;

public ClientServiceImpl(ClientRepository clientRepository) {
   this.clientRepository = clientRepository;
}
첫 번째 부분 링크: Spring Boot의 RESTful 서비스에 PostgreSQL 데이터베이스 추가. 1부 추신: 이 교육용 애플리케이션을 계속 개발하고 싶은 분이 계시다면 이 기사에 가이드 링크를 추가해 드리겠습니다. 아마도 언젠가 이 프로그램은 귀하의 포트폴리오에 작업을 추가할 수 있는 실제 비즈니스 응용 프로그램과 유사한 프로그램으로 성장할 것입니다. PPS 이 겸손한 기사와 관련하여 저는 이 펜 테스트를 사랑하는 소녀, 여성 및 숙녀 여러분에게 바치기로 결정했습니다. 이 여성이 없었다면 이제 Java도, JavaRush도, 프로그래밍도 자연계에 존재하지 않을지 누가 ​​알겠습니까 ? 사랑하는 똑똑한 사람들, 휴가를 축하합니다! 3월 8일을 축하해주세요! 행복하고 아름답습니다!
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION