Premiers pas dans Spring, Rest API, focus sur PUT en conjonction avec le frontend

Un peu de moi: en ce moment je suis étudiant en Skillbox et je prends le cours "Java-développeur". En aucun cas pas de publicité, je parle un peu de moi. Il a commencé à apprendre Java en mai 2019, avant d'étudier un peu seul le HTML, le CSS et le JS.

En fait, cela m'a poussé à écrire cet article avec une conscience de la façon dont le frontend fonctionne avec le backend ensemble et un manque de compréhension de la demande PUT. Partout où je «google», l'API Rest a été implémentée avec des requêtes POST et GET, parfois avec DELETE et il n'y avait aucun exemple de frontend. Je voudrais transmettre, tout d'abord, la même chose que j'implémente l'API REST avec le frontend, pour que la compréhension vienne. Mais l'article est destiné non seulement aux débutants que je suis, mais aussi aux utilisateurs expérimentés des technologies Spring, car dans les commentaires, je veux voir les instructions justes des camarades seniors. Après tout, je décrirai ma décision en fonction de mon expérience (lire le manque d'expérience).

J'ai rencontré le problème de la compréhension de Spring, et en particulier de la demande PUT, c'est-à-dire de la modification des données d'un élément dans la base de données. Je décrirai également les requêtes POST et GET. En général, CRUD standard (correct si je me trompe). Et aussi une petite interface, c'est-à-dire que là-bas, une demande est envoyée au serveur et la réponse est traitée.

J'ai utilisé:

  • Maven
  • MySQL
  • IntelliJ IDEA

Je veux également faire une réservation que le frontend était partiellement dans le projet de formation, j'ai implémenté des requêtes PUT et DELETE.

L'ensemble du projet peut être consulté sur GitHub .

Un petit hack en direct: lors de la création d'un projet avec maven, la version java passe au cinquième, pour corriger cela dans pom.xml, nous écrivons ce qui suit, où le numéro est la version.

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


Connexion à ressort


Pour commencer, dans pom.xml, nous connectons Spring Boot en tant que parent, cela s'explique par le fait que la connexion ultérieure des dépendances n'entre pas en conflit par version:

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

Maintenant, nous nous connectons Spring web est responsable du lancement de l'application:

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

Nous créons et exécutons l'application


Vous devez commencer à écrire l'application dans le bon répertoire, à savoir src / main / java / main , oui, c'est vrai, je n'ai pas encore trouvé d'explication à cela, je pense que je le découvrirai avec le temps.

Et exposez immédiatement toute la structure de l'application.

image

La première chose que j'ai faite a été de créer une classe Main.java pour exécuter l'application avec annotation @SpringBootApplication:

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

Et vous pouvez déjà cliquer sur Exécuter et même Mon serveur démarrera!

image

Lancé sur le port 8080. Vous pouvez aller à l'adresse http://localhost:8080/et nous verrons l'erreur 404 car il n'y a pas encore de pages.

En toute justice, vous devez implémenter un frontend.

Vous devez inclure la dépendance dans pom.xml au modèle HTML.

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

Pour commencer, la page de démarrage index.html dans le répertoire src / main / resources / templates .

Ici avec un tel balisage 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>


Nous écrivons également des styles dans le répertoire src / main / resources / static / css, créons styles.css

Afficher les styles
* {
    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;
}


Vous pouvez essayer de démarrer l'application et aller http://localhost:8080/et vous pouvez admirer la page de démarrage, mais jusqu'à présent sans aucune action.

Et bien sûr js avec le plugin jQuery dans le répertoire src / main / resources / static / js , encore une fois je ferai une réservation, jQuery et une partie de main.js écrite existaient déjà dans le projet de formation .
Tout le même hub sur Java et Spring, donc des liens vers les js pleins de code pensent sera suffisant:
Lien vers jquery-3.4.0.min.js
Lien vers main.js .

Ci-dessous sera une attention particulière à la demande de GET et PUT. Du côté serveur et du frontend.

Vous pouvez maintenant essayer de démarrer le projet et vous assurer que le frontend fonctionne et l'action aussi (le bouton ajouter lance le formulaire).

Interaction DB


L'étape suivante est l'entité pour interagir avec la base de données, pour cela nous connectons la dépendance jpa des données Spring:

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

Et dans le répertoire src / main / java / main / model je crée une classe POJO pour attacher une annotation @Entity.

Nous déclarons les champs, j'aurai: id, nom, description, date.

