JavaRush /Java blogi /Random-UZ /REST API va ma'lumotlarni tekshirish
Денис
Daraja
Киев

REST API va ma'lumotlarni tekshirish

Guruhda nashr etilgan
Birinchi qismga havola: REST API va keyingi test topshirig'i Xo'sh, ilovamiz ishlayapti, biz undan qandaydir javob olishimiz mumkin, ammo bu bizga nima beradi? U hech qanday foydali ish qilmaydi. Haqiqatan ham, keling, foydali narsani amalga oshiraylik. Avvalo, build.gradle-ga bir nechta yangi bog'liqliklarni qo'shamiz, ular biz uchun foydali bo'ladi:
implementation 'org.apache.commons:commons-lang3:3.12.0'
implementation 'org.apache.commons:commons-collections4:4.4'
implementation 'org.springframework.boot:spring-boot-starter-validation'
Va biz qayta ishlashimiz kerak bo'lgan haqiqiy ma'lumotlardan boshlaymiz. Keling, qat'iyatlilik paketimizga qaytaylik va ob'ektni to'ldirishni boshlaylik. Esingizda bo'lsa, biz uni faqat bitta maydon bilan mavjud bo'lishini qoldirgan edik va keyin avtomatik `@GeneratedValue(strategy = GenerationType.IDENTITY)` orqali yaratiladi. Keling, birinchi bobdagi texnik xususiyatlarni eslaylik:
{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [
  ]
}
Bizda birinchi marta yetarli maydonlar bor, shuning uchun uni amalga oshirishni boshlaylik. Birinchi uchta maydon savollar tug'dirmaydi - bu oddiy qatorlar, ammo ish haqi sohasi allaqachon taklif qiladi. Nega haqiqiy chiziq? Haqiqiy ishda bu ham sodir bo'ladi, mijoz sizning oldingizga keladi va aytadi - men sizga ushbu foydali yukni yubormoqchiman va siz uni qayta ishlaysiz. Siz, albatta, elkangizni qisib, buni qilishingiz mumkin, siz kelishuvga kelishga harakat qilishingiz va ma'lumotlarni kerakli formatda uzatish yaxshiroq ekanligini tushuntirishingiz mumkin. Tasavvur qilaylik, biz aqlli mijozga duch keldik va raqamlarni raqamli formatda uzatish yaxshiroq ekanligiga rozi bo'ldik va biz pul haqida gapirayotganimiz uchun u Double bo'lsin. Bizning foydali yukimizning keyingi parametri ishga qabul qilish sanasi bo'ladi, mijoz uni kelishilgan formatda bizga yuboradi: yyyy-aa-dd, bu erda y yillar uchun, m kunlar uchun va d kunlar uchun kutiladi - 2022- 08-12. Hozirgi vaqtda oxirgi maydon mijozga topshirilgan vazifalar ro'yxati bo'ladi. Shubhasiz, Vazifa bizning ma'lumotlar bazamizdagi boshqa ob'ektdir, ammo biz bu haqda hali ko'p narsa bilmaymiz, shuning uchun biz avval Xodim bilan qilganimizdek, eng asosiy ob'ektni yaratamiz. Hozir biz taxmin qilishimiz mumkin bo'lgan yagona narsa shundaki, bitta xodimga bir nechta vazifa yuklanishi mumkin, shuning uchun biz "Birga ko'p" deb ataladigan yondashuvni qo'llaymiz, birdan ko'pga nisbati. Batafsil gapiradigan bo'lsak, xodimlar jadvalidagi bitta yozuv bir nechta yozuvlarga mos kelishi mumkin vazifalar jadvali . Men bir xodimni boshqasidan aniq ajratib olishimiz uchun noyobNumber maydoni kabi narsani qo'shishga qaror qildim. Ayni paytda bizning Xodimlar sinfimiz quyidagicha ko'rinadi:
@Entity
@Data
@Accessors(chain = true)
public class Employee {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @JsonIgnore
    Long id;

    @NotBlank
    @Column(unique = true)
    private String uniqueNumber;
    private String firstName;
    private String lastName;
    private String department;
    private Double salary;
    private LocalDate hired;

