Validasi dalam Aplikasi Java

Teks ini dikhususkan untuk berbagai pendekatan validasi data: perangkap apa yang mungkin muncul dalam suatu proyek dan metode serta teknologi apa yang harus diikuti ketika memvalidasi data dalam aplikasi Java.


Validasi


Saya sering melihat proyek yang pembuatnya tidak repot-repot memilih pendekatan untuk validasi data. Tim bekerja pada proyek di bawah tekanan luar biasa dalam bentuk tenggat waktu dan persyaratan yang tidak jelas, dan sebagai hasilnya, mereka tidak punya waktu untuk validasi yang akurat dan konsisten. Oleh karena itu, kode validasinya tersebar di mana-mana: dalam cuplikan Javascript, pengontrol layar, dalam nampan logika bisnis, entitas domain, pemicu, dan batasan basis data. Kode ini penuh dengan pernyataan if-else, ia mengeluarkan banyak pengecualian, dan mencoba mencari tahu di mana potongan data tertentu ini divalidasi di sana ... Akibatnya, ketika proyek berkembang, menjadi sulit dan mahal untuk memenuhi persyaratan (seringkali cukup membingungkan), dan keseragaman pendekatan validasi data.


Jadi apakah ada cara sederhana dan elegan untuk memvalidasi data? Adakah cara yang akan melindungi kita dari dosa ketidakterbacaan, cara yang akan menyatukan semua logika validasi, dan mana yang telah diciptakan untuk kita oleh pengembang kerangka Java yang populer?


Ya, ada cara seperti itu.


Bagi kami, pengembang platform CUBA , sangat penting bagi Anda untuk menggunakan praktik terbaik. Kami percaya bahwa kode validasi harus:


  1. Dapat digunakan kembali dan ikuti prinsip KERING;
  2. Alami dan dapat dimengerti;
  3. Ditempatkan di tempat yang diharapkan pengembang untuk melihatnya;
  4. Mampu memverifikasi data dari berbagai sumber: antarmuka pengguna, panggilan SOAP, REST, dll.
  5. Bekerja di lingkungan multi-utas tanpa masalah;
  6. Dipanggil di dalam aplikasi secara otomatis, tanpa perlu menjalankan pemeriksaan secara manual;
  7. Untuk memberi pengguna pesan yang jelas dan dilokalkan dalam kotak dialog ringkas;
  8. Ikuti standar.

Mari kita lihat bagaimana ini dapat diimplementasikan menggunakan contoh aplikasi yang ditulis menggunakan kerangka Platform CUBA. Namun, karena CUBA didasarkan pada Spring dan EclipseLink, sebagian besar teknik yang digunakan di sini akan bekerja pada platform Java lain yang mendukung spesifikasi JPA dan Validasi Bean.


Validasi menggunakan batasan basis data


Mungkin cara yang paling umum dan jelas untuk memvalidasi data adalah dengan menggunakan batasan di tingkat basis data, misalnya, bendera yang diperlukan (untuk bidang yang nilainya tidak boleh kosong), panjang string, indeks unik, dll. Metode ini paling cocok untuk aplikasi perusahaan, karena jenis perangkat lunak ini biasanya sangat fokus pada pemrosesan data. Namun, bahkan di sini pengembang sering membuat kesalahan dengan menetapkan batas secara terpisah untuk setiap tingkat aplikasi. Paling sering, alasannya terletak pada distribusi tanggung jawab antara pengembang.


Pertimbangkan sebuah contoh yang sebagian besar dari kita tahu, beberapa bahkan dari pengalaman kita sendiri ... Jika spesifikasi mengatakan bahwa harus ada 10 karakter di bidang nomor paspor, sangat mungkin bahwa ini akan diperiksa oleh semua orang: arsitek DB di DDL, pengembang backend di Entitas terkait dan Layanan REST, dan akhirnya, pengembang UI langsung di sisi klien. Kemudian persyaratan ini berubah, dan bidang bertambah menjadi 15 karakter. Devops mengubah nilai kendala dalam database, tetapi tidak ada yang berubah bagi pengguna, karena di sisi klien batasannya sama ...