Une attention particulière setDate(), je l'ai fait, à l'entrée de String puis à la conversion en java.util.Date, et même avec atStartOfDay().atZone(ZoneId.of("UTC"), je fais également attention à l'annotation du champ date:

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());
    }
}

Ajoutez une dépendance dans pom.xml pour établir une connexion à MySQL:

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

Dans le répertoire src / main / resources , créez application.properties et écrivez des données pour vous connecter à la base de données:

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

Passons maintenant à la création du référentiel. Dans le répertoire src / main / java / main / model , créez l'interface TodoRepository avec annotation @Repositoryet héritez CrudRepository <Todo, Integer>. Digression lyrique - si je comprends bien, il s'agit d'un joint entre la base de données et le contrôleur, Spring est bon en cela, vous n'avez pas besoin de créer de sockets, vous n'avez pas à vous soucier d'interagir avec la base de données, il fait tout pour vous.

package main.model;

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

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

En fait, la communication avec la base de données se fera via ce référentiel.

Il est maintenant temps de créer un contrôleur où les demandes du front-end, l'interaction avec la base de données et les réponses du front-end seront traitées.

Dans le répertoire src / main / java / main / controller , créez la classe TodoController avec annotation @RestController, déclarez la variable TodoRepository et initialisez-la via le constructeur.

Commençons par la requête POST. Nous créons la méthode add () acceptant Todo et renvoyant int (id), marquons avec annotation @PostMapping(“/todo-list/”)et le chemin où nous allons ajouter. Nous prenons le référentiel et save()enregistrons l'objet Todo fourni avec la demande dans la base de données dans la méthode . Juste magique.

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

En général, similaire à GET et DELETE, mais en utilisant id et en retournant Todo dans le shell ResponseEntity. Notez également que le paramètre de la méthode est get()marqué d'une annotation, ci-dessous est un peu plus détaillé. Ensuite, une réponse est générée ResponseEntity.ok(todoOptional.get());, c'est-à-dire le code 200, ou s'il n'est pas trouvé par cet identifiant, renvoie le code 404 avec le corps null.

@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());
}

Que se passe-t-il du côté frontal?

En utilisant l'exemple GET: en

cliquant sur un lien dans la liste todo => nous retirons id todo => une demande est formée (notez que l'id lui-même n'est pas transmis à la méthode. L'identifiant dans la méthode get () est extrait de (valeur = "/ todo-list / {id} ") pour cela, nous avons besoin d'une annotation @PathVariabledans le paramètre de méthode) => la réponse se présente sous la forme d'un objet Todo => le front-end fait ce qu'il juge bon, dans ce cas, Todo a une description et une date.

Morceau de code main.js, implémentation 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;
});


Créons un autre contrôleur qui affichera immédiatement la liste de tâches sur la page de démarrage. Nous travaillons également avec le référentiel et obtenons la liste Todo, puis la liste des tâches est magiquement transmise au 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";
    }
}

Ici, avec de telles corrections dans index.html, le chargement dynamique de todoList se produit:

<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>

Demande PUT


Dans TodoController, nous créons une méthode put()avec une annotation @PutMappingà l'entrée Map <String, String> avec une annotation @RequestParamet un int, qui est extrait de la valeur, enveloppé dans ResponseEntity à la sortie de Todo. Et le référentiel n'a pas non plus la méthode update (), donc tout se passe comme suit:

Todo est extrait de la base de données via todoRepository par id => de nouveaux paramètres sont attribués Todo => il est enregistré dans la base de données via le référentiel => la réponse du frontend est envoyée

@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());
}

À l'avant à ce moment:

cliquez sur le bouton «Modifier» => Les données à Todo sont collectées à partir de l'élément => le formulaire est modifié pour changer la casse (le nom du formulaire est renommé et le bouton est substitué dans la valeur d'entrée Todo data) => le formulaire est ouvert => les données à modifier sont introduites dans => cliquez sur le bouton "Modifier" dans le formulaire => collecte de données => une requête PUT est générée (chemin, données) => réception d'une réponse de l'objet Todo modifié, mais avec le même id => le frontend fait ce qu'il veut, dans ce cas le remplacement des données Faire.

Morceau de code main.js, implémentation 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;
    });
});


Vous pouvez vous familiariser avec le projet plus en détail sur le github .

C'est écrit pour les débutants par un débutant, mais j'aimerais entendre des critiques constructives d'utilisateurs expérimentés, c'est tout aussi cool si vous expliquez dans les commentaires pourquoi Map <String, String> vient du côté du contrôleur lorsque vous demandez PUT, pourquoi je ne peux pas soumettre Todo là-bas.

Ressources:


All Articles