• Najnowsze pytania
  • Bez odpowiedzi
  • Zadaj pytanie
  • Kategorie
  • Tagi
  • Zdobyte punkty
  • Ekipa ninja
  • IRC
  • FAQ
  • Regulamin
  • Książki warte uwagi

[Spring MVC] Model zwraca null

VPS Starter Arubacloud
+1 głos
549 wizyt
pytanie zadane 9 sierpnia 2018 w Java przez Przemyslaw Użytkownik (610 p.)

Cześć! Potrzebuję zrobić tak, aby za pomocą jednego formularza, uzupełnić 3 tabele(Account, Customer, Users) które są od siebie wzajemnie zależne.

W tym celu, utworzyłem klasę RegisterObject, która agreguje pod sobą wszystkie 3 encje:

package main.com.java.entity;

public class RegisterObject {

    private Account account;
    private Customer customer;
    private Users users;

    public RegisterObject(Account account, Customer customer, Users users) {
        this.account = account;
        this.customer = customer;
        this.users = users;
    }

    public Account getAccount() {
        return account;
    }

    public void setAccount(Account account) {
        this.account = account;
    }

    public Customer getCustomer() {
        return customer;
    }

    public void setCustomer(Customer customer) {
        this.customer = customer;
    }

    public Users getUsers() {
        return users;
    }

    public void setUsers(Users users) {
        this.users = users;
    }
}

 

Jednak po uzupełnieniu formularza w widoku, wartości te nie są przeniesione na model, przez co zwraca mi nullowe wartości na parametrach obiektów Account, Users, Customer. 

Metody w kontrolerze:

@GetMapping("/showFormForAddAccount")
	public String showFormForAddAccount(Model theModel){
        RegisterObject registerObject = new RegisterObject(new Account(), new Customer(), new Users());
		theModel.addAttribute("registerObject", registerObject);
		
		return "account-form";
	}
	
	@PostMapping("/saveAccount")
	public String saveAccount(@ModelAttribute("registerObject") RegisterObject registerObject) {
		registerObject.getAccount().setAccountNumber(accountNumberGenerator.generateAccountNumber());
		registerObject.getUsers().setPassword(passwordGenerator.generatePassword());
		registerObject.getCustomer().setIdOfAccount(registerObject.getAccount().getAccountID());
		registerObject.getUsers().setUsername(registerObject.getAccount().getUsername());
        accountService.addAccount(registerObject.getAccount());
		usersService.addUser(registerObject.getUsers());
		customerService.addCustomer(registerObject.getCustomer());

		return "redirect:/customer/list";
	}

 

Formularz:

<div class="table-container">

		<form:form action="saveAccount" modelAttribute="registerObject" method="POST">

			<!-- need to associate this data with customer id -->
			<form:hidden path="account.accountID" />

			<table>
				<tbody>

				<tr>
					<td><label>Balance:</label></td>
					<td><form:input path="account.balance" /></td>
				</tr>

				<tr>
					<td><label>Username:</label></td>
					<td><form:input path="account.username" /></td>
				</tr>

				<tr>
					<td><label>First name:</label></td>
					<td><form:input path="customer.firstName" /></td>
				</tr>

				<tr>
					<td><label>Last name:</label></td>
					<td><form:input path="customer.lastName" /></td>
				</tr>

				<tr>
					<td><label>Country:</label></td>
					<td><form:input path="customer.country" /></td>
				</tr>

				<tr>
					<td><label>User Enable:</label></td>
					<td><form:input path="users.enabled" /></td>
				</tr>

				<tr>
					<td><label></label></td>
					<td><input type="submit" value="Save" class="save" /></td>
				</tr>


				</tbody>
			</table>

 

Czy ktoś zna rozwiązanie tego problemu?

Dodam, że jeżeli ktoś zna lepszy sposób na uzupełnienie w jednym czasie 3 tabeli, to chętnie go poznam. :)

3 odpowiedzi

+1 głos
odpowiedź 10 sierpnia 2018 przez mbabane Szeryf (79,280 p.)
wybrane 13 sierpnia 2018 przez Przemyslaw
 
