تركز الخطوات الأولى في Spring ، Rest API ، على PUT بالتزامن مع الواجهة الأمامية

القليل عن نفسي: في الوقت الحالي أنا طالب في Skillbox وألتحق بالدورة "Java-developer". في أي حال من الأحوال لا الإعلان ، أقول القليل عن نفسي. بدأ في تعلم جافا في مايو 2019 ، قبل أن يدرس HTML و CSS و JS قليلاً بمفرده.

في الواقع ، دفعتني إلى كتابة هذه المقالة مع إدراك كيفية عمل الواجهة الأمامية مع الواجهة الخلفية وعدم وجود فهم لطلب PUT. في كل مكان قمت فيه بـ "google" ، تم تنفيذ Rest API بطلبات POST و GET ، وأحيانًا باستخدام DELETE ولم تكن هناك أمثلة للواجهة الأمامية. أود أن أنقل ، أولاً وقبل كل شيء ، نفس الشيء الذي أقوم فيه بتطبيق واجهة برمجة تطبيقات REST مع الواجهة الأمامية ، حتى يتحقق التفاهم. لكن المقالة مخصصة ليس فقط للمبتدئين الذين أنا ، ولكن أيضًا للمستخدمين ذوي الخبرة لتقنيات الربيع ، لأنني في التعليقات أريد أن أرى التعاليم الصالحة للرفاق الأكبر سنًا. بعد كل شيء ، سأصف قراري بناءً على خبرتي (اقرأ نقص الخبرة).

لقد واجهت مشكلة فهم الربيع ، وتحديداً مع طلب PUT ، أي تغيير بيانات عنصر في قاعدة البيانات. سأصف أيضًا طلبات POST و GET. بشكل عام ، CRUD القياسي (صحيح إذا كنت مخطئًا). وكذلك واجهة أمامية صغيرة ، أي أنه يتم إرسال طلب إلى الخادم ومعالجة الاستجابة.

انا إستعملت:

  • مخضرم
  • MySQL
  • IntelliJ IDEA

أريد أيضًا أن أقدم حجزًا بأن الواجهة الأمامية كانت جزئية في مشروع التدريب ، وقمت بتنفيذ استفسارات PUT و DELETE.

يمكن الاطلاع على المشروع بأكمله على GitHub .

اختراق مباشر صغير: عند إنشاء مشروع باستخدام مخضرم ، يقفز إصدار جافا إلى الإصدار الخامس ، لإصلاح ذلك في pom.xml ، نكتب ما يلي ، حيث يكون الرقم هو الإصدار.

افتح Life Hack
<properties>
    <maven.compiler.target>11</maven.compiler.target>
    <maven.compiler.source>11</maven.compiler.source>
</properties>


اتصال الربيع


بالنسبة للمبتدئين ، في pom.xml نقوم بتوصيل Boot Spring كأصل ، يتم تفسير ذلك من خلال حقيقة أن المزيد من اتصال التبعيات لا يتعارض مع الإصدار:

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

الآن نقوم بتوصيل Spring web وهي المسؤولة عن إطلاق التطبيق:

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

نقوم بإنشاء وتشغيل التطبيق


تحتاج إلى البدء في كتابة التطبيق في الدليل الصحيح ، وهو src / main / java / main ، نعم ، هذا صحيح ، لم أجد بعد شرحًا توضيحيًا لذلك ، أعتقد أنني سأكتشف ذلك مع مرور الوقت.

وعلى الفور وضع الهيكل الكامل للتطبيق.

صورة

أول شيء فعلته هو إنشاء فئة Main.java لتشغيل التطبيق مع تعليق توضيحي @SpringBootApplication:

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

ويمكنك بالفعل النقر فوق تشغيل وحتى سيبدأ الخادم الخاص بي!

صورة

تم إطلاقه على المنفذ 8080. يمكنك الذهاب إلى العنوان http://localhost:8080/وسنرى الخطأ 404 لأنه لا توجد صفحات حتى الآن.

في الإنصاف ، تحتاج إلى تنفيذ واجهة أمامية.

تحتاج إلى تضمين التبعية في pom.xml لقالب HTML.

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

للبدء ، صفحة index.html الرئيسية في دليل src / main / resources / templates .

هنا مع مثل هذا الترميز العادي
<!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>


نكتب أيضًا الأنماط في دليل src / main / resources / static / css ، وننشئ styles.css

عرض الأنماط
* {
    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;
}


يمكنك محاولة بدء التطبيق والذهاب http://localhost:8080/ويمكنك الإعجاب بصفحة البداية ، حتى الآن بدون إجراء.

وبالطبع js مع المكوّن الإضافي jQuery في دليل src / main / resources / static / js ، سأحجز مرة أخرى ، jQuery وبعض ملفات main.js المكتوبة موجودة بالفعل في مشروع التدريب .
وكل نفس المحور حول جافا والربيع، وذلك وصلات إلى شبيبة كود كامل يعتقدون يكون كافيا:
لينك لمسج-3.4.0.min.js
لينك لmain.js .

أدناه سيكون هناك اهتمام خاص لطلب GET و PUT. سواء من جانب الخادم ، أو من الواجهة الأمامية.

يمكنك الآن محاولة بدء المشروع والتأكد من عمل الواجهة الأمامية والإجراء أيضًا (يعمل زر الإضافة على تشغيل النموذج).

تفاعل DB


الخطوة التالية هي الكيان للتفاعل مع قاعدة البيانات ، لذلك نربط تبعية jpa لبيانات الربيع:

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

وفي دليل src / main / java / main / model أقوم بإنشاء فئة POJO Todo لإرفاق تعليق توضيحي @Entity.

نعلن الحقول ، سيكون لدي: id ، الاسم ، الوصف ، التاريخ.