    @OneToMany
    @JoinColumn(name = "employee_id")
    List<Task> tasks = new ArrayList<>();
}
Task ob'ekti uchun quyidagi sinf yaratilgan:
@Entity
@Data
@Accessors(chain = true)
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long taskId;
}
Aytganimdek, biz Vazifada hech qanday yangilikni ko'rmaymiz, bu sinf uchun yangi ombor ham yaratildi, bu Xodimlar uchun omborning nusxasi - men uni bermayman, uni o'xshashlik bo'yicha o'zingiz yaratishingiz mumkin. Ammo Xodimlar sinfi haqida gapirish mantiqan. Aytganimdek, biz bir nechta maydonlarni qo'shdik, ammo hozir faqat oxirgisi qiziq - vazifalar. Bu ro'yxat<Task> vazifalar , u darhol bo'sh ArrayList bilan ishga tushiriladi va bir nechta izohlar bilan belgilanadi. 1. @OneToMany Aytganimdek, bu bizning xodimlarning vazifalarga nisbati bo'ladi. 2. @JoinColumn - ob'ektlar birlashtiriladigan ustun. Bunday holda, Vazifalar jadvalida xodimimizning identifikatoriga ishora qiluvchi xodim_identifikatori ustuni yaratiladi; u bizga ForeighnKey bo'lib xizmat qiladi Ismning ko'rinib turgan muqaddasligiga qaramay, ustunga o'zingizga yoqqan narsani nomlashingiz mumkin. Agar siz shunchaki identifikatorni emas, balki qandaydir haqiqiy ustunni ishlatishingiz kerak bo'lsa, vaziyat biroz murakkabroq bo'ladi, biz bu mavzuga keyinroq to'xtalamiz. 3. Siz identifikator ustidagi yangi izohga ham e'tibor bergan bo'lishingiz mumkin - @JsonIgnore. Id bizning ichki ob'ektimiz bo'lgani uchun biz uni mijozga qaytarishimiz shart emas. 4. @NotBlank - bu tekshirish uchun maxsus izoh bo'lib, unda maydon null yoki bo'sh qator bo'lmasligi kerak 5. @Column(unique = true) bu ustun noyob qiymatlarga ega bo'lishi kerakligini aytadi. Shunday qilib, bizda allaqachon ikkita ob'ekt bor, ular hatto bir-biriga bog'langan. Ularni dasturimizga integratsiya qilish vaqti keldi - keling, xizmatlar va kontrollerlar bilan shug'ullanaylik. Avvalo, getAllEmployees() usulidan stubimizni olib tashlaymiz va uni amalda ishlaydigan narsaga aylantiramiz:
public List<Employee> getAllEmployees() {
       return employeeRepository.findAll();
   }
Shunday qilib, bizning omborimiz ma'lumotlar bazasida mavjud bo'lgan hamma narsani to'playdi va bizga beradi. Shunisi e'tiborga loyiqki, u vazifalar ro'yxatini ham oladi. Ammo uni yirtib tashlash, albatta, qiziq, lekin u erda hech narsa bo'lmasa, uni yirtib tashlash nima? To'g'ri, demak, u erga biror narsani qanday qo'yish kerakligini aniqlashimiz kerak. Avvalo, kontrollerimizga yangi usul yozamiz.
@PostMapping("${application.endpoint.employee}")
    public ResponseEntity<?> createOrUpdateEmployee(@RequestBody final Employee employee) {
        try {
            employeeService.save(employee);
        } catch (ValidationException e) {
            return ResponseEntity.badRequest().body(e.getViolations());
        }
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }
Bu @PostMapping, ya'ni. u xodimlarimizning so'nggi nuqtasiga keladigan POST so'rovlarini qayta ishlaydi. Umuman olganda, men ushbu kontrollerga barcha so'rovlar bitta yakuniy nuqtaga kelganligi sababli, keling, buni biroz soddalashtiraylik deb o'yladim. application.yml-dagi yaxshi sozlamalarimizni eslaysizmi? Keling, ularni tuzatamiz. Ilova bo'limi endi shunday ko'rinishga ega bo'lsin:
application:
  endpoint:
    root: api/v1
    employee: ${application.endpoint.root}/employees
    task: ${application.endpoint.root}/tasks