Najlepsza
Spróbowałem odtworzyć Twój przypadek u siebie i jedyne do czego się przywalił Spring to, to że w RegisterObject nie było domyślnego konstruktora. Po jego dodaniu zaczęło działać i w metodzie mapującej POST nie było nula.

 

Jeśli chodzi o inną metodę to zamiast JSP można użyć Thymeleaf.
komentarz 14 sierpnia 2018 przez Przemyslaw Użytkownik (610 p.)
Hmmm, bardzo ciekawe. Już rozumiem ten punkt widzenia.

Błąd, to efekt wykonania submit'a, w formularzu. Poprawnie tworzy się obiekt na bazie typu User, typu Account, ale wyrzuca błąd przy próbie utworzenia Customer'a.
1
komentarz 14 sierpnia 2018 przez mbabane Szeryf (79,280 p.)
edycja 14 sierpnia 2018 przez mbabane
Nie jestem pewny ale zdaje się, że przez to, że na wszystkich polach masz zastosowane CascadeType.ALL - oznacza to tyle że jeśli zapisujesz do bazy Account, który ma przypisaną referencję do Customer to Customer jest automatycznie zapisywany do bazy -> kaskadowo (i pole User też). I teraz gdy próbujesz ręcznie zapisać Customera to po pierwsze on już jest zapisany, a po drugie jak ustawiasz mu referencje do Account to hibernate próbuje zapisać w bazie ponownie obiekt Account ale, że już on istnieje w tabeli to wywala błąd, że zostały naruszone jakieś tam constrainty. (Usun testowo CascadeType.ALL z wszystkich klas i zobacz co się stanie).

Edit, ale masz tam użyte insertable false, co, o ile dobrze to rozumiem, zapobiega tworzeniu obiektu z racji danej encji.
1
komentarz 14 sierpnia 2018 przez Wiciorny Ekspert (269,120 p.)

Wiecie co : co do faktu tego

Dlaczego SPRING potrzebuje tutaj gettera/settera ? 

czytając rzetelnie dokumentacje i ksiazki : generalnie biorąc pod uwagę to że SPRING powstał na bazie POJO [ które zawsze w swojej implementacji wymagały gettera i settera ] to jest wydje mi się odpowiedź dlaczego jest mu to tutaj potrzebne

W sytuacji kiedy  tworzymy obiekty z domyślnym tzw konstruktorem, a nie np z parametrami, gdzie podajemy parametry i ustawiamy w konstruktorze  

https://stackoverflow.com/questions/7455630/is-it-necessary-to-have-getters-and-setters-in-pojos 

 

A i jeszcze co do CASCADE.ALL -> To @mbabane ma racje  zastosowanie tego w perzystencji HIBERNATE powoduje że wszystkie operacje 

(PERSIST, REMOVE, REFRESH, MERGE, DETACH) 

są kaskadowe, więc zarówno usuwanie obiektu User -> usunie obiekt wewnętrzny który np jest dowiązaniem Account i analogicznie przy tworzeniu będzie wymagał tego tak na logike moją https://stackoverflow.com/questions/13027214/jpa-manytoone-with-cascadetype-all

 

komentarz 14 sierpnia 2018 przez Przemyslaw Użytkownik (610 p.)

@mbabane, już sobie poradziłem.

Zwracało mi błąd, gdyż nie miałem uzupełnianego Foreing Key.

Początkowo:

public String saveAccount(@ModelAttribute("registerObject") RegisterObject registerObject) {
		registerObject.getAccount().setAccountNumber(accountNumberGenerator.generateAccountNumber());
		registerObject.getUsers().setPassword(passwordGenerator.generatePassword());
		registerObject.getUsers().setUsername(registerObject.getAccount().getUsername());
		registerObject.getCustomer().setIdOfAccount(registerObject.getAccount().getAccountID());		
                registerObject.getUsers().setEnabled(true);
		accountService.addAccount(registerObject.getAccount());
		usersService.addUser(registerObject.getUsers());
		customerService.addCustomer(registerObject.getCustomer());

		return "redirect:/customer/list";
	}

Obecnie:

