Primeros pasos en Spring, Rest API, enfóquese en PUT junto con la interfaz

Un poco sobre mí: en este momento soy un estudiante de Skillbox y tomo el curso "Desarrollador Java". En ningún caso no es publicidad, cuento un poco sobre mí. Comenzó a aprender Java en mayo de 2019, antes de eso estudió HTML, CSS y JS un poco por su cuenta.

En realidad, me empujó a escribir este artículo con una conciencia de cómo funciona la interfaz con el back-end y una falta de comprensión de la solicitud PUT. En todas partes donde "googleé", la API Rest se implementó con solicitudes POST y GET, a veces con DELETE y no había ejemplos de la interfaz. Me gustaría transmitir, en primer lugar, lo mismo que implemento la API REST junto con el frontend, para que llegue la comprensión. Pero el artículo está destinado no solo a los principiantes que soy, sino también a los usuarios experimentados de las tecnologías de Spring, porque en los comentarios quiero ver las enseñanzas justas de los camaradas mayores. Después de todo, describiré mi decisión en función de mi experiencia (lea la falta de experiencia).

Me encontré con el problema de comprender Spring, y específicamente con la solicitud PUT, es decir, cambiar los datos de un elemento en la base de datos. También describiré las solicitudes POST y GET. En general, CRUD estándar (correcto si me equivoco). Y también una pequeña interfaz, es decir, como allí, se envía una solicitud al servidor y se procesa la respuesta.

Solía:

  • Maven
  • MySQL
  • IDEA IntelliJ

También quiero hacer una reserva de que el frontend estaba parcialmente en el proyecto de capacitación, implementé consultas PUT y DELETE.

Todo el proyecto se puede ver en GitHub .

Un pequeño truco en vivo: al crear un proyecto con maven, la versión de Java salta a la quinta, para arreglar esto en pom.xml escribimos lo siguiente, donde el número es la versión.

Open Life Hack
<properties>
    <maven.compiler.target>11</maven.compiler.target>
    <maven.compiler.source>11</maven.compiler.source>
</properties>


Conexión de primavera


Para empezar, en pom.xml conectamos Spring boot como padre, esto se explica por el hecho de que una mayor conexión de dependencias no entra en conflicto por versión:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.4.RELEASE</version>
</parent>

Ahora nos conectamos Spring web se encarga de lanzar la aplicación:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

Creamos y ejecutamos la aplicación


Debe comenzar a escribir la aplicación en el directorio correcto, es decir, src / main / java / main , sí, es cierto, todavía no he encontrado una explicación explicativa para esto, creo que lo descubriré con el tiempo.

E inmediatamente diseñe toda la estructura de la aplicación.

imagen

Lo primero que hice fue crear una clase Main.java para ejecutar la aplicación con anotaciones @SpringBootApplication:

@SpringBootApplication
public class Main {
    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
    }
}

¡Y ya puede hacer clic en Ejecutar e incluso Mi servidor se iniciará!

imagen

Lanzado en el puerto 8080. Puede ir a la dirección http://localhost:8080/y veremos el error 404 porque todavía no hay páginas.

Para ser justos, debe implementar una interfaz. Debe

incluir la dependencia en pom.xml a la plantilla HTML.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Para comenzar, la página de inicio index.html en el directorio src / main / resources / templates .

Aquí con un marcado tan simple
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>ToDo List</title>
    <script src="/js/jquery-3.4.0.min.js"></script>
    <script src="/js/main.js"></script>
    <link rel="stylesheet" type="text/css" href="/css/styles.css">
</head>
<body>
    <div id="todo-form">
        <form>
            <label> :
            </label>
            <input id="todo-form-name" type="text" name="name" value="">
            <label>:
            </label>
            <input id="todo-form-description" type="text" name="description" value="">
            <label>  :
            </label>
            <input id="todo-form-date" type="date" name="date" value="">
            <hr>
        </form>
    </div>
    <h1> </h1>
    <button id="show-add-todo-list"> </button>
    <br><br>
    <div id="todo-list">

    </div>
</body>
</html>


También escribimos estilos en el directorio src / main / resources / static / css, create styles.css

Ver estilos
* {
    font-family: Arial, serif;
}

#todo-form {
    display: none;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    z-index: 10;
    background-color: #88888878;
}

#todo-form form {
    background-color: white;
    border: 1px solid #333;
    width: 300px;
    padding: 20px;
}

#todo-form h2 {
    margin-top: 0;
}

#todo-form label {
    display: block;
}

#todo-form form > * {
    margin-bottom: 5px;
}

