/ / Wiązanie danych złożonych obiektów MVC - sprężyna, sprężyna-mcc, wiązanie danych

Wiązanie danych złożonych obiektów MVC - sprężyna, sprężyna-mcc, wiązanie danych

Wciąż zmagam się ze Spring MVC z czymś, co powinno być dość prostym problemem, ale to, co wydaje się być skromnie udokumentowane w dokumentacji Spring MVC.

Mój projekt wykorzystuje Spring MVC i Thymeleaf do wyświetlania poglądów, ale silnik renderujący widok nie jest tak naprawdę związany z problemem.

Moja aplikacja koncentruje się wokół działaniaklasa, która modeluje aktywność (wewnętrzną lub zewnętrzną) organizowaną przez członka i gdzie mogą subskrybować inni członkowie. Aktywność zawiera między innymi pole Kategoria i pole Region, które są polami rozwijanymi, które są modelowane przez Hibernate jako wiele-do-jedności do tabel wyszukiwania DB, które zawierają pole identyfikatora i opis.

Kod klasy Activity entity jest następujący: nieistotne pola są pomijane, aby skrócić kod:

package nl.drsklaus.activiteitensite.model;

//imports

@Entity
@Table(name="activity")
public class Activity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;

@ManyToOne(cascade=CascadeType.ALL)
@JoinColumn(name="organizer_id")
private Member organizer;

@Size(min=5, max=50)
@Column(name = "title", nullable = false)
private String title;

@Size(min=5, max=500)
@Column(name = "description", nullable = false)
private String description;

@ManyToOne
@JoinColumn(name="category_id")
private ActivityCategory category;

@ManyToOne
@JoinColumn(name="region_id")
private ActivityRegion region;


@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinTable(name="member_activity_subscription",
joinColumns = {@JoinColumn(name="activity_id")},
inverseJoinColumns={@JoinColumn(name="member_id")})
private List<Member> participants = new ArrayList<Member>();

//getters and setters

@Override
public int hashCode() {
...
}

@Override
public boolean equals(Object obj) {
...
}
}

W widoku użytkownik powinien mieć możliwość wyboru regionu i kategorii z pola wyboru. Opcje są wprowadzane do modelu przy użyciu metody adnotacji @ModelAttribute na poziomie klasy.

Problem polega na powiązaniu tego pola z polami właściwości wyszukiwania.

Na przykład pole Kategoria jest typu ActivityCategory, który jest klasą encji zawierającą identyfikator i właściwość opisu.

W widoku pole wyboru jest wypełnione znakiemlista możliwych opcji (allCategories zawierających instancje ActivityCategory), Thymeleaf dba o wybranie bieżącej wartości, dopasowując wartość atrybutu "value" do listy:

<label>Categorie</label>
<select th:field="*{category}">
<option th:each="cat : ${allCategories}"
th:value="${cat}"
th:text="${cat.description}">
</option>
</select>

Wygenerowany HTML wygląda następująco:

<select id="category" name="category">
<option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory@20">Actief en sportief</option>
<option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory@21">Uitgaan en nachtleven</option>
<option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory@22" selected="selected">Kunst en cultuur</option>
<option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory@23">Eten en drinken</option>
<option value="nl.drsklaus.activiteitensite.model.lookup.ActivityCategory@24" selected="selected">Ontspanning en gezelligheid</option>
</select>

Jak widzimy, atrybuty wartości zawierają ciąg znakówreprezentacja samego obiektu, który oczywiście nie jest pożądany, aby pokazać wartości id, możemy użyć $ {cat.id} zamiast $ {cat}, ale wtedy wybór bieżącej wartości (ustawienie atrybutu "selected =" selected "" ) już nie działa. Dlatego zaimplementowałem konwerter, który konwertuje obiekt ActivityCategory na int (wartość id). W Thymeleaf konwerter jest wywoływany za pomocą podwójnych wyróżnień {{}}:

th:value="${{cat}}"

Konwerter jest tworzony i dodawany do Spring:

public class LookupConverter implements Converter<LookupEntity, String> {
public String convert(LookupEntity source) {
return String.valueOf(source.getId());
}
}

// W klasie MvcConfig

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new LookupConverter());
}

Teraz HTML pokazuje wartości id dla opcji, co jest dużo bardziej logiczne:

<select id="category" name="category">
<option value="1">Actief en sportief</option>
<option value="2">Uitgaan en nachtleven</option>
<option value="3" selected="selected">Kunst en cultuur</option>
<option value="4">Eten en drinken</option>
<option value="5">Ontspanning en gezelligheid</option>
</select>

Ale nadal jest błędna po przesłaniu, wartość id nie może być powiązana z obiektem Activity, który zamiast klasy liczbowej oczekuje wartości ActivityCategory, więc generowany jest błąd sprawdzania poprawności typu.

Moja metoda obsługi wygląda następująco:

@RequestMapping(value = "/{id}/submit", method = RequestMethod.POST)
public String submitForm(@ModelAttribute("activity") Activity activity, BindingResult result, ModelMap model) {

if (result.hasErrors()) {
return "activityform";
} else {

if (activity.getId() == null) {
this.service.saveActivity(activity);
} else {
this.service.mergeWithExistingAndUpdate(activity);
}

return "redirect:/activity/" + activity.getId() + "/detail";
}
}

Sprawdziłem wiele postów, ale wciąż znalazłemnie ma rozwiązania dla tego IMHO dość trywialnego problemu. W jaki sposób wartość ciągu znaków zawierająca identyfikator może zostać zaakceptowana przez metodę obsługi i poprawnie przekonwertowana? Czy nie możemy użyć wartości id w tym celu? Szukasz wskazówek ...

Odpowiedzi:

0 dla odpowiedzi № 1

Myślę, że nie możesz użyć swojego modelu podmiotu do przesłaniadane z twojego formularza do kontrolera MVC. Spróbuj utworzyć oddzielny obiekt formularza, który pasuje do danych formularza i napisz metodę usługi, aby przetłumaczyć ją na podmioty, które mogą być przechowywane w bazie danych.


0 dla odpowiedzi nr 2

Przy pomocy innego forum znalazłemnajbardziej eleganckie rozwiązanie! Zamiast Konwertera używamy Formattera, który może konwertować z specfiec Typu obiektu na Ciąg i na odwrót. Formater jest zarejestrowany na Spring i automatycznie wywoływany z Thymeleaf i konwertuje pole id do instancji ActivityCategory z tylko ustawioną wartością id. Nie szukamy rzeczywistej instancji z bazy danych, ponieważ nie potrzebujemy tutaj opisu, ponieważ Hober zjadł identyfikator wystarczający do utworzenia zapytania.

Mój formater wygląda następująco:

public class ActivityCategoryFormatter implements Formatter<ActivityCategory> {

@Override
public String print(ActivityCategory ac, Locale locale) {
// TODO Auto-generated method stub
return Integer.toString(ac.getId());
}

@Override
public ActivityCategory parse(final String text, Locale locale) throws ParseException {
// TODO Auto-generated method stub
int id = Integer.parseInt(text);
ActivityCategory ac = new ActivityCategory(id);

return ac;
}
}

i jest zarejestrowany na Spring (razem z ActivityRegionFormatter dla drugiego pola wyszukiwania) przez:

@Override
public void addFormatters(FormatterRegistry registry) {
//registry.addConverter(new LookupConverter());
registry.addFormatter(new ActivityCategoryFormatter());
registry.addFormatter(new ActivityRegionFormatter());
}

A teraz działa zgodnie z oczekiwaniami!

Pozostaje tylko kwestia, że ​​mamy trochęduplikacja kodu, ponieważ dwie klasy Formatter są prawie takie same, różnią się jedynie klasą ogólną, która jest przekazywana. Próbowałem rozwiązać ten problem, używając wspólnego interfejsu LookupEntity, który jest implementowany przez dwie klasy encji wyszukiwania (ActivityCategory i RegionCategory) i używa tego wspólnego interfejsu do zdefiniowania formatyzatora, ale niestety to nie zadziałało ...