public String saveAccount(@ModelAttribute("registerObject") RegisterObject registerObject) {
		registerObject.getAccount().setAccountNumber(accountNumberGenerator.generateAccountNumber());
		registerObject.getUsers().setPassword(passwordGenerator.generatePassword());
		registerObject.getUsers().setUsername(registerObject.getAccount().getUsername());
		registerObject.getUsers().setEnabled(true);
		accountService.addAccount(registerObject.getAccount());
		usersService.addUser(registerObject.getUsers());
		registerObject.getCustomer().setIdOfAccount(registerObject.getAccount().getAccountID());
		customerService.addCustomer(registerObject.getCustomer());
		
		return "redirect:/customer/list";
	}

 

Przeniosłem po prostu moment spisywania ID Accounta na pole Customer'a. 

Wcześniej po prostu nie miało co spisać, bo sam ID tworzy się w momencie dodania obiektu na bazę.

 

Mam kolejne pytanie, być może przesadzam z nimi, ale bardzo dużo się dzięki wam uczę.

Chciałbym dodać do mojej aplikacji testy jednostkowe. Jestem w tym temacie póki co zielony. Czytałem kiedyś że nie warto pisać testy na warstwie perzystencji, bo jedyne co sprawdzimy, to to, czy działa framework(w tym wypadku hibernate). Obecnie w warstwie serwisów mam jedynie generatory i serwisy domenowe(te pomiędzy kontrolerami a DAO). Czy ja obecnie nie mam co testować? Czy da się w jakiś sposób testować generatory?

1
komentarz 16 sierpnia 2018 przez Wiciorny Ekspert (269,120 p.)
Testujesz swoje metody i ich działanie według SOLID -> GŁÓWNIE S - > Single Responsibility principe -  czyli 1 metoda - 1 funkcjonalność

Wszystkie metody powinny byc testowane, bo to ty je pisałeś i trzeba sprawdzić przypadki ich działania czy faktycznie one są poprawnie implementowane.

Sprawdź sobie fejkowe obiekty " MOCKITO biblioteka " Mocito junit

https://www.tutorialspoint.com/mockito/mockito_junit_integration.htm
+1 głos
odpowiedź 10 sierpnia 2018 przez Wiciorny Ekspert (269,120 p.)
edycja 10 sierpnia 2018 przez Wiciorny
 RegisterObject registerObject = new RegisterObject(new Account(), new Customer(), new Users());

w każdym Mapowaniu tworzysz nowy obiekt? Pusty... ?

więc nawet wywołując wcześniej 

 @PostMapping("/saveAccount")

to i tak otrzymasz nową czystą instancje registerObject...

zależy jak wyglądają konstruktory dla posłanych obiektów,  :) 