h4 {
    margin-bottom: 0;
}


Puede intentar iniciar la aplicación e ir http://localhost:8080/y puede admirar la página de inicio, aunque hasta ahora sin una acción.

Y, por supuesto, js con el complemento jQuery en el directorio src / main / resources / static / js , una vez más haré una reserva, jQuery y una parte de main.js escrita ya existía en el proyecto de capacitación .
Todo al mismo concentrador sobre Java y primavera, por lo que los enlaces a los js código completo piensan será suficiente:
Enlace a jquery-3.4.0.min.js
Enlace a main.js .

A continuación se prestará especial atención a la solicitud de GET y PUT. Tanto del lado del servidor como del frontend.

Ahora puede intentar iniciar el proyecto y asegurarse de que la interfaz funciona y la acción también (el botón Agregar inicia el formulario).

Interacción DB


El siguiente paso es la entidad para interactuar con la base de datos, para esto conectamos la dependencia jpa de datos Spring:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

Y en el directorio src / main / java / main / model creo una clase POJO Todo adjunte una anotación @Entity.

Declaramos campos, tendré: id, nombre, descripción, fecha.

Atención especial setDate(), hice exactamente eso, en la entrada de String y luego la conversión a java.util.Date, e incluso con atStartOfDay().atZone(ZoneId.of("UTC"), también presto atención a la anotación del campo de fecha:

package main.model;

import javax.persistence.*;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;

@Entity
public class Todo {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;
    private String name;
    private String description;
    @Temporal(TemporalType.DATE)
    private Date date;

    //getters and setters …
   
    public void setDate(String date) {
        this.date = Date.from(LocalDate.parse(date).atStartOfDay().atZone(ZoneId.of("UTC")).toInstant());
    }
}

Agregue una dependencia en pom.xml para establecer una conexión a MySQL:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.19</version>
</dependency>

En el directorio src / main / resources , cree application.properties y escriba datos para conectarse a la base de datos:

spring.datasource.url=jdbc:mysql://localhost:3306/todolist?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=none

Ahora pasemos a crear el repositorio. En el directorio src / main / java / main / model , cree la interfaz TodoRepository con anotaciones @Repositoryy herede CrudRepository <Todo, Integer>. Digresión de letras: según tengo entendido, esta es una junta entre la base de datos y el controlador, Spring es bueno en esto, no necesita crear sockets, no necesita preocuparse por interactuar con la base de datos, hace todo por usted.

package main.model;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface TodoRepository extends CrudRepository<Todo, Integer> {
}

En realidad, la comunicación con la base de datos se realizará a través de este repositorio.

Ahora es el momento de crear un controlador donde se procesarán las solicitudes del front-end, la interacción con la base de datos y las respuestas del front-end.

En el directorio src / main / java / main / controller , cree la clase TodoController con anotaciones @RestController, declare la variable TodoRepository e inicialice a través del constructor.

Comencemos con la solicitud POST. Creamos el método add () aceptando Todo y devolviendo int (id), marca con anotación @PostMapping(“/todo-list/”)y la ruta donde agregaremos. Tomamos el repositorio y save()guardamos el objeto Todo que vino con la solicitud en la base de datos en el método . Solo magia.

@PostMapping("/todo-list/")
public int add(Todo todo) {
    Todo newTodo = todoRepository.save(todo);
    return newTodo.getId();
}

En general, similar a GET y DELETE, pero usando id y devolviendo Todo en el shell ResponseEntity. También tenga en cuenta que el parámetro del método está get()marcado con una anotación, a continuación se muestra un poco más detallado. Luego se genera una respuesta ResponseEntity.ok(todoOptional.get());, es decir, el código 200, o si este id no lo encuentra, devuelve el código 404 con cuerpo nulo.

@GetMapping("/todo-list/{id}")
public ResponseEntity<Todo> get(@PathVariable int id){
    Optional<Todo> todoOptional = todoRepository.findById(id);
    if(todoOptional.isEmpty()){
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
    }
    return ResponseEntity.ok(todoOptional.get());
}

¿Qué está pasando en el lado frontend?

Usando el ejemplo GET: al

hacer clic en un enlace en la lista de todo => sacamos id todo => se forma una solicitud (tenga en cuenta que la identificación en sí no se pasa al método. La identificación en el método get () se extrae de (value = "/ todo-list / {id} ") para esto, necesitamos una anotación @PathVariableen el parámetro método) => la respuesta viene en forma de un objeto Todo => el front-end hace lo que le parece, en este caso, Todo tiene una descripción y una fecha.

Pieza de código main.js, implementación GET
$(document).on('click', '.todo-link', function(){
    var link = $(this);
    var todoId = link.data('id');
    $.ajax({
        method: "GET",
        url: '/todo-list/' + todoId,
        success: function(response)
        {
            if($('.todo-div > span').is('#' + todoId)){
                return;
            }
            link.parent().append(codeDataTodo(response, todoId));
        },
        error: function(response)
        {
            if(response.status == 404) {
                alert('  !');
            }
        }
    });
    return false;
});


Creemos otro controlador que muestre inmediatamente la lista de tareas pendientes en la página de inicio. También trabajamos con el repositorio y obtenemos la lista de Todo, y luego todoList se pasa mágicamente al front-end:

@Controller
public class DefaultController {
    @Autowired
    TodoRepository todoRepository;
    @RequestMapping("/")
    public String index(Model model){
        Iterable<Todo>  todoIterable = todoRepository.findAll();
        ArrayList<Todo> todoList = new ArrayList<>();
        for(Todo todo : todoIterable){
            todoList.add(todo);
        }
        model.addAttribute("todoList", todoList);
        return "index";
    }
}

Aquí con tales correcciones en index.html se produce la carga dinámica de todoList:

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<div id="todo-list">
    <div class="todo-div" th:each="todo : ${todoList}" th:attr="id=${todo.id}">
        <a href="#" class="todo-link" th:attr="data-id=${todo.id}" th:text="${todo.name}"></a>
        <br>
    </div>
</div>

PONER solicitud


En TodoController creamos un método put()con una anotación @PutMappingen la entrada Map <String, String> con una anotación @RequestParamy un int que se extrae del valor envuelto en ResponseEntity en la salida de Todo. Y también el repositorio no tiene el método update (), por lo que todo sucede de la siguiente manera:

Todo se extrae de la base de datos a través de todoRepository por id => se asignan nuevos parámetros Todo => se guarda en la base de datos a través del repositorio => se envía la respuesta de la interfaz

@PutMapping(value = "todo-list/{id}")
public ResponseEntity<Todo> put(@RequestParam Map<String, String> mapParam, @PathVariable int id){
    Optional<Todo> todoOptional = todoRepository.findById(id);
    if(todoOptional.isEmpty()){
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
    }
    todoOptional.get().setName(mapParam.get("name"));
    todoOptional.get().setDescription(mapParam.get("description"));
    todoOptional.get().setDate(mapParam.get("date"));
    todoRepository.save(todoOptional.get());
    return ResponseEntity.ok(todoOptional.get());
}

En la parte frontal en este momento:

haga clic en el botón "Cambiar" => Todos los datos se recopilan del elemento => el formulario se edita para cambiar el caso (el nombre del formulario se renombra y el botón se sustituye en el valor de entrada Todo datos) => el formulario se abre => se introducen los datos para el cambio => haga clic en el botón "Cambiar" en el formulario => recopilación de datos => se genera una solicitud PUT (ruta, datos) => recibe una respuesta del objeto Todo modificado, pero con el mismo id => la interfaz hace lo que quiere, en este caso reemplazo de datos Que hacer.

Pieza de código main.js, implementación de PUT
//Update _todo and show updating _todo form
$(document).on('click', '#show-update-todo-list', function(){
    var buttonUpdate = $(this);
    var todoId = buttonUpdate.data('id');
    var todoName = buttonUpdate.data('name');
    var todoDescription = buttonUpdate.data('description');
    var todoDate = buttonUpdate.data('date');
    todoFormNameAndButton(' ', '', 'update-todo');
    todoInputValue(todoName, todoDescription, todoDate);
    $('#todo-form').css('display', 'flex');
    $('#update-todo').click(function() {
        var data = $('#todo-form form').serialize();
        $.ajax({
            method: "PUT",
            url: '/todo-list/' + todoId,
            data: data,
            success: function(response) {
                $('#todo-form').css('display', 'none');
                response.date = response.date.slice(0,10);
                $('.todo-div#' + todoId  + ' > a').text(response.name);
                $('.todo-div#' + todoId +' > span').replaceWith(codeDataTodo(response, todoId));
            }
        });
        return false;
    });
});


Puede familiarizarse con el proyecto con más detalle en el github .

Está escrito para principiantes desde un principiante, pero me gustaría escuchar críticas constructivas de usuarios experimentados, es igual de bueno si explica en los comentarios por qué Map <String, String> viene del lado del controlador cuando solicito PUT, por qué no puedo enviar Todo allí.

Recursos:


All Articles