Pengembang mana pun tahu cara menghindari masalah ini - validasi harus terpusat! Dalam CUBA, validasi tersebut ditemukan dalam anotasi entitas JPA. Berdasarkan meta-informasi ini, CUBA Studio akan menghasilkan skrip DDL yang benar dan menerapkan validator sisi klien yang sesuai.


Contoh transisi


Jika anotasi berubah, CUBA akan memperbarui skrip DDL dan menghasilkan skrip migrasi, sehingga saat berikutnya Anda menggunakan proyek, pembatasan berbasis JPA baru akan berlaku baik di antarmuka maupun di basis data aplikasi.


Terlepas dari kesederhanaan dan implementasi di tingkat basis data, yang memberikan keandalan mutlak untuk metode ini, ruang lingkup penjelasan JPA terbatas pada kasus paling sederhana yang dapat dinyatakan dalam standar DDL dan tidak termasuk pemicu basis data atau prosedur tersimpan. Jadi, kendala berbasis JPA dapat membuat bidang entitas unik atau wajib atau menetapkan panjang kolom maksimum. Anda bahkan dapat menetapkan batasan unik pada kombinasi kolom menggunakan anotasi @UniqueConstraint . Tapi mungkin itu saja.


Meskipun demikian, dalam kasus yang membutuhkan logika validasi yang lebih kompleks, seperti memeriksa bidang untuk nilai minimum / maksimum, memvalidasi dengan ekspresi reguler, atau melakukan pemeriksaan kustom khusus untuk aplikasi Anda saja, pendekatan yang dikenal sebagai "Validasi Bean" diterapkan .


Validasi kacang


Semua orang tahu bahwa praktik yang baik untuk mengikuti standar memiliki siklus hidup yang panjang, yang efektivitasnya telah terbukti di ribuan proyek. Java Bean Validation adalah pendekatan yang didokumentasikan dalam JSR 380, 349, dan 303 dan aplikasinya: Hibernate Validator dan Apache BVal .


Meskipun pendekatan ini akrab bagi banyak pengembang, ini sering dianggap remeh. Ini adalah cara mudah untuk menanamkan validasi data bahkan dalam proyek lawas, yang memungkinkan Anda membangun validasi dengan jelas, sederhana, andal, dan sedekat mungkin dengan logika bisnis.


Menggunakan Validasi Kacang memberikan banyak keuntungan bagi proyek:


  • Logika validasi terletak di sebelah area subjek: definisi pembatasan untuk bidang dan metode nampan terjadi secara alami dan benar-benar berorientasi objek.
  • Standar Validasi Bean memberi kita puluhan anotasi validasi langsung dari kotak , misalnya: @NotNull , @Size , @Min , @Max , @Pattern , @Email , @Past , tidak cukup standar @URL , @Length , @Length paling kuat dan banyak lainnya .
  • Standar tidak membatasi kita pada anotasi siap pakai dan memungkinkan kita untuk membuat anotasi kita sendiri. Kami juga dapat membuat anotasi baru dengan menggabungkan beberapa lainnya, atau mendefinisikannya menggunakan kelas Java yang terpisah sebagai validator.
    Misalnya, dalam contoh di atas, kita dapat mengatur anotasi tingkat kelas @ValidPassportNumber untuk memverifikasi bahwa nomor paspor cocok dengan format tergantung pada nilai bidang country .
  • Kendala dapat diatur tidak hanya pada bidang atau kelas, tetapi juga pada metode dan parameternya. Pendekatan ini disebut "validasi berdasarkan kontrak" dan akan dibahas sedikit kemudian.

Ketika pengguna mengirimkan informasi yang dimasukkan, Platform CUBA (seperti beberapa kerangka kerja lainnya) memulai Validasi Bean secara otomatis, sehingga secara instan memberikan pesan kesalahan jika validasi gagal, dan kami tidak perlu menjalankan validator bin secara manual.


Mari kita kembali ke contoh dengan nomor paspor, tetapi kali ini kami akan menambahkannya dengan beberapa batasan entitas Orang:


  • Bidang name harus 2 karakter atau lebih dan harus valid. (Seperti yang Anda lihat, regexp tidak sederhana, tetapi "Charles Ogier de Batz de Castelmore Comte d'Artagnan" akan lulus ujian, tetapi "R2D2" tidak akan);
  • height (tinggi) harus dalam interval berikut: 0 < height <= 300 cm;
  • Bidang email harus berisi string yang cocok dengan format email yang benar.

Dengan semua cek ini, kelas Person akan terlihat seperti ini:


 @Listeners("passportnumber_PersonEntityListener") @NamePattern("%s|name") @Table(name = "PASSPORTNUMBER_PERSON") @Entity(name = "passportnumber$Person") @ValidPassportNumber(groups = {Default.class, UiCrossFieldChecks.class}) @FraudDetectionFlag public class Person extends StandardEntity { private static final long serialVersionUID = -9150857881422152651L; @Pattern(message = "Bad formed person name: ${validatedValue}", regexp = "^[AZ][az]*(\\s(([az]{1,3})|(([az]+\\')?[AZ][az]*)))*$") @Length(min = 2) @NotNull @Column(name = "NAME", nullable = false) protected String name; @Email(message = "Email address has invalid format: ${validatedValue}", regexp = "^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+$") @Column(name = "EMAIL", length = 120) protected String email; @DecimalMax(message = "Person height can not exceed 300 centimeters", value = "300") @DecimalMin(message = "Person height should be positive", value = "0", inclusive = false) @Column(name = "HEIGHT") protected BigDecimal height; @NotNull @Column(name = "COUNTRY", nullable = false) protected Integer country; @NotNull @Column(name = "PASSPORT_NUMBER", nullable = false, length = 15) protected String passportNumber; ... } 

Person.java


Saya percaya penggunaan anotasi seperti @NotNull , @DecimalMin , @Length , @Pattern dan sejenisnya cukup jelas dan tidak memerlukan komentar. Mari kita lihat lebih dekat implementasi dari anotasi @ValidPassportNumber .


@ValidPassportNumber kami yang baru memeriksa apakah Person#passportNumber cocok dengan pola regexp untuk setiap negara yang ditentukan oleh bidang Person#country .


Pertama, mari kita lihat dokumentasi (manual CUBA atau Hibernate baik-baik saja), sesuai dengan itu, kita perlu menandai kelas kita dengan anotasi baru ini dan meneruskan parameter groups ke sana, di mana UiCrossFieldChecks.class berarti bahwa validasi ini harus dijalankan di cross- validasi - setelah memeriksa semua bidang individual, dan Default.class menyimpan batasan dalam grup validasi default.


Deskripsi anotasi terlihat seperti ini:


 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Constraint(validatedBy = ValidPassportNumberValidator.class) public @interface ValidPassportNumber { String message() default "Passport number is not valid"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } 

ValidPassportNumber.java


Di sini @Target(ElementType.TYPE) mengatakan bahwa tujuan dari anotasi runtime ini adalah kelas, dan @Constraint(validatedBy = … ) menentukan bahwa validasi dilakukan oleh kelas ValidPassportNumberValidator yang mengimplementasikan antarmuka ConstraintValidator<...> . Kode validasi itu sendiri dalam metode isValid(...) , yang melakukan verifikasi aktual dengan cara yang cukup mudah:


 public class ValidPassportNumberValidator implements ConstraintValidator<ValidPassportNumber, Person> { public void initialize(ValidPassportNumber constraint) { } public boolean isValid(Person person, ConstraintValidatorContext context) { if (person == null) return false; if (person.country == null || person.passportNumber == null) return false; return doPassportNumberFormatCheck(person.getCountry(), person.getPassportNumber()); } private boolean doPassportNumberFormatCheck(CountryCode country, String passportNumber) { ... } } 

ValidPassportNumberValidator.java


Itu saja. Dengan Platform CUBA, kami tidak perlu menulis apa pun kecuali sebaris kode yang akan membuat validasi khusus kami berfungsi dan memberikan pesan kesalahan pengguna.
Tidak ada yang rumit, bukan?


Sekarang mari kita lihat bagaimana semuanya bekerja. Di sini CUBA memiliki nishtyaki lainnya: tidak hanya menampilkan pesan kesalahan kepada pengguna, tetapi juga menyoroti di bidang merah yang tidak lulus validasi kacang:


Representasi UI


Bukankah itu solusi yang elegan? Anda mendapatkan tampilan kesalahan validasi yang memadai di UI dengan menambahkan hanya beberapa anotasi Java ke entitas area subjek.


Untuk meringkas bagian ini, mari kita buat daftar singkat keuntungan Validasi Bean untuk entitas sekali lagi:


  1. Itu bisa dimengerti dan dibaca;
  2. Memungkinkan Anda untuk mendefinisikan batasan nilai secara langsung di kelas entitas;
  3. Itu dapat disesuaikan dan ditambah;
  4. Diintegrasikan ke dalam ORM populer, dan cek dijalankan secara otomatis sebelum perubahan disimpan ke database;
  5. Beberapa kerangka kerja juga menjalankan validasi kacang secara otomatis ketika pengguna mengirim data ke UI (dan jika tidak, mudah untuk memanggil antarmuka Validator secara manual);
  6. Validasi Bean adalah standar yang diakui dan penuh dengan dokumentasi di Internet.

Tetapi bagaimana jika Anda perlu menetapkan batasan pada metode, konstruktor atau alamat REST untuk memvalidasi data yang berasal dari sistem eksternal? Atau jika Anda perlu secara deklaratif memeriksa nilai-nilai parameter metode, tanpa menulis kode membosankan dengan banyak kondisi if-else di setiap metode yang diuji?


Jawabannya sederhana: Validasi Bean juga berlaku untuk metode!


Validasi berdasarkan kontrak


Kadang-kadang perlu untuk melampaui validasi keadaan model data. Banyak metode dapat mengambil manfaat dari validasi otomatis parameter dan nilai pengembalian. Ini mungkin diperlukan tidak hanya untuk memeriksa data yang masuk ke alamat REST atau SOAP, tetapi juga dalam kasus-kasus ketika kita ingin menuliskan prasyarat dan postkondisi panggilan metode untuk memastikan bahwa data yang dimasukkan diverifikasi sebelum tubuh metode dieksekusi, atau bahwa nilai pengembalian ada dalam kisaran yang diharapkan, atau misalnya, kita hanya perlu secara deskriptif mendeskripsikan rentang nilai dari parameter input untuk meningkatkan keterbacaan kode.


Menggunakan validasi kacang, pembatasan dapat diterapkan pada parameter input dan mengembalikan nilai metode dan konstruktor untuk memeriksa prasyarat dan postkondisi panggilan mereka di kelas Java apa pun. Jalur ini memiliki beberapa keunggulan dibandingkan metode tradisional untuk memeriksa validitas parameter dan mengembalikan nilai:


  1. Tidak perlu melakukan pemeriksaan secara manual dalam gaya imperatif (misalnya, dengan melempar IllegalArgumentException dan sejenisnya). Anda dapat mendefinisikan batasan secara deklaratif, dan membuat kode lebih mudah dipahami dan ekspresif;
  2. Kendala dapat dikonfigurasi, digunakan kembali, dan dikonfigurasi: Anda tidak perlu menulis logika validasi untuk setiap pemeriksaan. Lebih sedikit kode berarti lebih sedikit bug.
  3. Jika kelas, nilai pengembalian metode, atau parameternya ditandai dengan anotasi @Validated , maka pemeriksaan akan secara otomatis dilakukan oleh platform dengan setiap pemanggilan metode.
  4. Jika executable ditandai dengan anotasi @Documented , prekondisi dan postkondisinya akan dimasukkan dalam JavaDoc yang dihasilkan.

Menggunakan 'validasi kontrak' kami mendapatkan kode yang jelas, ringkas dan mudah dipelihara.


Sebagai contoh, mari kita lihat antarmuka pengontrol REST aplikasi CUBA. Antarmuka PersonApiService memungkinkan Anda untuk mendapatkan daftar orang dari basis data menggunakan metode getPersons() dan menambahkan orang baru menggunakan addNewPerson(...) .


Dan jangan lupa bahwa validasi kacang diwariskan! Dengan kata lain, jika kita memberi anotasi pada kelas, atau bidang, atau metode tertentu, maka semua kelas yang mewarisi kelas ini atau mengimplementasikan antarmuka ini akan dikenakan anotasi validasi yang sama.


 @Validated public interface PersonApiService { String NAME = "passportnumber_PersonApiService"; @NotNull @Valid @RequiredView("_local") List<Person> getPersons(); void addNewPerson( @NotNull @Length(min = 2, max = 255) @Pattern(message = "Bad formed person name: ${validatedValue}", regexp = "^[AZ][az]*(\\s(([az]{1,3})|(([az]+\\')?[AZ][az]*)))*$") String name, @DecimalMax(message = "Person height can not exceed 300 cm", value = "300") @DecimalMin(message = "Person height should be positive", value = "0", inclusive = false) BigDecimal height, @NotNull CountryCode country, @NotNull String passportNumber ); } 

PersonApiService.java


Apakah kode ini cukup jelas?
_ (Kecuali untuk anotasi @RequiredView(β€œ_local”) , khusus untuk Platform CUBA dan memverifikasi bahwa objek Person dikembalikan berisi semua bidang dari tabel PASSPORTNUMBER_PERSON ) ._


@Valid menetapkan bahwa setiap objek koleksi yang dikembalikan oleh metode getPersons() juga harus divalidasi terhadap pembatasan kelas Person .


Dalam aplikasi CUBA, metode ini tersedia di alamat berikut:


  • / app / rest / v2 / services / passportnumber_PersonApiService / getPersons
  • / app / rest / v2 / services / passportnumber_PersonApiService / addNewPerson

Mari kita buka aplikasi Postman dan pastikan validasi berfungsi sebagaimana mestinya:


Aplikasi tukang pos


Seperti yang mungkin telah Anda perhatikan, nomor paspor tidak divalidasi dalam contoh di atas. Ini karena bidang ini memerlukan pemeriksaan silang parameter metode addNewPerson , karena pilihan templat ekspresi reguler untuk memvalidasi passportNumber tergantung pada nilai bidang country . Validasi silang ini adalah analog lengkap dari pembatasan entitas tingkat kelas!


Validasi silang parameter didukung oleh JSR 349 ​​dan 380. Anda dapat membaca dokumentasi hibernate untuk mempelajari cara menerapkan validasi silang Anda sendiri atas metode kelas / antarmuka.


Validasi kacang luar


Tidak ada kesempurnaan di dunia, jadi validasi kacang memiliki kekurangan dan keterbatasannya:


  1. Terkadang kita hanya perlu memeriksa keadaan grafik objek yang kompleks sebelum menyimpan perubahan ke database. Misalnya, Anda perlu memastikan bahwa semua elemen pesanan pelanggan ditempatkan dalam satu paket. Ini adalah operasi yang agak sulit, dan untuk melakukannya setiap kali pengguna menambahkan item baru ke pesanan bukanlah ide yang baik. Oleh karena itu, pemeriksaan semacam itu mungkin diperlukan hanya sekali: sebelum menyimpan objek Order dan OrderItem dalam database.
  2. Beberapa cek perlu dilakukan dalam suatu transaksi. Misalnya, sistem toko elektronik harus memeriksa apakah ada cukup banyak salinan barang untuk memenuhi pesanan sebelum memasukkannya ke database. Cek semacam itu hanya dapat dilakukan di dalam transaksi, karena Sistem ini multi-threaded dan jumlah barang dalam persediaan dapat berubah setiap saat.

Platform CUBA menawarkan dua mekanisme validasi data pra-komitmen yang disebut entitas pendengar dan pendengar transaksi . Mari kita pertimbangkan secara lebih detail.


Listemer entitas


Pendengar entitas di CUBA sangat mirip dengan PreInsertEvent , PreUpdateEvent , dan PredDeleteEvent yang ditawarkan JPA kepada pengembang. Kedua mekanisme memungkinkan Anda untuk memeriksa objek entitas sebelum dan sesudah mereka disimpan dalam database.


Di CUBA, mudah untuk membuat dan menghubungkan pendengar entitas, untuk ini Anda perlu dua hal:


  1. Buat kacang yang dikelola yang mengimplementasikan salah satu antarmuka pendengar entitas. 3 antarmuka penting untuk validasi:
    BeforeDeleteEntityListener<T> ,
    BeforeInsertEntityListener<T> ,
    BeforeUpdateEntityListener<T>
  2. Tambahkan anotasi @Listeners ke objek entitas yang ingin Anda lacak.

Dan itu saja.


Dibandingkan dengan standar JPA (JSR 338, Bagian 3.5), antarmuka pendengar Platform CUBA diketik, sehingga Anda tidak perlu memberikan argumen tipe Object ke tipe entitas untuk mulai bekerja dengannya. Platform CUBA menambahkan entitas terkait atau penelepon EntityManager kemampuan untuk memuat dan memodifikasi entitas lain. Semua perubahan ini juga akan memanggil pendengar entitas yang sesuai.


Platform CUBA juga mendukung "penghapusan lunak" , sebuah pendekatan di mana alih-alih menghapus catatan dari database, mereka hanya ditandai sebagai terhapus dan menjadi tidak dapat diakses untuk penggunaan normal. Jadi, untuk penghapusan lunak, platform memanggil pendengar AfterDeleteEntityListener / AfterDeleteEntityListener , sementara implementasi standar akan memanggil PostUpdate PreUpdate / PostUpdate .


Mari kita lihat sebuah contoh. Di sini, kacang pendengar peristiwa terhubung ke kelas entitas dengan hanya satu baris kode: penjelasan @Listeners , yang mengambil nama kelas pendengar:


 @Listeners("passportnumber_PersonEntityListener") @NamePattern("%s|name") @Table(name = "PASSPORTNUMBER_PERSON") @Entity(name = "passportnumber$Person") @ValidPassportNumber(groups = {Default.class, UiCrossFieldChecks.class}) @FraudDetectionFlag public class Person extends StandardEntity { ... } 

Person.java


Implementasi pendengar itu sendiri terlihat seperti ini:


 /** * Checks that there are no other persons with the same * passport number and country code * Ignores spaces in the passport number for the check. * So numbers "12 45 768007" and "1245 768007" and "1245768007" * are the same for the validation purposes. */ @Component("passportnumber_PersonEntityListener") public class PersonEntityListener implements BeforeDeleteEntityListener<Person>, BeforeInsertEntityListener<Person>, BeforeUpdateEntityListener<Person> { @Override public void onBeforeDelete(Person person, EntityManager entityManager) { if (!checkPassportIsUnique(person.getPassportNumber(), person.getCountry(), entityManager)) { throw new ValidationException( "Passport and country code combination isn't unique"); } } @Override public void onBeforeInsert(Person person, EntityManager entityManager) { // use entity argument to validate the Person object // entityManager could be used to access database // if you need to check the data // throw ValidationException object if validation check failed if (!checkPassportIsUnique(person.getPassportNumber(), person.getCountry(), entityManager)) throw new ValidationException( "Passport and country code combination isn't unique"); } @Override public void onBeforeUpdate(Person person, EntityManager entityManager) { if (!checkPassportIsUnique(person.getPassportNumber(), person.getCountry(), entityManager)) throw new ValidationException( "Passport and country code combination isn't unique"); } ... } 

PersonEntityListener.java


Pendengar entitas adalah pilihan yang cocok jika:


  • Diperlukan untuk memeriksa data di dalam transaksi sebelum objek entitas disimpan dalam database;
  • Diperlukan untuk memeriksa data dalam database selama proses validasi, misalnya, untuk memeriksa bahwa ada cukup stok produk untuk menerima pesanan;
  • Anda perlu melihat tidak hanya pada objek entitas, seperti Order , tetapi juga pada entitas terkait, misalnya, OrderItems untuk entitas Order ;
  • Kami ingin melacak operasi memasukkan, memperbarui, atau menghapus hanya untuk kelas entitas tertentu, misalnya, hanya untuk OrderItem Order dan OrderItem , dan kami tidak perlu memeriksa perubahan di kelas entitas lain selama transaksi.

Pendengar transaksi


Pendengar transaksi CUBA juga bertindak dalam konteks transaksi, tetapi, dibandingkan dengan pendengar entitas, mereka dipanggil untuk setiap transaksi basis data.


Ini memberi mereka kekuatan super:


  • tidak ada yang bisa luput dari perhatian mereka.

Tetapi hal yang sama ditentukan oleh kekurangan mereka:


  • mereka lebih sulit untuk ditulis;
  • mereka dapat secara signifikan mengurangi kinerja;
  • Mereka harus ditulis dengan sangat hati-hati: bug di pendengar transaksi dapat mengganggu bahkan dengan pemuatan awal aplikasi.

Jadi, pendengar transaksi adalah solusi yang baik ketika Anda perlu memeriksa berbagai jenis entitas menggunakan algoritma yang sama, misalnya, memeriksa semua data untuk penipuan cyber dengan layanan tunggal yang melayani semua objek bisnis Anda.


Anda tidak akan lulus!


Lihatlah contoh yang memeriksa untuk melihat apakah entitas memiliki anotasi @FraudDetectionFlag , dan, jika ada, memulai detektor penipuan. Saya ulangi: perlu diingat bahwa metode ini dipanggil pada sistem sebelum melakukan setiap transaksi database , jadi kode harus mencoba memeriksa objek sesedikit mungkin.


 @Component("passportnumber_ApplicationTransactionListener") public class ApplicationTransactionListener implements BeforeCommitTransactionListener { private Logger log = LoggerFactory.getLogger(ApplicationTransactionListener.class); @Override public void beforeCommit(EntityManager entityManager, Collection<Entity> managedEntities) { for (Entity entity : managedEntities) { if (entity instanceof StandardEntity && !((StandardEntity) entity).isDeleted() && entity.getClass().isAnnotationPresent(FraudDetectionFlag.class) && !fraudDetectorFeedAndFastCheck(entity)) { logFraudDetectionFailure(log, entity); String msg = String.format( "Fraud detection failure in '%s' with id = '%s'", entity.getClass().getSimpleName(), entity.getId()); throw new ValidationException(msg); } } } ... } 

ApplicationTransactionListener.java


Untuk menjadi pendengar transaksi, kacang yang dikelola harus mengimplementasikan antarmuka BeforeCommitTransactionListener dan metode beforeCommit . Pendengar transaksi mengikat secara otomatis ketika aplikasi dimulai. CUBA mendaftarkan semua kelas yang menerapkan BeforeCommitTransactionListener atau AfterCompleteTransactionListener sebagai pendengar transaksi.


Kesimpulan


Validasi Bean (JPA 303, 349 dan 980) adalah pendekatan yang dapat berfungsi sebagai dasar yang dapat diandalkan untuk 95% dari kasus validasi data yang dihadapi dalam proyek perusahaan. Keuntungan utama dari pendekatan ini adalah bahwa sebagian besar logika validasi terkonsentrasi langsung di kelas model domain. Karena itu, mudah ditemukan, mudah dibaca dan mudah dirawat. Spring, CUBA, dan banyak perpustakaan lainnya mendukung standar-standar ini dan secara otomatis melakukan pemeriksaan validasi ketika menerima data pada lapisan UI, memanggil metode yang divalidasi, atau menyimpan data melalui ORM, sehingga validasi Bean sering terlihat seperti sulap dari sudut pandang pengembang.


Beberapa pengembang perangkat lunak melihat validasi pada tingkat kelas model subjek sebagai tidak alami dan terlalu rumit, mereka mengatakan bahwa validasi data pada tingkat UI adalah strategi yang cukup efektif. Namun, saya percaya bahwa banyak poin validasi dalam komponen dan pengontrol UI bukan pendekatan yang paling rasional. , , , , , listener' .


, , :


  1. JPA , , DDL.
  2. Bean Validation β€” , , , . , .
  3. bean validation, . , , REST.
  4. Entity listeners: , Bean Validation, . , . Hibernate .
  5. Transaction listeners β€” , , . , , .

PS: , Java, , , .




Tautan yang bermanfaat


Source: https://habr.com/ru/post/id427543/


All Articles