(new Account(), new Customer(), new Users()

Co zawierają?

CO NAJWAŻNIEJSZE: JEŚLI TO JEST SPRING MVC...

to powinieneś: po pierwsze stosować DEPENDENCY INJECTION ... automatycznie wstrzykiwać i wiązać obiekty.... 

i kontrole obiektów zostawić Springowi i IoC kontenerowi ... , czemu twoja klasa w takim razie nie jest Componentem?  

Jedno pytanie też mi się nasuwa: czemu GET-> z racji pobierania zasobu u Ciebie dodaje atrybut ? Który generalnie tworzy pusty? :D ... 

A post okej, nie tworzy obiektu ale przypisuje do otrzymanego wartości... więc to co u Ciebie dzieje się w metodzie get-> jest robione dla lokalnego elementu 

1
komentarz 10 sierpnia 2018 przez mbabane Szeryf (79,280 p.)

Jedno pytanie też mi się nasuwa: czemu GET-> z racji pobierania zasobu u Ciebie dodaje atrybut ? Który generalnie tworzy pusty?

Nie wiem czy to masz na myśli ale wydaje mi się, że autor nie robi REST API tylko zwykła aplikacje internetową, co zdaje się zmienia trochę postać rzeczy. To GET będzie wywoływane, w momencie wejścia na stronę z formularzem, który ma odwoływać się własnie do tego atrybutu ustawianego w metodzie z GetMapping. Po kliknięciu submit w formularzu przechodzi POSTEM do saveAccount gdzie atrybut przyjmowany jako parametr metody odpowiada referencji z metody GET, dzięki temu jest dostęp do danych z obiektu z formularza.

komentarz 10 sierpnia 2018 przez Wiciorny Ekspert (269,120 p.)
no tak masz racje, tylko że napisane w temacie jest SPRING-MVC, a to z kolei do czegoś zoobowiązuje, że jak stosujemy FRAMEWORK i jakiś "feature" z niego to powinien być on implementowany w całości a nie tu trochę tego, trochę tego- dla mnie kod jest niezrozumiały troszeczkę :D w stosunku do tematu autora.

 

Dzięki za reszte wypowiedzi co do parametryzacji tutaj metody
GET-> PUT
komentarz 10 sierpnia 2018 przez mbabane Szeryf (79,280 p.)
O ile się nie mylę to SPRING-MVC nie zobowiązuje do pisania REST API (jeśli to masz na myśli). Można go zdaje się wykorzystać jako zamiennik JSF w Javie EE.
komentarz 12 sierpnia 2018 przez Przemyslaw Użytkownik (610 p.)

@Wiciorny, Zgadza się, popełniłem błąd, że nie tworzę tego za pomocą IoC.

Czy klasę RegisterObject, powinienem utworzyć w tym samym module co inne encje? Jakie powinno być oznaczenie tej klasy? @Component? Niestety jak próbuję wykorzystać @Entity, zgłasza mi brak primary key.

Jak powinienem powiązać obiekty Account, Customer i Users z RegistejObject? Gdy wykorzystuję @Autowired, IDE zgłąsza mi, że te obiekty nie są "beansami" Czy to oznacza, że Klasy typu @Entity, nie są beansami?

komentarz 12 sierpnia 2018 przez mbabane Szeryf (79,280 p.)

 Klasy typu @Entity, nie są beansami

Tak.

@Component zdaje się że stosuje się kiedy klasa zawiera jakąś logikę aplikacji. W przypadku RegisterObject tak raczej nie jest, więc zrobienie z niej Componentu nie będzie zbyt dobrym posunięciem.

@Entity oznacza się klasy, które mają być mapowane (przekształcone) na tabele bazy danych. Jeśli chodzi o RegisterObject to chyba to nie jest Twoim celem, więc oznaczenie jako @Entity też nie będzie potrzebne.

komentarz 12 sierpnia 2018 przez Przemyslaw Użytkownik (610 p.)
Hmm, więc jakie tutaj powinno być oznaczenie, abym mógł skorzystać z kontenera IoC?
komentarz 12 sierpnia 2018 przez mbabane Szeryf (79,280 p.)
Wydaje mi się, że do obiektu RegisterObject będzie to mało użyteczne.
komentarz 12 sierpnia 2018 przez Przemyslaw Użytkownik (610 p.)
Czyli dobrze, że nie skorzystałem wtedy z kontenera IoC? Czyli używając springa, nie trzeba zawsze polegać na DI i IoC?
komentarz 14 sierpnia 2018 przez Wiciorny Ekspert (269,120 p.)
Spring zawsze używa  IoC i DI bo taka jest jego specyfikacja... jeśli dany kontener/szablon/klase/ etc.  uzależnisz od Springa to niestety ApplicationContext bedzie tak realizowany
0 głosów
odpowiedź 10 sierpnia 2018 przez Aisekai Nałogowiec (42,190 p.)
A nie możesz po prostu @RequestBody i przeslac to za pomocą put albo post?
komentarz 12 sierpnia 2018 przez Aisekai Nałogowiec (42,190 p.)
@Wiciorny slyszalem, że jesli chce się coś utworzyć - to należy skorzystac z post. Jeżeli chce się podmienić (caly obiekt, np zalogowanego Usera) to należy skorzystac z put. Natomiast jeżeli chce się zmienic istniejący obiekt, to wtedy powinno sie uzywac Patch.
komentarz 12 sierpnia 2018 przez Wiciorny Ekspert (269,120 p.)
tzn PUT służąc do aktualizacji w REST api tak czy siak działa na bazie "podmienienia całego zasobu, czyli cała encja jest zastąpiona nową "  - taka jest metodyka działania PUT, z Patch się nie spotkałem.
komentarz 12 sierpnia 2018 przez Przemyslaw Użytkownik (610 p.)

@Wiciorny, Czytam książki, które dotyczą ogólnie programowania, takie jak Clean Code etc. Samego frameworka, uczę się z tutorialu. Jeżeli masz do polecenia jakąś lekturę, chętnie ją poznam.

1
komentarz 12 sierpnia 2018 przez mbabane Szeryf (79,280 p.)

PATCH chyba rzadko kiedy jest wykorzystywany w REST API. Np. na githubie przy PATCH jest coś takiego:

Used for updating resources with partial JSON data. For instance, an Issue resource has title and body attributes. A PATCH request may accept one or more of the attributes to update the resource. PATCH is a relatively new and uncommon HTTP verb, so resource endpoints also accept POST requests.

( https://developer.github.com/v3/#http-verbs )

Z tego też co zrozumiałem z książek PUT-em za każdym razem trzeba podawać cały obiekt, natomiast PATCHEM można podać jego część.

Przykładowo  np. API Facebooka do edycji używa POST. PATCHA chyba nie używają,  API YouTuba też nie wspiera PATCH - mają standardowe GET, PUT, POST, DELETE.

komentarz 13 sierpnia 2018 przez Wiciorny Ekspert (269,120 p.)
no tak PUTEM musimy podawać cały obiekt: dlatego że to wynika z idempotentne -> czyli z algebry zbiór jakby jest podmieniany, Paradoksalnie jak tranzakcje :D albo w całości albo wcale [ tutaj całością jest obiekt] .

W sumie z punktu widzenia - oszczędności, czy tez pamięci to bardzo nieoptymalne. Ciekaw jestem ( bo to pewnie od implementacji zależy ) czy obiekt bedzie nadpisany co jest mniej pamięcio rzerne, czy np HIBERNATE   na takich obiektach u siebie skorzysta z tego co zawsze czyli WYPIERNICZY, USUNIE ;D i doda nowy obiekt, a nie go nadpisze...

Podobne pytania

0 głosów
1 odpowiedź 496 wizyt
pytanie zadane 14 października 2021 w Java przez Tajniakkk Użytkownik (600 p.)
+1 głos
1 odpowiedź 417 wizyt
pytanie zadane 14 sierpnia 2021 w Java przez DziQu Początkujący (420 p.)
0 głosów
1 odpowiedź 304 wizyt
pytanie zadane 25 listopada 2019 w Java przez Piotrek1604 Użytkownik (560 p.)

92,451 zapytań

141,261 odpowiedzi

319,073 komentarzy

61,853 pasjonatów

Motyw:

Akcja Pajacyk

Pajacyk od wielu lat dożywia dzieci. Pomóż klikając w zielony brzuszek na stronie. Dziękujemy! ♡

Oto polecana książka warta uwagi.
Pełną listę książek znajdziesz tutaj.

Akademia Sekuraka

Akademia Sekuraka 2024 zapewnia dostęp do minimum 15 szkoleń online z bezpieczeństwa IT oraz dostęp także do materiałów z edycji Sekurak Academy z roku 2023!

Przy zakupie możecie skorzystać z kodu: pasja-akademia - użyjcie go w koszyku, a uzyskacie rabat -30% na bilety w wersji "Standard"! Więcej informacji na temat akademii 2024 znajdziecie tutaj. Dziękujemy ekipie Sekuraka za taką fajną zniżkę dla wszystkich Pasjonatów!

Akademia Sekuraka

Niedawno wystartował dodruk tej świetnej, rozchwytywanej książki (około 940 stron). Mamy dla Was kod: pasja (wpiszcie go w koszyku), dzięki któremu otrzymujemy 10% zniżki - dziękujemy zaprzyjaźnionej ekipie Sekuraka za taki bonus dla Pasjonatów! Książka to pierwszy tom z serii o ITsec, który łagodnie wprowadzi w świat bezpieczeństwa IT każdą osobę - warto, polecamy!

...