Bu bizga nima beradi? Tekshirgichda biz har bir aniq usul uchun xaritalashni olib tashlashimiz mumkinligi va oxirgi nuqta @RequestMapping ("${application.endpoint.employee}") izohida sinf darajasida o'rnatilishi . Hozir go'zallik shu. Bizning nazoratchimiz:
@RestController
@RequestMapping("${application.endpoint.employee}")
@RequiredArgsConstructor
public class EmployeeController {

    private final EmployeeService employeeService;

    @GetMapping
    public ResponseEntity<List<Employee>> getEmployees() {
        return ResponseEntity.ok().body(employeeService.getAllEmployees());
    }

    @PostMapping
    public ResponseEntity<?> createOrUpdateEmployee(@RequestBody final Employee employee) {
        try {
            employeeService.save(employee);
        } catch (ValidationException e) {
            return ResponseEntity.badRequest().body(e.getViolations());
        }
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }
}
Biroq, keling, davom etaylik. CreateOrUpdateEmployee usulida aniq nima sodir bo'ladi? Shubhasiz, bizning staffeService saqlash usuliga ega, u barcha tejash ishlari uchun javobgar bo'lishi kerak. Bundan tashqari, bu usul o'z-o'zidan tushunarli nom bilan istisno qilishi mumkinligi aniq. Bular. qandaydir tekshirish amalga oshirilmoqda. Va javob to'g'ridan-to'g'ri tekshirish natijalariga bog'liq, u 201 yaratilganmi yoki 400 badRequest bo'ladimi, nima noto'g'ri bo'lganligi ro'yxati bilan. Oldinga qarab, bu bizning yangi tekshirish xizmatimiz bo'lib, u kiruvchi ma'lumotlarni kerakli maydonlar mavjudligini tekshiradi (@NotBlank esingizdami?) va bunday ma'lumotlar bizga mos keladimi yoki yo'qligini hal qiladi. Saqlash usuliga o'tishdan oldin, keling, ushbu xizmatni tasdiqlaymiz va amalga oshiramiz. Buning uchun men o'z xizmatimizni qo'yadigan alohida tekshirish paketini yaratishni taklif qilaman.
import com.example.demo.exception.ValidationException;
import com.example.demo.persistence.entity.Employee;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;

@Service
@RequiredArgsConstructor
public class ValidationService {

    private final Validator validator;

    public boolean isValidEmployee(Employee employee) throws ValidationException {
        Set<Constraintviolation<Employee>> constraintViolations = validator.validate(employee);

        if (CollectionUtils.isNotEmpty(constraintViolations)) {
            throw new ValidationException(buildViolationsList(constraintViolations));
        }
        return true;
    }