اهتمام خاص setDate()، لقد فعلت ذلك بالضبط ، عند إدخال String ثم التحويل إلى java.util.Date ، وحتى مع ذلك atStartOfDay().atZone(ZoneId.of("UTC")، ألاحظ أيضًا التعليقات التوضيحية في حقل التاريخ:

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

أضف تبعية في pom.xml لإنشاء اتصال بـ MySQL:

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

في دليل src / main / resources ، قم بإنشاء application.properties وكتابة البيانات للاتصال بقاعدة البيانات:

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

الآن دعنا ننتقل إلى إنشاء المستودع. في دليل src / main / java / main / model ، قم بإنشاء واجهة TodoRepository مع التعليقات التوضيحية @Repositoryووراثة CrudRepository <Todo، Integer>. الاستطراد الغنائي - كما أفهمها ، هذه حشية بين قاعدة البيانات ووحدة التحكم ، الربيع جيد في هذا ، لا تحتاج إلى إنشاء مآخذ ، لا داعي للقلق بشأن التفاعل مع قاعدة البيانات ، فهي تفعل كل شيء من أجلك.

package main.model;

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

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

في الواقع ، سيحدث الاتصال بقاعدة البيانات من خلال هذا المستودع.

حان الوقت الآن لإنشاء وحدة تحكم حيث تتم معالجة الطلبات من الواجهة الأمامية والتفاعل مع قاعدة البيانات واستجابات الواجهة الأمامية.

في دليل src / main / java / main / controller ، قم بإنشاء فئة TodoController مع تعليق توضيحي @RestController، وقم بتعريف متغير TodoRepository وتهيئته من خلال المنشئ.

لنبدأ بطلب POST. نقوم بإنشاء طريقة add () بقبول Todo وإرجاع int (id) ، @PostMapping(“/todo-list/”)ووضع علامة مع التعليق التوضيحي والمسار الذي سنضيفه. نأخذ المستودع save()ونحفظ كائن Todo الذي جاء مع الطلب إلى قاعدة البيانات في الطريقة . مجرد سحر.

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

بشكل عام ، على غرار GET و DELETE ، ولكن باستخدام id وإرجاع Todo في shell ResponseEntity shell. لاحظ أيضًا أن معلمة الطريقة تم get()تمييزها بتعليق توضيحي ، أدناه أكثر تفصيلاً قليلاً. ثم يتم إنشاء استجابة ResponseEntity.ok(todoOptional.get());، أي الرمز 200 ، أو إذا لم يتم العثور عليها بواسطة هذا المعرف ، فتُرجع الرمز 404 مع نص فارغ.

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

ماذا يحدث على الجانب الأمامي؟

باستخدام مثال GET:

النقر فوق ارتباط في قائمة todo => نقوم بسحب id todo => يتم تكوين طلب (لاحظ أن المعرّف نفسه لم يتم تمريره إلى الطريقة. يتم استخراج المعرّف في طريقة get () من (value = "/ todo-list / {id} ") لهذا ، نحتاج إلى تعليق توضيحي @PathVariableفي معلمة الطريقة) => تأتي الإجابة على شكل كائن Todo => الواجهة الأمامية تفعل ما تراه مناسبًا ، في هذه الحالة ، لدى Todo وصف وتاريخ.

قطعة من كود main.js ، تنفيذ 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;
});


لنقم بإنشاء وحدة تحكم أخرى ستعرض على الفور قائمة المهام على صفحة البداية. نعمل أيضًا مع المستودع ونحصل على قائمة Todo ، ثم يتم تمرير todoList بطريقة سحرية إلى الواجهة الأمامية:

@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";
    }
}

هنا مع مثل هذه التصحيحات في index.html يحدث التحميل الديناميكي لـ 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>

طلب وضع


في TodoController ، نقوم بإنشاء طريقة put()تحتوي على تعليق توضيحي @PutMappingفي خريطة الإدخال <سلسلة ، سلسلة> مع تعليق توضيحي @RequestParamو int ، يتم استخراجه من القيمة ، عند إخراج Todo ملفوف في ResponseEntity. وأيضًا لا يحتوي المستودع على طريقة التحديث () ، لذلك يحدث كل شيء على النحو التالي:

يتم استخراج Todo من قاعدة البيانات عبر todoRepository بواسطة id => يتم تعيين المعلمات الجديدة Todo => يتم حفظها في قاعدة البيانات من خلال المستودع => يتم إرسال استجابة الواجهة

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

في الواجهة الأمامية في هذا الوقت:

انقر على زر "تغيير" => يتم جمع بيانات Todo من العنصر => يتم تحرير النموذج لتغيير الحالة (تتم إعادة تسمية اسم النموذج ويتم استبدال الزر في قيمة إدخال Todo data) => يتم فتح النموذج => يتم دفع بيانات التغيير في => انقر فوق الزر "تغيير" في النموذج => جمع البيانات => يتم إنشاء طلب PUT (المسار ، البيانات) => تلقي استجابة من كائن Todo المعدل ، ولكن بنفس المعرف => الواجهة الأمامية تفعل ما تريد ، في هذه الحالة استبدال البيانات لكى يفعل.

قطعة من كود main.js ، تنفيذ 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;
    });
});


يمكنك التعرف على المشروع بمزيد من التفاصيل حول جيثب .

إنه مكتوب للمبتدئين من المبتدئين ، لكني أود سماع النقد البناء من المستخدمين ذوي الخبرة ، إنه أمر رائع إذا أوضحت في التعليقات لماذا تأتي Map <String، String> على جانب وحدة التحكم عند السؤال عن PUT ، لماذا لا يمكنني إرسال Todo هناك.

مصادر:


All Articles