JavaRush /Blog Java /Random-ES /Agregar una base de datos PostgreSQL a un servicio RESTfu...
Artur
Nivel 40
Tallinn

Agregar una base de datos PostgreSQL a un servicio RESTful en Spring Boot. Parte 2

Publicado en el grupo Random-ES
Agregar una base de datos PostgreSQL a un servicio RESTful en Spring Boot. Parte 1 Agregar una base de datos PostgreSQL a un servicio RESTful en Spring Boot.  Parte 2 - 1 Entonces, en la última parte aprendimos cómo instalar una base de datos PostgresSQL en una computadora, crear una base de datos en pgAdmin y también crear y eliminar tablas en ella de forma manual y mediante programación. En esta parte reescribiremos nuestro programa para que aprenda a trabajar con esta base de datos y tablas. ¿Porque nosotros? Porque yo mismo estoy aprendiendo contigo de este material. Y luego no solo resolveremos la tarea que tenemos entre manos, sino que también corregiremos los errores que surjan sobre la marcha, con la ayuda de los consejos de programadores más experimentados. Por así decirlo, aprenderemos a trabajar en equipo ;) Primero, creemos com.javarush.lectures.rest_exampleun nuevo paquete en una carpeta y llamémoslo repository. En este paquete crearemos una nueva interfaz 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>  {
}
Esta interfaz interactuará “mágicamente” con nuestras bases de datos y tablas. ¿Por qué mágicamente? Porque no necesitaremos escribir su implementación y el marco Spring nos la proporcionará. Sólo necesita crear dicha interfaz y ya podrá utilizar esta "magia". El siguiente paso es editar la clase Clientasí:
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
}
Todo lo que hicimos en esta clase fue simplemente agregar algunas anotaciones. Repasémoslos:
  • @Entity : indica que este bean (clase) es una entidad.
  • @Table : indica el nombre de la tabla que se mostrará en esta entidad.
  • @Id : ID de columna (clave principal: el valor que se utilizará para garantizar la unicidad de los datos en la tabla actual. Nota: Andrei )
  • @Columna : indica el nombre de la columna que está asignada a la propiedad de la entidad.
  • @GeneratedValue : indica que esta propiedad se generará según la estrategia especificada.
Los nombres de los campos de la tabla no tienen que coincidir con los nombres de las variables de la clase. Por ejemplo, si tenemos una variable firstName, le daremos al campo un nombre en la tabla first_name. Estas anotaciones se pueden establecer tanto directamente en los campos como en sus captadores. Pero si elige uno de estos métodos, intente mantener este estilo durante todo el programa. Utilicé el primer método sólo para acortar los listados. Puede encontrar una lista más completa de anotaciones para trabajar con bases de datos aquí . Ahora vayamos a la clase ClientServiceImply reescribámosla de la siguiente manera:
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;
    }
}
Como puede ver en el listado, todo lo que hicimos fue eliminar las líneas que ya no necesitábamos:
// Хранoще клиентов
private static final Map<Integer, Client>  CLIENT_REPOSITORY_MAP = new HashMap<>();

// Переменная для генерации ID клиента
private static final AtomicInteger CLIENT_ID_HOLDER = new AtomicInteger();
En su lugar, declaramos nuestra interfaz ClientRepositoryy también colocamos la anotación @Autowired encima para que Spring agregara automáticamente esta dependencia a nuestra clase. También delegamos todo el trabajo a esta interfaz, o más bien a su implementación, que Spring agregará. Pasemos a la etapa final y más interesante: probar nuestra aplicación. Abramos el programa Postman (vea cómo usarlo aquí ) y enviemos una solicitud GET a esta dirección: http://localhost:8080/clients. Obtenemos esta respuesta:
[
    {
        "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)"
    }
]
Enviamos una solicitud POST:
{
  "name" : "Amigo",
  "email" : "amigo@jr.com",
  "phone" : "+7 (191) 746-43-23"
}
Y... detectamos nuestro primer error en el programa:
{
    "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"
}
Agregar una base de datos PostgreSQL a un servicio RESTful en Spring Boot.  Parte 2 - 2 Miramos los registros y encontramos el siguiente error:

org.postgresql.util.PSQLException: ОШИБКА: повторяющееся significado ключа нарушает ограничение уникальности "clients_pkey"
  Detail: Ключ "(id)=(1)" уже существует.