    private <T> List<Violation> buildViolationsList(Set<Constraintviolation<T>> constraintViolations) {
        return constraintViolations.stream()
                                   .map(violation -> new Violation(
                                                   violation.getPropertyPath().toString(),
                                                   violation.getMessage()
                                           )
                                   )
                                   .toList();
    }
}
Sinf juda katta bo'lib chiqdi, lekin vahima qo'ymang, buni hozir aniqlaymiz :) Bu erda biz tayyor validatsiya kutubxonasi vositalaridan foydalanamiz javax.validation Bu kutubxona bizga yangi bog'liqliklardan kelgan. build.graddle ilovasiga qo'shildi 'org.springframework.boot:spring-boot-starter -validation' Bizning eski do'stlarimiz Service va RequiredArgsConstructor allaqachon bizga ushbu sinf haqida bilishimiz kerak bo'lgan hamma narsani aytib berishgan, shuningdek, yakuniy tekshiruv maydoni ham mavjud. U sehr qiladi. Biz isValidEmployee usulini yaratdik, unga biz Employee ob'ektini o'tkazishimiz mumkin; bu usul ValidationExceptionni chiqaradi, biz uni birozdan keyin yozamiz. Ha, bu bizning ehtiyojlarimiz uchun odatiy istisno bo'ladi. validator.validate(xodim) usulidan foydalanib, biz ConstraintViolation ob'ektlari ro'yxatini olamiz - biz avval qo'shgan tekshirish izohlari bilan mos kelmaydigan barcha. Keyingi mantiq oddiy, agar bu ro'yxat bo'sh bo'lmasa (ya'ni qoidabuzarliklar mavjud bo'lsa), biz istisno qilamiz va buzilishlar ro'yxatini tuzamiz - buildViolationsList usuli Iltimos, bu umumiy usul ekanligini unutmang, ya'ni. turli ob'ektlarning buzilishi ro'yxatlari bilan ishlashi mumkin - agar biz boshqa narsani tasdiqlasak, kelajakda foydali bo'lishi mumkin. Bu usul aslida nima qiladi? Oqim API-dan foydalanib, biz buzilishlar ro'yxatini ko'rib chiqamiz. Biz xarita usulidagi har bir qoidabuzarlikni o‘zimizning qoidabuzarlik obyektiga aylantiramiz va natijada paydo bo‘lgan barcha obyektlarni ro‘yxatga yig‘amiz. Biz uni qaytarib beramiz. Bizning o'zimizning buzilish ob'ektimiz yana nima, deb so'rayapsizmi? Mana oddiy rekord
public record Violation(String property, String message) {}
Yozuvlar Java-dagi shunday maxsus yangiliklardir, agar sizga ma'lumotlarga ega ob'ekt kerak bo'lsa, hech qanday mantiqsiz yoki boshqa narsasiz. Garchi men o'zim bu nima uchun qilinganini hali tushunmagan bo'lsam ham, ba'zida bu juda qulay narsa. U oddiy sinf kabi alohida faylda yaratilishi kerak. Maxsus ValidationException ga qaytish - bu shunday ko'rinadi:
@RequiredArgsConstructor
public class ValidationException extends Exception {

    @Getter
    private final List<Violation> violations;
}
U barcha qoidabuzarliklar ro'yxatini saqlaydi, Lombok annotatsiyasi - Getter ro'yxatga biriktirilgan va boshqa Lombok annotatsiyasi orqali biz kerakli konstruktorni "amalga oshirdik" :) Bu erda shuni ta'kidlash kerakki, men isValid xatti-harakatlarini to'g'ri bajarmayapman. ... usuli, u rost yoki istisnoni qaytaradi, lekin biz odatdagi False bilan cheklanishga arziydi. Istisno yondashuvi amalga oshiriladi, chunki men bu xatoni mijozga qaytarmoqchiman, ya'ni mantiqiy usuldan rost yoki yolg'ondan boshqa narsani qaytarishim kerak. Sof ichki tekshirish usullari bo'lsa, istisno qilishning hojati yo'q, bu erda jurnalga kirish talab qilinadi. Biroq, keling, EmployeeService-ga qaytaylik, biz hali ham ob'ektlarni saqlashni boshlashimiz kerak :) Keling, bu sinf qanday ko'rinishini ko'rib chiqaylik:
@Service
@RequiredArgsConstructor
public class EmployeeService {

    private final EmployeeRepository employeeRepository;
    private final ValidationService validationService;

    public List<Employee> getAllEmployees() {
        return employeeRepository.findAll();
    }

    @Transactional
    public void save(Employee employee) throws ValidationException {
        if (validationService.isValidEmployee(employee)) {
            Employee existingEmployee = employeeRepository.findByUniqueNumber(employee.getUniqueNumber());
            if (existingEmployee == null) {
                employeeRepository.save(employee);
            } else {
                existingEmployee = updateFields(existingEmployee, employee);
                employeeRepository.save(existingEmployee);
            }
        }
    }

