JPA validáció (3. rész - "keretrendszeri bővítés")

Határon innen és túl

A végére pedig álljon itt egy érdekes probléma, feszegetve a technológia korlátait.

Adott az entitásunk, a definiált validációs szabályokkal, megfejelve jó néhány saját validációs kritériummal, és az ellenőrzést hagyjuk automatiksan triggerelődni a entitás perzisztálása során. Azt reméljük ettől, hogy ezáltal a megfelelő helyen elkapva az összes validációs kivételt (melyek keretrendszeri működésből kifolyólag tartalmazni fogják az összes kritérium sértést az adott objektumunk adataira vonatkoztatva) elegánsan kezelhetjük majd azokat a kód egy jól meghatározott, újra felhasználható, központi részén.

Igen ám, de tegyük fel, hogy szeretnénk egy olyan validációs kritériumot, mely például ellenőrzi egy felhasználói név egyediségét egy háttér adatbázisra vonatkoztatva.

Az első probléma ezzel, hogy az annotációk tipikusan light-weight megoldások, nem túl elegáns adatbázis hozzáférést programozni beléjük, nem is beszélve az ennek során felmerülő nehézségekről és az így születő irtózat hackkekről.
Megkerülhető persze a dolog azzal, hogy kivesszük az ellenőrzést a keretrendszer kezéből, és a ‘manuális' check után dobunk egy (akár custom) ConstraintViolationException-t, illeszkedve ezáltal a központi kivétel kezelő logikához. Ezzel azonban jön a második probléma, hogy a kivétel megszakítja a normál végrehajtási szekvenciát, a perzisztálás soha nem történik meg, így az objektum által definiált többi validációs kritérium sem kerül ellenőrzésre. Mondhatjuk persze hogy ez nem gond, ha nem zavar minket az, hogy a user a második kísérlete során - ezúttal helyes username megadásával - fogja megkapni a fennmaradó validációs hibákat, “két kört futva ezáltal a pályán”.

Erre a problémára javasolunk az alábbiakban egy megoldást, azzal a széljegyzettel, hogy ha bárki jobb és/vagy elegánsabb, esetleg bármilyen implementációban beépített lehetőséget tud erre, az ossza meg velünk legyen olyan jó.

Bevezetünk egy helper osztályt, melyet aztán majd kiterjesztünk azon entitásokkal, ahol erre szükségünk lesz. A @Transient annotációk fontosak, hogy az új attribútumok ne zavarjanak bele a perzisztálás folyamatába. A customConstraintViolationOccured attribútum @AssertFalse annotációja pedig egy igen elegáns módja a koncepció rendszerbe illesztésének. Ugyanis ezek után bárhol/bármikor/bárhogyan elvégezhetünk tetszőleges ellenőrzéseket az entitás életciklusa során, ahol ha hiba lépett fel, mindössze True értéket adunk ezen attribútumnak, ami máris valós kritérium sértést fog indukálni a perzisztálás során. Hogy aztán miként kezeljük az így keletkezett kivételt, már ránk van bízva, mindenestre a példában szemléltetett módon kényelmesen utaztathatunk custom ConstraintViolation-öket akár magában az entitásban is, hogy a megfelelő helyen feldolgozzuk őket.

public class CustomValidableEntity {

@Transient
@AssertFalse
private boolean customConstratinViolationOccured = false;

@Transient
private List constraintViolations = new ArrayList();

public void addConstraiontViolation(ConstraintViolation constraintViolation) {
constraintViolations.add(constraintViolation);
}

public List getConstraintViolations() {
return constraintViolations;
}

public void clearConstraintViolations() {
constraintViolations.clear();
}

public boolean isCustomConstratinViolationOccured() {
return customConstratinViolationOccured;
}

public void setCustomConstratinViolationOccured(boolean customConstratinViolationOccured) {
this.customConstratinViolationOccured = customConstratinViolationOccured;
}
}