Enviamos la misma solicitud POST nuevamente, el resultado es el mismo, pero con esta diferencia: Ключ "(id)=(2)" уже существует. enviamos la misma solicitud por tercera vez y obtenemos Estado: 201 Creado. Enviamos la solicitud GET nuevamente y recibimos una respuesta:
[
    {
        "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"
    }
]
Esto sugiere que nuestro programa está ignorando el hecho de que esta tabla ya se ha completado previamente y está asignando una identificación nuevamente a partir de uno. Bueno, un error es un momento de trabajo, no te desesperes, esto sucede a menudo. Por lo tanto, pediré ayuda a colegas más experimentados: "Estimados colegas, indiquen en los comentarios cómo solucionar este problema para que el programa funcione normalmente". La ayuda no tardó en llegar y Stas Pasinkov me dijo en los comentarios en qué dirección debía mirar. ¡Un agradecimiento especial a él por esto! Pero el caso es que en la clase Clientespecifiqué incorrectamente la estrategia para la anotación @GeneratedValue(strategy = GenerationType.IDENTITY)del campo id. Agregar una base de datos PostgreSQL a un servicio RESTful en Spring Boot.  Parte 2 - 3 Esta estrategia es adecuada para MySQL. Si trabajamos con Oracle o PostrgeSQL, entonces debemos establecer una estrategia diferente. Puede leer más sobre estrategias para claves primarias aquí . Elegí la estrategia GenerationType.SEQUENCE. Para implementarlo, necesitaremos reescribir ligeramente el archivo initDB.sql y, por supuesto, las anotaciones del campo id de la clase Cliente. Reescriba 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;
Qué ha cambiado: el tipo de columna de identificación de nuestra tabla ha cambiado, pero hablaremos de eso más adelante. Agregamos una línea a continuación en la que creamos una nueva secuencia client_id_seq, indicamos que debe comenzar con tres (porque la última identificación en el archivo populateDB.sql es 2) e indicamos que el incremento debe ocurrir en uno. Volvamos al tipo de columna de identificación. Aquí especificamos INTEGER, porque si dejamos SERIAL, la secuencia se creará automáticamente, con el mismo nombre client_id_seq, pero comenzará desde uno (lo que provocó el error del programa). Sin embargo, ahora, si desea eliminar una tabla, deberá eliminar adicionalmente esta secuencia, ya sea manualmente a través de la interfaz pgAdmin o mediante un archivo .sql usando los siguientes comandos:
DROP TABLE IF EXISTS clients;
DROP SEQUENCE IF EXISTS clients_id_seq
Pero si no usa un archivo como populateDB.sql para completar inicialmente la tabla, entonces puede usar los tipos SERIAL o BIGSERIAL para la clave principal y no tiene que crear la secuencia manualmente y, por lo tanto, no tiene que eliminarla. por separado. Puedes leer más sobre las secuencias en el sitio web de. Documentación de PostgreSQL . Pasemos a las anotaciones del campo idde clase Clienty formateémoslas de la siguiente manera:
@Id
@Column(name = "id")
@SequenceGenerator(name = "clientsIdSeq", sequenceName = "clients_id_seq", allocationSize = 1)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "clientsIdSeq")
private Integer id;
Lo que hicimos: instalamos una nueva anotación @SequenceGeneratorpara crear un generador de secuencia, le asignamos un nombre clientsIdSeq, indicamos que este es un generador para una secuencia clients_id_seqy agregamos un atributo. allocationSize = 1 Este es un atributo opcional, pero si no hacemos esto, obtendremos el siguiente error cuando ejecutemos el programa:

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]
Esto es lo que el usuario Andrei escribe sobre esto en los comentarios: asignaciónTamaño está destinado principalmente a reducir el viaje de hibernación a la base de datos para una "nueva identificación". Si el valor == 1, hiberne para cada nueva entidad, antes de guardarla en la base de datos, "ejecuta" la base de datos para obtener la identificación. Si el valor es > 1 (por ejemplo, 5), hibernate se comunicará con la base de datos para obtener una identificación "nueva" con menos frecuencia (por ejemplo, 5 veces), y al contactar, hibernate le pedirá a la base de datos que reserve este número (en nuestro caso, 5) valores. El error que describió sugiere que a Hibernate le gustaría recibir 50 identificadores predeterminados, pero en la base de datos indicó que está listo para emitir un identificador para esta entidad solo de acuerdo con el primero . El usuario Nikolya Kudryashov detectó otro error : si ejecuta una solicitud del artículo original http://localhost:8080/clients/1, se devolverá el error:
{
    "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"
}
Este error está relacionado con la inicialización diferida de Hibernate y, para solucionarlo, debemos agregar una anotación adicional a la clase Cliente:
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
De este modo:
@Entity
@Table(name = "clients")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Client {.....}
Ahora ejecutemos nuestro programa (después de eliminar la tabla de clientes de la base de datos si permanece allí desde la última vez) y comentemos 3 líneas del archivo application.properties:
#spring.datasource.initialization-mode=ALWAYS
#spring.datasource.schema=classpath*:database/initDB.sql
#spring.datasource.data=classpath*:database/populateDB.sql
La última vez comentamos sólo la última línea, pero... Como ya hemos creado y completado la tabla, esto me pareció más lógico en este momento. Pasemos a las pruebas, realicemos solicitudes GET, POST, PUT y DELETE a través de Postman, y veremos que los errores han desaparecido y todo funciona bien. Eso es todo, trabajo hecho. Ahora podemos resumir brevemente y considerar lo que hemos aprendido:
  • Instale PostgreSQL en su computadora
  • Crear bases de datos en pgAdmin
  • Crear y eliminar tablas de forma manual y programática
  • Rellenar tablas mediante archivos .sql
  • Aprendimos un poco sobre la interfaz “mágica” JpaRepository del framework Spring
  • Aprendimos sobre algunos errores que pueden surgir al crear un programa de este tipo.
  • Nos dimos cuenta de que no deberíamos avergonzarnos de pedir consejo a nuestros colegas.
  • Hemos confirmado que la comunidad JavaRush es una fuerza que siempre vendrá al rescate;)
Podemos terminar aquí por ahora. Agregar una base de datos PostgreSQL a un servicio RESTful en Spring Boot.  Parte 2 - 4Gracias a todos los que se tomaron el tiempo de leer este material. Estaré encantado de ver sus comentarios, observaciones, adiciones y críticas constructivas. Quizás ofrezca soluciones más elegantes a este problema, que prometo agregar a este artículo mediante la “palabra clave” UPD, mencionándolo a usted como autor, por supuesto. Bueno, en general, escribe si te gustó este artículo y este estilo de presentar el material, y en general, si debo seguir escribiendo artículos sobre JR. Aquí están las adiciones: UPD1: el usuario Justinian recomendó encarecidamente cambiar el nombre del paquete com.javarush.lectures.rest_examplea com.javarush.lectures.rest.exampley el nombre del proyecto, para no violar las convenciones de nomenclatura en Java. El usuario de UPD2 Alexander Pyanov sugirió que para inicializar un campo ClientRepositoryen una clase ClientServiceImples mejor usar un constructor que una anotación @Autowired. Esto se explica por el hecho de que en casos raros se puede obtener NullPointerExceptiony, en general, esta es la mejor práctica y estoy de acuerdo con ella. Lógicamente, si se requiere un campo para la funcionalidad inicial de un objeto, entonces es mejor inicializarlo en el constructor, porque una clase sin un constructor no se ensamblará en un objeto, por lo tanto, este campo se inicializará en la etapa de creación de objetos. Agregaré un fragmento de código con correcciones (qué debe reemplazarse con qué):
@Autowired
private ClientRepository clientRepository;

private final ClientRepository clientRepository;

public ClientServiceImpl(ClientRepository clientRepository) {
   this.clientRepository = clientRepository;
}
Enlace a la primera parte: Agregar una base de datos PostgreSQL a un servicio RESTful en Spring Boot. Parte 1 PD: Si alguno de ustedes desea continuar desarrollando esta aplicación educativa, estaré encantado de agregar un enlace a sus guías en este artículo. Quizás algún día este programa se convierta en algo similar a una aplicación empresarial real en la que pueda agregar trabajo a su cartera. PPS Respecto a este modesto artículo, he decidido dedicar esta prueba de la pluma a nuestras queridas niñas, mujeres y señoras. Quién sabe, tal vez ahora no habría Java, JavaRush, ni programación en la naturaleza, si no fuera por esta mujer . ¡Felicitaciones por sus vacaciones, querida gente inteligente! ¡Feliz 8 de marzo! ¡Sé feliz y hermosa!
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION