2017-03-02 2 views
11

Ich arbeite an einer Java-Spring-Mvc-Anwendung und haben eine wichtige Frage über Zuordnung von Modell-Objekten zu Datenbank-Modell-Objekte. Unsere Anwendung verwendet dozer mapper für diesen Zweck.validate related data beim Mapping View-Modell zu Datenbank-Modell im Frühjahr MVC

Angenommen, ich habe eine Person Modell und BaseInformation Modell. Das BaseInformation-Modell ist für allgemeine Daten, die in allen anderen Modellen verwendet werden können, z. B. Geschlechter, Farben, Einheiten, ....

BaseInformation:

class BaseInformation{ 
    private Long id; 
    private String category; 
    private String title; 
} 

Dies kann hat eine Datenbank-Tabelle wie folgt aus:

Id | Category | Title 
------------------------- 
1 | "gender" | "male" 
2 | "gender" | "female" 
3 | "color" | "red" 
4 | "color" | "green" 
... 

Dies ist Teil meiner Person Model:

public class Person{ 
    ... 
    private BaseInformation gender; 
    ... 
} 

Und dieser Teil ist von meinem RegisterPersonViewModel

public class RegisterPersonViewModel{ 
    ... 
    private Integer gender_id; 
    ... 
} 

In Register Person Ansicht, habe ich ein <select>, die von BaseInfromation mit Geschlecht Kategorie gefüllt werden. Wenn ein Benutzer dieses Formular abgesendet hat, ein Ajax-Request wie diese zu einem Verfahren des Controller sendet:

Hier ist meine Frage:

Ein Benutzer manuell value von Geschlecht Combobox in der Ansicht ändern kann (Setzen Sie zum Beispiel einen Farbwert anstelle des Geschlechts und senden Sie ungültige Daten an Controller-Methode. Dozer mapper map viewModel zu modellieren und diese ungültigen Daten gehen durch Datenzugriffsschicht und bestehen in der Datenbank. Mit anderen Worten, Ungültige Daten können ohne Kontrolle in die Datenbank gespeichert werden. Ich möchte den besten Weg zur Steuerung relationalen Daten mit Mindestcode kennen.

+0

meinst du ** Validierung ** durch prüferische Daten? –

Antwort

6

Die BaseInformation-Klasse ist viel zu allgemein: Geschlecht hat nichts mit Farbe zu tun. Sie müssen es aufteilen. Es ist ein Fall von „One True Lookup-Tabelle“ und sogar erwähnt auf Wikipedia:

In der Datenbank Welt sind die Entwickler manchmal das RDBMS zu umgehen, zum Beispiel versucht, indem sie alles in einer großen Tabelle mit drei Spalten mit der Bezeichnung Einheit speichern ID, Schlüssel und Wert.

... entspricht Ihrer ID, Kategorie und Titel.

Während dieses Entity-Attribut-Wert-Modell der Entwickler erlaubt, von der Struktur durch eine SQL-Datenbank auferlegt auszuzubrechen, verliert es auf allen Vorteilen aus, [1] da die ganze Arbeit, die effizient durch die getan werden könnte, RDBMS wird stattdessen auf die Anwendung erzwungen.Abfragen werden viel mehr verschachtelt, [2] die Indizes und Abfrageoptimierer können nicht mehr effektiv arbeiten, und Datengültigkeitseinschränkungen werden nicht erzwungen.

Der fett gedruckte Teil beschreibt das Problem, das Sie ziemlich gut haben.


Sie sollten die verschiedenen Kategorien in ihre eigenen Klassen und Tabellen verschieben. Für Geschlecht ist ein Enum gut genug:

public enum Gender { 
    Female, Male, Unknown, Unspecified 
} 

Und es in der Person Klasse wie folgt verwenden:

public class Person { 
    ... 
    private Gender gender; 
    ... 
} 

Wenn Sie Frühlings-Datenbindung verwenden die Eingangsdaten in Java konvertieren nur Objekte der Die in der Gender enum angegebenen Werte können verwendet werden, und es sind keine weiteren Prüfungen erforderlich.

Für Farbe könnten Sie ähnlich eine Aufzählung verwenden, wenn die Farben nicht zur Laufzeit oder einer Klasse geändert werden müssen.

+0

Die BaseInformation-Tabelle enthält mehr als 100 Kategorien. Also, wie Ihr Vorschlag, sollten wir mehr als 100 Tabelle statt einer BaseInformation erstellen. Dies wäre eine sehr schwierige Aufgabe für unser riesiges Projekt. Darüber hinaus haben etwa 500 Modelle einen Verweis auf das baseInformation-Modell, das ihren Verweis auf baseInformation möglicherweise entfernt und einen Verweis auf 100 alternative Modelle hinzufügt. Das ist auch eine sehr harte Arbeit. Gibt es eine Lösung, um mein Problem zu lösen, außer die baseInformation auf mehrere Tabellen aufzuteilen? – hamed

+1

@hamed Ich vermute, dass viele dieser Kategorien nicht als Tabellen dargestellt werden müssen, sondern als neue Spalten, wie das Geschlecht. Das Teilen von BaseInformation muss auch nicht auf einmal geschehen; Sie können Teile davon nacheinander abschneiden, beginnend mit Geschlecht gerade jetzt. Gibt es andere Lösungen? Sicher, aber du hättest immer noch ein riesiges Antipattern an deinen Händen. Und da es bei Ihrer Frage nur um die Geschlechterkategorie geht, ist es nicht einmal viel Arbeit, meine Lösung zu verwenden und nur Geschlecht in seine eigene Klasse und Spalte zu verschieben. – Bewusstsein

3

Sie müssen das Ansichtsmodell validieren, und bevor Sie fortfahren, können Sie auch die Entität validieren. Es scheint, dass Sie Bean Validierung verwenden, weil Sie @Valid Annotation in Ihrer Controller-Methode verwenden. Fügen Sie einfach die Validierungsbedingungen für die Modelleigenschaften hinzu. Zum Beispiel:

Aber wenn jemand sendet 1 und bedeutet Farbe grün und nicht weiblich sind Sie verloren. Daher wäre es viel besser, ein Enum für das Geschlecht und die anderen Eigenschaften zu verwenden, wenn möglich.

Die Bean-Validierung kann auch für Ihre Datenbankobjekte (Entitäten) verwendet werden.

1

Sie können Hibernate Validator 5.x und Validation API 1.1 in Spring validation mechanism verwenden.
Die neue Version von Hibernate Validator kann Ihre persistente Bean als ein Methodenargument validieren und javax.validation.ConstraintViolationException auf Ihrem Controller werfen.

@Inject MyPersonService myPersonService; 

@RequestMapping("/person/save", method = RequestMethod.POST, produces = "application/json") 
public @ResponseBody Person create(@RequestBody RegisterPersonViewModel viewModel) throws Exception { 

    Person person = ...; // map 
    try{ 
     myPersonService.persist(person); 
    }catch (ConstraintViolationException cvex) { 
     for (ConstraintViolation cv : cvex.getConstraintViolations()) { 
     String errorMessage = cv.getMessage(); 
     } 
    } 

} 

Service:

@Service 
public class MyPsersonService{ 

    public void persist(@Valid Person person){ 
     // do persist here without checking related data 
    } 
} 

pserson:

public class Person{ 
    ... 
    @ValidCategory("gender") 
    private BaseInformation gender; 
    ... 
} 

ValidCategory:

@Documented 
@Retention(RetentionPolicy.RUNTIME) 
@Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE }) 
@Constraint(validatedBy = CategoryValidator.class) 
public @interface ValidCategory { 

    String message() default "{info.invalid}"; 
    Class<?>[] groups() default { }; 
    Class<? extends Payload>[] payload() default { }; 
    String value(); // the category name goes here 

    @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE }) 
    @Retention(RUNTIME) 
    @Documented 
    @interface List { 
     ValidCategory[] value(); 
    } 
} 

CategoryValidator:

import javax.validation.ConstraintValidator; 
import javax.validation.ConstraintValidatorContext; 

public class CategoryValidator implements ConstraintValidator<ValidCategory, BaseInformation> { 

    private String category; 
    @Override 
    public void initialize(ValidCategory validCat) { 
    this.category = validCat.value(); 
    } 

    @Override 
    public boolean isValid(BaseInformation baseInfo, ConstraintValidatorContext cvc) { 
     if(!this.category.equals(baseInfo.getCategory()){ 
     addError(cvc,"you've entered invalid category!"); 
     return false; 
     }else{ 
     return true; 
     } 
    } 

    private void addError(ConstraintValidatorContext cvc, String m){ 
    cvc.buildConstraintViolationWithTemplate(m).addConstraintViolation(); 
    } 
} 

Definieren Sie zwei Beans in applicationContext. Spring erkennt sie automatisch (another question).

Auf den ersten Blick klingt es nicht minimaler Code, aber es ist so sauber und säubert die Domain. Es ist die beste Lösung.