JavaRush /Blog Jawa /Random-JV /REST API lan Validasi Data
Денис
tingkat
Киев

REST API lan Validasi Data

Diterbitake ing grup
Link menyang bagean pisanan: REST API lan tugas test sabanjuré Inggih, aplikasi kita digunakake, kita bisa njaluk sawetara jenis respon saka iku, nanging apa iki menehi kita? Ora nindakake pakaryan sing migunani. Ora let suwe, ayo dileksanakake sing migunani. Kaping pisanan, ayo nambah sawetara dependensi anyar menyang build.gradle kita, bakal migunani kanggo kita:
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'
Lan kita bakal miwiti karo data nyata sing kudu diproses. Ayo bali menyang paket kegigihan lan miwiti ngisi entitas kasebut. Nalika sampeyan ngelingi, kita ninggalake mung siji lapangan, banjur otomatis digawe liwat `@GeneratedValue(strategi = GenerationType.IDENTITY)` Ayo elinga spesifikasi teknis saka bab pisanan:
{
  "firstName": String,
  "lastName": String,
  "department": String,
  "salary": String
  "hired": String //"yyyy-mm-dd"
  "tasks": [
  ]
}
Kita duwe lapangan sing cukup kanggo pisanan, mula ayo miwiti ngetrapake. Telu lapangan pisanan ora nggawe pitakonan - iki garis biasa, nanging lapangan gaji wis sugestif. Kenapa garis nyata? Ing karya nyata, iki uga kedadeyan, ana pelanggan sing teka lan ujar - Aku pengin ngirim muatan iki, lan sampeyan ngolah. Sampeyan bisa, mesthi, shrug pundak lan nindakaken, sampeyan bisa nyoba kanggo teka menyang persetujuan lan nerangake yen iku luwih apik kanggo ngirim data ing format sing dibutuhake. Ayo padha mbayangno sing kita teka tengen klien pinter lan sarujuk sing iku luwih apik kanggo ngirim nomer ing format numerik, lan wiwit kita ngomong bab dhuwit, supaya iku pindho. Parameter sabanjure payload kita bakal tanggal hiring, klien bakal ngirim menyang kita ing format sing disepakati: yyyy-mm-dd, ngendi y tanggung jawab kanggo taun, m kanggo dina, lan d wis samesthine kanggo dina - 2022- 08-12. Lapangan pungkasan saiki bakal dadi dhaptar tugas sing ditugasake kanggo klien. Temenan, Tugas minangka entitas liyane ing database kita, nanging kita durung ngerti akeh babagan iki, mula kita bakal nggawe entitas paling dhasar kaya sing ditindakake karo Karyawan sadurunge. Siji-sijine sing bisa kita anggep saiki yaiku luwih saka siji tugas bisa ditugasake kanggo siji karyawan, mula kita bakal ngetrapake pendekatan One-To-Many, rasio siji-kanggo-akeh. Luwih khusus, siji rekaman ing tabel karyawan bisa cocog karo sawetara cathetan saka tabel tugas . Aku uga mutusaké kanggo nambah kuwi minangka kolom uniqueNumber, supaya kita bisa mbedakake kanthi cetha siji karyawan saka liyane. Ing wayahe kelas Karyawan kita katon kaya iki:
@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<>();
}
Kelas ing ngisor iki digawe kanggo entitas Tugas:
@Entity
@Data
@Accessors(chain = true)
public class Task {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long taskId;
}
Nalika aku ngandika, kita ora bakal weruh apa-apa anyar ing Tugas, gudang anyar uga digawe kanggo kelas iki, kang salinan repositori kanggo Employee - Aku ora bakal menehi, sampeyan bisa nggawe dhewe dening analogi. Nanging iku ndadekake pangertèn kanggo pirembagan bab kelas Employee. Kaya sing dakkandhakake, kita nambahake sawetara lapangan, nanging mung sing pungkasan saiki dadi minat - tugas. Iki minangka List<Task> tasks , langsung diinisialisasi nganggo ArrayList kosong lan ditandhani karo sawetara anotasi. 1. @OneToMany Kaya sing dakkandhakake, iki bakal dadi rasio karyawan kanggo tugas. 2. @JoinColumn - kolom sing entitas bakal digabung. Ing kasus iki, kolom employee_id bakal digawe ing Tabel Tugas nuding menyang id karyawan kita, iku bakal ngawula kita minangka ForeighnKey Senadyan ketoke sacredness jeneng, sampeyan bisa jeneng kolom apa wae sing disenengi. Kahanan bakal dadi luwih rumit yen sampeyan kudu nggunakake ora mung ID, nanging sawetara jinis kolom nyata; kita bakal ndemek topik iki mengko. 3. Sampeyan bisa uga wis ngeweruhi anotasi anyar ing ndhuwur id - @JsonIgnore. Amarga id minangka entitas internal kita, kita ora kudu bali menyang klien. 4. @NotBlank minangka anotasi khusus kanggo validasi, sing nyatakake yen kolom kasebut ora kudu null utawa string kosong 5. @Column (unik = bener) ngandika yen kolom iki kudu duwe nilai unik. Dadi, kita wis duwe rong entitas, malah nyambungake siji liyane. Wektu wis teka kanggo nggabungake menyang program kita - ayo menehi hasil karo layanan lan pengontrol. Kaping pisanan, ayo mbusak rintisan saka metode getAllEmployees () lan ngowahi dadi sing bener:
public List<Employee> getAllEmployees() {
       return employeeRepository.findAll();
   }
Mangkono, repositori kita bakal rake kabeh sing kasedhiya saka database lan menehi kanggo kita. Wigati dimangerteni manawa uga bakal njupuk dhaptar tugas. Nanging raking metu iku mesthi menarik, nanging apa raking metu yen ora ana apa-apa? Bener, tegese kita kudu ngerteni kepiye carane nyelehake barang ing kana. Kaping pisanan, ayo nulis metode anyar ing pengontrol kita.
@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();
    }
Iki @PostMapping, yaiku. ngolah panjalukan POST menyang titik pungkasan karyawan. Umumé, aku ngira yen kabeh panjaluk kanggo pengontrol iki bakal teka ing siji titik pungkasan, ayo gampang banget. Elinga setelan becik kita ing application.yml? Ayo padha ndandani. Ayo bagean aplikasi saiki katon kaya iki:
application:
  endpoint:
    root: api/v1
    employee: ${application.endpoint.root}/employees
    task: ${application.endpoint.root}/tasks
Apa iki menehi kita? Kasunyatan bilih ing controller kita bisa mbusak pemetaan kanggo saben cara tartamtu, lan titik pungkasan bakal disetel ing tingkat kelas ing @RequestMapping ("${application.endpoint.employee}") annotation . Iki kaendahan saiki ing Controller kita:
@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();
    }
}
Nanging, ayo nerusake. Apa persis sing kedadeyan ing metode createOrUpdateEmployee? Temenan, EmployeeService duwe cara nyimpen, sing kudu tanggung jawab kanggo kabeh karya nyimpen. Iku uga ketok sing cara iki bisa uncalan pangecualian karo jeneng poto-panjelasan. Sing. sawetara jinis validasi lagi ditindakake. Lan jawaban langsung gumantung ing asil validasi, apa bakal 201 Digawe utawa 400 badRequest kanthi dhaptar apa sing salah. Looking ahead, iki layanan validasi anyar kita, mriksa data mlebu kanggo ngarsane lapangan dibutuhake (elinga @NotBlank?) Lan mutusaké apa informasi kuwi cocok kanggo kita utawa ora. Sadurunge pindhah menyang cara nyimpen, ayo ngevalidasi lan ngleksanakake layanan iki. Kanggo nindakake iki, aku ngusulake nggawe paket validasi sing kapisah sing bakal dilebokake layanan.
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();
    }
}
Kelas dadi gedhe banget, nanging aja gupuh, kita bakal ngerti saiki :) Ing kene kita nggunakake alat perpustakaan validasi siap-siap javax.validation Pustaka iki teka saka dependensi anyar sing kita lakoni. ditambahaké kanggo build.graddle implementasine 'org.springframework.boot: spring-boot-starter -validation' Kita kanca lawas Service lan RequiredArgsConstructor Wis marang kita kabeh kita kudu ngerti bab kelas iki, ana uga lapangan validator final pribadi. Dheweke bakal nindakake sihir. Kita nggawe metode isValidEmployee, ing ngendi kita bisa ngliwati entitas Karyawan; metode iki mbuwang ValidationException, sing bakal ditulis mengko. Ya, iki bakal dadi pangecualian khusus kanggo kabutuhan kita. Nggunakake metode validator.validate(karyawan), kita bakal entuk dhaptar obyek ConstraintViolation - kabeh inconsistencies karo anotasi validasi sing ditambahake sadurunge. Logika luwih prasaja, yen dhaptar iki ora kosong (i.e. ana Pelanggaran), kita uncalan pangecualian lan mbangun dhaptar Pelanggaran - cara buildViolationsList Wigati dimangerteni menawa iki cara Generik, i.e. bisa nggarap dhaptar pelanggaran obyek sing beda-beda - bisa uga migunani ing mangsa ngarep yen kita validasi liyane. Apa sejatine cara iki? Nggunakake API stream, kita mbukak dhaptar pelanggaran. Kita ngowahi saben pelanggaran ing metode peta dadi obyek pelanggaran dhewe, lan ngumpulake kabeh obyek sing diasilake dadi dhaptar. Kita bali dheweke. Apa maneh obyek pelanggaran kita dhewe, sampeyan takon? Punika rekaman prasaja
public record Violation(String property, String message) {}
Rekaman minangka inovasi khusus ing Jawa, yen sampeyan butuh obyek kanthi data, tanpa logika utawa liya-liyane. Sanadyan aku dhewe durung ngerti apa sebabe iki ditindakake, kadang-kadang cukup trep. Sampeyan kudu digawe ing file sing kapisah, kaya kelas biasa. Bali menyang ValidationException adat - katon kaya iki:
@RequiredArgsConstructor
public class ValidationException extends Exception {

    @Getter
    private final List<Violation> violations;
}
Iki nyimpen dhaptar kabeh pelanggaran, anotasi Lombok - Getter ditempelake ing dhaptar, lan liwat anotasi Lombok liyane kita "dilaksanakake" konstruktor sing dibutuhake :) Wigati dicathet yen aku ora nindakake kanthi bener prilaku isValid ... cara, bali salah siji bener utawa istiméwa, nanging bakal worth matesi dhéwé kanggo Palsu biasanipun. Pendekatan pangecualian digawe amarga aku pengin mbalekake kesalahan iki menyang klien, tegese aku kudu mbalekake liyane saka bener utawa salah saka metode boolean. Ing kasus metode validasi internal murni, ora perlu mbuwang pengecualian; logging dibutuhake ing kene. Nanging, ayo bali menyang EmployeeService, kita isih kudu miwiti nyimpen obyek :) Ayo ndeleng kaya apa kelas iki saiki:
@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());
    }
}
Kabar properti final anyar pribadi ValidationService validationService pungkasan; Cara nyimpen dhewe ditandhani karo @Transactional anotasi supaya yen RuntimeException ditampa, owah-owahan bakal mbalek maneh. Kaping pisanan, kita ngesyahke data sing mlebu nggunakake layanan sing lagi wae kita tulis. Yen kabeh dadi lancar, kita mriksa manawa ana karyawan ing database (nggunakake nomer unik). Yen ora, kita nyimpen sing anyar, yen ana, kita nganyari lapangan ing kelas. Oh ya, carane kita bener mriksa? Ya, gampang banget, kita nambahake cara anyar menyang repositori Karyawan:
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
    Employee findByUniqueNumber(String uniqueNumber);
}
Apa sing penting? Aku ora nulis logika utawa pitakon SQL, sanajan kasedhiya ing kene. Spring, mung kanthi maca jeneng metode kasebut, nemtokake apa sing dikarepake - golek ByUniqueNumber lan pass senar sing cocog karo metode kasebut. Bali menyang nganyari lapangan - ing kene aku mutusake nggunakake akal sehat lan mung nganyari departemen, gaji lan tugas, amarga ngganti jeneng, sanajan ana sing bisa ditampa, isih ora umum. Lan ngganti tanggal hiring umume dadi masalah kontroversial. Apa sing apik kanggo nindakake ing kene? Gabungke dhaptar tugas, nanging amarga kita durung duwe tugas lan ora ngerti carane mbedakake, kita bakal ninggalake TODO. Ayo dadi nyoba kanggo miwiti Frankenstein kita. Yen aku ora lali kanggo njlèntrèhaké apa-apa, mesthine bisa, nanging pisanan, iki wit kelas sing kita entuk: REST API lan Validasi Data - 1 Kelas sing wis diowahi disorot biru, sing anyar disorot warna ijo, indikasi kasebut bisa dipikolehi yen sampeyan kerja. karo repositori git, nanging git dudu topik kanggo artikel kita, mula kita ora bakal mikir babagan iki. Dadi, saiki kita duwe siji titik pungkasan sing ndhukung rong cara GET lan POST. Miturut cara, sawetara informasi menarik babagan endpoint. Napa, umpamane, kita ora nyedhiyakake titik pungkasan sing kapisah kanggo GET lan POST, kayata getAllEmployees utawa createEmployees? Kabeh gampang banget - duwe titik siji kanggo kabeh panjaluk luwih trep. Routing dumadi adhedhasar metode HTTP lan intuisi, ora perlu ngelingi kabeh variasi getAllEmployees, getEmployeeByName, njaluk ... nganyari ... nggawe ... mbusak ... Ayo nyoba apa sing entuk. Aku wis nulis ing artikel sadurunge yen kita butuh Tukang Pos, lan wektune kanggo nginstal. Ing antarmuka program, kita nggawe request POST anyar REST API lan Validasi Data - 2 lan nyoba kanggo ngirim. Yen kabeh mlaku kanthi apik, kita bakal entuk Status 201 ing sisih tengen layar. Nanging contone, sawise ngirim perkara sing padha nanging tanpa nomer unik (sing duwe validasi), aku entuk jawaban sing beda: REST API lan Validasi Data - 3 Ya, ayo mriksa cara pilihan lengkap kita - nggawe metode GET kanggo titik pungkasan sing padha lan ngirim. . REST API lan Validasi Data - 4 Muga-muga kabeh bisa ditindakake kanggo sampeyan kaya sing ditindakake kanggo aku, lan nganti ketemu ing artikel sabanjure .
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION