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

[Spring MVC] Model zwraca null

+1 głos
139 wizyt
pytanie zadane 6 dni temu w Java przez Przemyslaw Użytkownik (550 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ź 5 dni temu przez mbabane Nałogowiec (44,680 p.)
wybrane 2 dni temu 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.
1
komentarz 1 dzień temu przez mbabane Nałogowiec (44,680 p.)
edycja 1 dzień temu przez mbabane

Na to wychodzi, aż tak głęboko nie znam Springa żeby to sensownie wytłumaczyć, pod spodem prawdopodobnie siedzi mechanizm refleksji, dlatego, że program nie jest myślącym bytem i nie jest wstanie przewidzieć co będzie miała dana klasa. Być może pobiera listę metod i odseparowuje te, które zaczynają się na get/set, bo za pomocą refleksji można wywoływać metody (nawet prywatne):

public class SomeClass {
    private String name;
    private String lastName;

    public SomeClass() {
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getLastName() {
        return this.lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public void method() {
    }

    public String toString() {
        return "SomeClass{name='" + this.name + '\'' + ", lastName='" + this.lastName + '\'' + '}';
    }
}
public class Main
{
    public static void main(String[] args) throws Exception
    {
        SomeClass someClass = new SomeClass();
        Method[] allMethods = someClass.getClass().getMethods();

        List<Method> onlySetters = new ArrayList<>();

        for (Method method : allMethods)
        {
            if (method.getName().startsWith("set"))
                onlySetters.add(method);
        }

        onlySetters.forEach( m -> System.out.println( m.getName() ) );

        Method setter = onlySetters.get(0);
        setter.invoke(someClass, "arg");
        System.out.println(someClass);
    }
}

Ale jak to dokładnie jest to póki co jeszcze nie wiem. Mogę się tylko, na podstawie tego co wiem do tej pory, domyślać (gdybym miał zaimplementować podobny mechanizm pewnie bym jakoś kombinował z tymi refleksjami).

W Javie 10 ma zdaje się byc takie coś jak datum, które będzie domyślnie posiadało settey i gettery (w dużym uproszczeniu). Punkt 3:

https://javastart.pl/b/java/co-nowego-w-javie/

 

A ten błąd z constraintami, to co robisz, że Ci powstaje?

komentarz 1 dzień temu przez Przemyslaw Użytkownik (550 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 1 dzień temu przez mbabane Nałogowiec (44,680 p.)
edycja 1 dzień temu 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 1 dzień temu przez Wiciorny Maniak (57,620 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 1 dzień temu przez Przemyslaw Użytkownik (550 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 głos
odpowiedź 5 dni temu przez Wiciorny Maniak (57,620 p.)
edycja 5 dni temu 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 5 dni temu przez mbabane Nałogowiec (44,680 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 5 dni temu przez Wiciorny Maniak (57,620 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 5 dni temu przez mbabane Nałogowiec (44,680 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 3 dni temu przez Przemyslaw Użytkownik (550 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 3 dni temu przez mbabane Nałogowiec (44,680 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 3 dni temu przez Przemyslaw Użytkownik (550 p.)
Hmm, więc jakie tutaj powinno być oznaczenie, abym mógł skorzystać z kontenera IoC?
komentarz 3 dni temu przez mbabane Nałogowiec (44,680 p.)
Wydaje mi się, że do obiektu RegisterObject będzie to mało użyteczne.
komentarz 3 dni temu przez Przemyslaw Użytkownik (550 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 1 dzień temu przez Wiciorny Maniak (57,620 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ź 5 dni temu przez Aisekai Nałogowiec (26,630 p.)
A nie możesz po prostu @RequestBody i przeslac to za pomocą put albo post?
komentarz 3 dni temu przez Aisekai Nałogowiec (26,630 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 3 dni temu przez Wiciorny Maniak (57,620 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 3 dni temu przez Przemyslaw Użytkownik (550 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 3 dni temu przez mbabane Nałogowiec (44,680 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 2 dni temu przez Wiciorny Maniak (57,620 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
0 odpowiedzi 69 wizyt
pytanie zadane 10 maja w Java przez m_rience Nowicjusz (120 p.)
0 głosów
0 odpowiedzi 83 wizyt
pytanie zadane 8 kwietnia w Java przez Tomek Reda Użytkownik (850 p.)
0 głosów
0 odpowiedzi 55 wizyt
pytanie zadane 30 kwietnia 2017 w Java przez Jonki Dyskutant (8,220 p.)
Porady nie od parady
Pytania na temat serwisu SPOJ należy zadawać z odpowiednią kategorią dotyczącą tej strony.SPOJ

53,042 zapytań

96,245 odpowiedzi

197,034 komentarzy

25,898 pasjonatów

Przeglądających: 143
Pasjonatów: 2 Gości: 141

Motyw:

Akcja Pajacyk

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

Oto dwie polecane książki warte uwagi. Pełną listę znajdziesz tutaj.

...