    private Employee updateFields(Employee existingEmployee, Employee updatedEmployee) {
        return existingEmployee.setDepartment(updatedEmployee.getDepartment())
                               .setSalary(updatedEmployee.getSalary())
            				 //TODO implement tasks merging instead of replacement
                               .setTasks(updatedEmployee.getTasks());
    }
}
Yangi yakuniy xususiy yakuniy ValidationService validationServicega e'tibor bering; Saqlash usulining o'zi @Transactional izohi bilan belgilanadi, shuning uchun RuntimeException qabul qilinsa, o'zgarishlar orqaga qaytariladi. Avvalo, biz yozgan xizmatdan foydalanib, kiruvchi ma'lumotlarni tasdiqlaymiz. Agar hamma narsa muammosiz bo'lsa, biz ma'lumotlar bazasida mavjud bo'lgan xodim mavjudligini tekshiramiz (noyob raqam yordamida). Agar yo'q bo'lsa, biz yangisini saqlaymiz, agar mavjud bo'lsa, sinfdagi maydonlarni yangilaymiz. Ha, biz qanday tekshiramiz? Ha, bu juda oddiy, biz Xodimlar omboriga yangi usul qo'shdik:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    Employee findByUniqueNumber(String uniqueNumber);
}
Nimasi diqqatga sazovor? Men hech qanday mantiq yoki SQL so'rovini yozmadim, garchi u bu erda mavjud bo'lsa. Bahor, usulning nomini o'qib, men nimani xohlayotganimni aniqlaydi - ByUniqueNumber ni toping va mos keladigan qatorni usulga o'tkazing. Maydonlarni yangilashga qaytish - bu erda men sog'lom fikrdan foydalanishga qaror qildim va faqat bo'limni, ish haqi va vazifalarni yangilashga qaror qildim, chunki nomni o'zgartirish, garchi qabul qilinadigan narsa bo'lsa ham, unchalik keng tarqalgan emas. Va ishga qabul qilish sanasini o'zgartirish odatda bahsli masala. Bu erda nima qilish yaxshi bo'lardi? Vazifalar ro'yxatini birlashtiring, lekin bizda hali vazifalar yo'qligi va ularni qanday ajratishni bilmasligimiz sababli, biz TODOni tark etamiz. Keling, Frankenshteynimizni ishga tushirishga harakat qilaylik. Agar biror narsani tasvirlashni unutmagan bo'lsam, u ishlashi kerak, lekin birinchi navbatda, biz olgan sinf daraxti: REST API va ma'lumotlarni tekshirish - 1 O'zgartirilgan sinflar ko'k rangda, yangilari yashil rangda ta'kidlangan, agar siz ishlasangiz, bunday ko'rsatkichlarni olish mumkin. git ombori bilan, lekin git bizning maqolamizning mavzusi emas, shuning uchun biz bu haqda to'xtalmaymiz. Shunday qilib, hozir bizda ikkita GET va POST usullarini qo'llab-quvvatlaydigan bitta so'nggi nuqta mavjud. Aytgancha, oxirgi nuqta haqida ba'zi qiziqarli ma'lumotlar. Nega, masalan, getAllEmployees yoki createEmployees kabi GET va POST uchun alohida so'nggi nuqtalarni ajratmadik? Hammasi juda oddiy - barcha so'rovlar uchun bitta nuqtaga ega bo'lish ancha qulayroq. Marshrutlash HTTP usuli asosida amalga oshiriladi va u intuitivdir, getAllEmployees, getEmployeeByName, olish... yangilash... yaratish... o‘chirish... Biz olgan narsalarni sinab ko‘ramiz. Men oldingi maqolada bizga Postman kerak bo'lishini yozgan edim va uni o'rnatish vaqti keldi. Dastur interfeysida biz yangi POST so'rovini yaratamiz REST API va ma'lumotlarni tekshirish - 2 va uni yuborishga harakat qilamiz. Agar hamma narsa yaxshi bo'lsa, biz ekranning o'ng tomonida Status 201-ni olamiz. Ammo, masalan, bir xil narsani yuborgan bo'lsak, lekin noyob raqamsiz (bizda tekshirish mavjud), men boshqacha javob olaman: Keling REST API va ma'lumotlarni tekshirish - 3 , bizning to'liq tanlovimiz qanday ishlashini tekshirib ko'raylik - biz bir xil so'nggi nuqta uchun GET usulini yaratamiz va uni jo'natamiz. . REST API va ma'lumotlarni tekshirish - 4 Men chin dildan umid qilamanki, hamma narsa men uchun bo'lgani kabi siz uchun ham muvaffaqiyatli bo'ldi va keyingi maqolada ko'rishguncha .
Izohlar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION