JavaRush /Java блогы /Random-KK /REST API және деректерді тексеру
Денис
Деңгей
Киев

REST API және деректерді тексеру

Топта жарияланған
Бірінші бөлімге сілтеме: REST API және келесі сынақ тапсырмасы Жақсы, біздің қолданба жұмыс істеп тұр, біз одан қандай да бір жауап ала аламыз, бірақ бұл бізге не береді? Ол ешқандай пайдалы жұмыс жасамайды. Айтпақшы, орындалды, пайдалы нәрсені жүзеге асырайық. Ең алдымен, build.gradle-ге бірнеше жаңа тәуелділіктер қосайық, олар бізге пайдалы болады:
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'
Біз өңдеуге тиісті нақты деректерден бастаймыз. Табандылық пакетімізге оралып, нысанды толтыруды бастайық. Естеріңізде болса, біз оны тек бір өріспен қалдырдық, содан кейін автоматты `@GeneratedValue(стратегия = GenerationType.IDENTITY)` арқылы жасалады. Бірінші тараудағы техникалық сипаттамаларды еске түсірейік:
{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [
  ]
}
Бізде бірінші рет өрістер жеткілікті, сондықтан оны іске асыруды бастайық. Алғашқы үш өріс сұрақ тудырмайды - бұл қарапайым жолдар, бірақ жалақы өрісі қазірдің өзінде болжамды. Неліктен нақты сызық? Нақты жұмыста бұл да болады, сізге тұтынушы келіп: «Мен сізге бұл пайдалы жүкті жібергім келеді, сіз оны өңдейсіз. Сіз, әрине, иығыңызды көтеріп, мұны істей аласыз, сіз келісімге келуге тырысып, деректерді қажетті форматта беру жақсы екенін түсіндіруге болады. Біз ақылды клиентті кездестірдік және сандарды сандық форматта беру жақсы деп келістік және біз ақша туралы айтатын болсақ, ол Double болсын деп елестетіп көрейік. Пайдалы жүктемеміздің келесі параметрі жұмысқа қабылдау күні болады, клиент оны бізге келісілген форматта жібереді: жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжж.ж.жж.жжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжжндеінен, m күндер үшін жауап береді - 2022- 08-12. Қазіргі уақытта соңғы өріс клиентке тағайындалған тапсырмалар тізімі болады. Әлбетте, Тапсырма біздің дерекқордағы басқа нысан, бірақ біз бұл туралы әлі көп білмейміз, сондықтан біз бұрын Қызметкермен жасағандай ең негізгі нысанды жасаймыз. Қазір біз болжауға болатын жалғыз нәрсе - бір қызметкерге бірнеше тапсырма тағайындалуы мүмкін, сондықтан біз «Бірге-көпке» деп аталатын әдісті, «бірге-көпке» қатынасын қолданамыз. Егжей-тегжейлі айтатын болсақ, қызметкерлер кестесіндегі бір жазба тапсырмалар кестесіндегі бірнеше жазбаларға сәйкес келуі мүмкін . Мен бір қызметкерді екіншісінен анық ажырата алуымыз үшін бірегейNumber өрісі сияқты нәрсені қосуды шештім. Қазіргі уақытта біздің Қызметкерлер класы келесідей:
@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<>();
}
Тапсырма нысаны үшін келесі сынып жасалды:
@Entity
@Data
@Accessors(chain = true)
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long taskId;
}
Мен айтқанымдай, біз тапсырмада жаңа ештеңе көрмейміз, бұл сынып үшін жаңа репозиторий де жасалды, ол қызметкерге арналған репозиторийдің көшірмесі болып табылады - мен оны бермеймін, оны аналогия бойынша өзіңіз жасай аласыз. Бірақ Қызметкерлер класы туралы айтудың мағынасы бар. Мен айтқанымдай, біз бірнеше өрістерді қостық, бірақ қазір тек соңғысы қызығушылық тудырады - тапсырмалар. Бұл List<Task> тапсырмалары , ол бірден бос ArrayList арқылы инициализацияланады және бірнеше annotationлармен белгіленеді. 1. @OneToMany Жоғарыда айтқанымдай, бұл қызметкерлердің тапсырмаларға қатынасы болады. 2. @JoinColumn - нысандар біріктірілетін баған. Бұл жағдайда қызметкердің идентификаторын көрсететін Тапсырмалар кестесінде қызметкер_идентификаторы бағаны жасалады; ол бізге ForeighnKey ретінде қызмет етеді Атаудың қасиетті болып көрінгеніне қарамастан, бағанға өзіңізге ұнайтын кез келген атау беруге болады. Егер сізге идентификаторды ғана емес, қандай да бір нақты бағанды ​​пайдалану қажет болса, жағдай біршама күрделі болады, біз бұл тақырыпты кейінірек қозғаймыз. 3. Сондай-ақ идентификатордың үстіндегі жаңа annotationны байқаған боларсыз - @JsonIgnore. Идентификатор біздің ішкі нысанымыз болғандықтан, оны клиентке қайтарудың қажеті жоқ. 4. @NotBlank - өріс нөл немесе бос жол болмауы тиіс екенін айтатын тексеруге арналған арнайы annotation 5. @Column(unique = true) бұл бағанның бірегей мәндері болуы керек екенін айтады. Сонымен, бізде екі субъекті бар, олар тіпті бір-бірімен байланысты. Оларды біздің бағдарламамызға біріктіретін уақыт келді - қызметтер мен контроллерлермен айналысайық. Ең алдымен, getAllEmployees() әдісінен өзімізді алып тастап, оны шынымен жұмыс істейтін нәрсеге айналдырайық:
public List<Employee> getAllEmployees() {
       return employeeRepository.findAll();
   }
Осылайша, біздің репозиторий дерекқордан қол жетімді барлық нәрсені жинап, бізге береді. Бір қызығы, ол тапсырмалар тізімін де алады. Бірақ оны шығару, әрине, қызық, бірақ онда ештеңе болмаса, оны жоюдың несі бар? Бұл дұрыс, ол жерге бір нәрсені қалай қою керектігін анықтау керек дегенді білдіреді. Алдымен контроллерімізге жаңа әдісті жазайық.
@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();
    }
Бұл @PostMapping, яғни. ол қызметкерлеріміздің соңғы нүктесіне келетін POST сұрауларын өңдейді. Жалпы, мен бұл контроллерге барлық сұраулар бір соңғы нүктеге келетіндіктен, мұны сәл жеңілдетейік деп ойладым. application.yml сайтындағы тамаша параметрлерімізді есте сақтаңыз ба? Оларды түзетейік. Қолданба бөлімі енді келесідей болсын:
application:
  endpoint:
    root: api/v1
    employee: ${application.endpoint.root}/employees
    task: ${application.endpoint.root}/tasks
Бұл бізге не береді? Контроллерде біз әрбір нақты әдіс үшін салыстыруды алып тастай алатындығымыз және соңғы нүкте @RequestMapping ("${application.endpoint.employee}") annotationсында сынып деңгейінде орнатылатыны , бұл қазір біздің сұлулық. Контроллер:
@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();
    }
}
Дегенмен, әрі қарай жүрейік. CreateOrUpdateEmployee әдісінде нақты не болады? Біздің staffeService-де сақтау әдісі бар, ол барлық үнемдеу жұмыстарына жауап береді. Сондай-ақ, бұл әдіс өздігінен түсінікті атаумен ерекшелік жасай алатыны анық. Анау. валидацияның қандай да бір түрі жүргізілуде. Жауап 201 Created немесе қателер тізімі бар 400 badRequest болатын тексеру нәтижелеріне тікелей байланысты. Болашаққа қарап, бұл біздің жаңа валидация қызметіміз, ол кіріс деректерді қажетті өрістердің бар-жоғын тексереді (@NotBlank есіңізде ме?) және мұндай ақпарат бізге сәйкес келетін-келмейтінін шешеді. Сақтау әдісіне көшпес бұрын, осы қызметті растап, жүзеге асырайық. Ол үшін мен өз қызметімізді орналастыратын бөлек валидация пакетін жасауды ұсынамын.
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();
    }
}
Сынып тым үлкен болып шықты, бірақ үрейленбеңіз, қазір анықтаймыз :) Мұнда біз javax.validation дайын валидация кітапханасының құралдарын пайдаланамыз Бұл кітапхана бізге жаңа тәуелділіктерден келді. build.graddle іске асыруға қосылды 'org.springframework.boot:spring-boot-starter -validation' Біздің ескі достарымыз Service және RequiredArgsConstructor бізге осы сынып туралы білуіміз керек нәрсенің бәрін айтып берді, сонымен қатар жеке соңғы валидатор өрісі бар. Ол сиқыр жасайды. Біз isValidEmployee әдісін жасадық, оған біз Employee нысанын жібере аламыз; бұл әдіс ValidationException шығарады, біз оны сәл кейінірек жазамыз. Иә, бұл біздің қажеттіліктеріміз үшін реттелетін ерекшелік болады. validator.validate(қызметкер) әдісін қолдану арқылы біз ConstraintViolation нысандарының тізімін аламыз - біз бұрын қосқан тексеру annotationларымен сәйкессіздіктердің барлығы. Одан әрі логика қарапайым, егер бұл тізім бос болмаса (яғни бұзушылықтар болса), біз ерекше жағдайды жасаймыз және бұзушылықтар тізімін жасаймыз - buildViolationsList әдісі Бұл Жалпы әдіс екенін ескеріңіз, яғни. әртүрлі an objectілердің бұзушылықтарының тізімдерімен жұмыс істей алады - егер біз басқа нәрсені растайтын болсақ, бұл болашақта пайдалы болуы мүмкін. Бұл әдіс шын мәнінде не істейді? Ағындық API көмегімен біз бұзушылықтар тізімінен өтеміз. Біз карта әдісіндегі әрбір бұзушылықты өзіміздің бұзу an objectісіне айналдырамыз және нәтижесінде барлық нысандарды тізімге жинаймыз. Біз оны қайтарамыз. Біздің өзіміздің бұзу an objectісі тағы қандай, сіз сұрайсыз ба? Міне, қарапайым рекорд
public record Violation(String property, String message) {}
Жазбалар - бұл Java тіліндегі ерекше жаңалықтар, егер сізге ешқандай логикасыз немесе басқа ештеңесіз деректері бар нысан қажет болса. Мен мұның не үшін жасалғанын әлі түсінбесем де, кейде бұл өте ыңғайлы нәрсе. Ол кәдімгі сынып сияқты бөлек файлда жасалуы керек. Теңшелетін ValidationException параметріне оралу - бұл келесідей:
@RequiredArgsConstructor
public class ValidationException extends Exception {

    @Getter
    private final List<Violation> violations;
}
Ол барлық бұзушылықтардың тізімін сақтайды, Lombok annotationсы - Getter тізімге қосылды, ал басқа Lombok annotationсы арқылы біз қажетті конструкторды «жүзеге асырдық» :) Бұл жерде айта кету керек, мен isValid әрекетін дұрыс орындамаймын. ... әдісі, ол ақиқат немесе ерекше жағдайды қайтарады, бірақ әдеттегі False мәнімен шектелген жөн. Ерекшелік тәсілі жасалған, себебі мен бұл қатені клиентке қайтарғым келеді, яғни логикалық әдістен ақиқат немесе жалғаннан басқа нәрсені қайтаруым керек. Таза ішкі валидация әдістері жағдайында ерекшелік тастаудың қажеті жоқ, мұнда тіркеу қажет болады. Дегенмен, EmployeeService-ке оралайық, біз әлі де нысандарды сақтауды бастауымыз керек :) Енді бұл сыныптың қалай көрінетінін көрейік:
@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());
    }
}
Жаңа соңғы сипатты жеке соңғы ValidationService validationService ескеріңіз; Сақтау әдісінің өзі @Transactional annotationсымен белгіленеді, сондықтан RuntimeException алынған болса, өзгертулер кері қайтарылады. Ең алдымен, біз жаңа ғана жазған сервис арқылы кіріс деректерді тексереміз. Егер бәрі ойдағыдай болса, біз дерекқорда бар қызметкердің бар-жоғын тексереміз (бірегей нөмірді пайдалана отырып). Егер жоқ болса, біз жаңасын сақтаймыз, егер бар болса, сыныптағы өрістерді жаңартамыз. Иә, біз қалай тексереміз? Иә, бұл өте қарапайым, біз Қызметкерлер репозиторийіне жаңа әдіс қостық:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    Employee findByUniqueNumber(String uniqueNumber);
}
Не ерекше? Мен ешқандай логикалық немесе SQL сұрауын жазбадым, бірақ ол мұнда қол жетімді. Көктем, әдістің атын оқу арқылы, мен не қалайтынымды анықтайды - ByUniqueNumber тауып, әдіске сәйкес жолды өткізіңіз. Өрістерді жаңартуға оралу - бұл жерде мен ақылдылықты қолданып, бөлімді, жалақы мен міндеттерді ғана жаңартуды шештім, өйткені атауды өзгерту қолайлы нәрсе болса да, әлі де кең таралған емес. Ал жұмысқа қабылдау күнін өзгерту негізінен даулы мәселе. Мұнда не істеген жақсы болар еді? Тапсырмалар тізімдерін біріктіріңіз, бірақ бізде әлі тапсырмалар болмағандықтан және оларды қалай ажыратуға болатынын білмейміз, біз TODO-дан шығамыз. Біздің Франкенштейнді іске қосуға тырысайық. Егер мен ештеңені сипаттауды ұмытпаған болсам, ол жұмыс істеуі керек, бірақ алдымен бізде сынып ағашы бар: Өзгертілген REST API және деректерді тексеру - 1 сыныптар көк түспен, жаңалары жасыл түспен ерекшеленеді, егер сіз жұмыс жасасаңыз, мұндай белгілерді алуға болады. git репозиторийімен, бірақ git біздің мақаламыздың тақырыбы емес, сондықтан біз оған тоқталмаймыз. Сонымен, қазіргі уақытта бізде GET және POST екі әдісін қолдайтын бір соңғы нүкте бар. Айтпақшы, соңғы нүкте туралы кейбір қызықты ақпарат. Неліктен, мысалы, getAllEmployees немесе createEmployees сияқты GET және POST үшін бөлек соңғы нүктелерді бөлмедік? Барлығы өте қарапайым - барлық сұраулар үшін бір нүктенің болуы әлдеқайда ыңғайлы. Маршруттау HTTP әдісі негізінде жүзеге асырылады және ол интуитивті, getAllEmployees, getEmployeeByName, алу... жаңарту... жасау... жою барлық нұсқаларын есте сақтаудың қажеті жоқ... Алғанымызды тексеріп көрейік. Мен алдыңғы мақалада бізге Postman қажет болатынын жазған болатынмын және оны орнату уақыты келді. Бағдарлама интерфейсінде біз жаңа POST сұрауын жасаймыз REST API және деректерді тексеру - 2 және оны жіберуге тырысамыз. Егер бәрі жақсы болса, экранның оң жағында 201 күйін аламыз. Бірақ, мысалы, бірдей нәрсені жіберген, бірақ бірегей нөмірсіз (бізде валидация бар) мен басқа жауап аламын: REST API және деректерді тексеру - 3 Біздің толық таңдау қалай жұмыс істейтінін тексерейік - біз бірдей соңғы нүкте үшін GET әдісін жасаймыз және оны жібереміз. . REST API және деректерді тексеру - 4 Барлығы мен үшін болғандай сіз үшін де сәтті болды деп шын жүректен үміттенемін және келесі мақалада кездескенше .
Пікірлер
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION