Spring model attribute gets null within the request - java

I have a page with pagination links at the top.
When I click the pages it takes me from record 1-50, 51-100 and so on.
I am having issue when i click the second action like when I click page # 2 #ModelAttribute values gets null.
this is tha page url: http://localhost:8080/tax/taxedYear.html?p=2
It takes me to spring controller class with /taxedYear.html and the method is as below:
#RequestMapping(value = "/taxedYear.html", method = RequestMethod.GET)
public ModelAndView showTaxResults(#ModelAttribute("criteria")
Criteria criteria, Model model, HttpSession session, HttpServletRequest request) {
String src = criteria.getSource();
System.out.println("src === "+src);
//....
//
}
When it is called anything from criteria is null. The same method is called from the previou page and it works fine.
This happens only when I click the page urls which also calls the same method in the controller and sends page # in addition.

From Spring reference:
An #ModelAttribute on a method argument indicates the argument should be retrieved from the model. If not present in the model, the argument should be instantiated first and then added to the model.
Model is populated by controller. You assume that the model remains the same when second call is made, but apparently your assumption is wrong. Because Spring initializes the model, I believe you thought it's persistent. And it is the reasonable way, model shouldn't be persistent among HTTP calls.

Related

Thymeleaf + Spring MVC Post/Redirect/Get

I'm doing a Post/Redirect/Get thingy, using thymeleaf and Spring MVC and it works fine. Apart from when I get to the page doing the GET and do a refresh, the ModelAttribute is reset. Here's a snippet of something similar I'm doing.
#RequestMapping("/site")
public class SiteController {
#RequestMapping(values = {"", "/"}, method = RequestMethod.GET)
public String index(#ModelAttribute("form") Form form, Model model) {
return "/site/index";
}
#RequestMapping(values = {"/postit"}, method = RequestMethod.POST)
public String indexPoster(#ModelAttribute("form") Form form, Model model, RedirectAttributes redirectAttributes) {
redirectAttributes.addFlashAttribute("form", form);
return "redirect:/site/result";
}
#RequestMapping(values = {"/result"}, method = RequestMethod.GET)
public String indexResult(#ModelAttribute("form") Form form, Model model) {
// This will print the form, but if I do a page reload, the form properties will be reset.
System.out.println(form);
return "/site/result";
}
}
My Form object is just a simple java bean with one String property called name, obviously my real form has a buttload of properties, but this is for simplicity in the question.
On my html page for /site/index I have a simple form using th:object="${form}" and th:field="*{name}" that does a post to /postit. And on my /site/result I just output the name entered in the form. (so it's sort of a Hello World at the moment :) ).
This flow works for me, but if I hit refresh after the /site/result has been loaded, the form variable in indexResult gets reset.
I've tried using
private Form form;
#ModelAttribute("form")
public Form getForm() {
if (this.form == null) {
this.form = new Form();
}
return this.form;
}
on the class level, but it feels "hacky". And if I navigate away from the page, do other stuff and then return the old data will still be there.
In this project we are refraining (as far as we can) to use the session to store data.
What you are experiencing is the exact behavior of flash attributes. They are stored in the session right before the redirect, then loaded as model attributes after the redirect and removed from the session immediately. They are only present for a single request after redirecting.
Unfortunately there doesn't seem to be a very elegent way of handling requests to a request mapping that requires certain flash/model attributes to be present. One way of doing it would be adding a flash attribute with a predefined key/value. This will be present as a model attribute after the redirect. If it's missing you can take action accordingly (e.g. redirect to the initial form).
If you want the form data to be available after multiple refreshes of the result page you have to either use the session or send them as request attributes through the URL although the latter is not really an elegant option.
A word of caution though: Your "sollution" of storing the form as a private will not work at all. Remember that spring controllers are singleton beans so now this single form model instance will be used for every concurrent request on your site effectivly making them share the submitted data.

Spring MVC - send model to view and back to controller

I have a problem with data flow in my app..
In the controller I am taking some model from DB, then I pass it to view - here some fields are shown (not all of them), and the user is able to modify them..
Then when he submits form, the controller should update the model in db.
The problem is flow, because not all of the fields are in tags, so they won't pass after submiting the form..
The only solution I found, is to create additional tags with all of the fields, which are not used in to pass them forward..
But in case I have many fields, for example - 30, I would have to create a lot of hidden fields...
What solution do you think would be the best?
Greetings,
M.
You have 2 options
Create a #ModelAttribute annotated method to get the model object from the database for each request
Put it in the session using #SessionAttributes.
#ModelAttribute annotated method
Instead of having a GET method filling the model you can also use a #ModelAttribute annotated method. This method will be invoked before each request handling method in the controller. One thing to take care of is that the id is passed in each request.
#ModelAttribute
public YourObject modelObject(#RequestParam long id) {
return yourObjectDao.findOne(id);
}
Now you can simply remove the filling of the model from the GET method and add a #ModelAttribute annotated method argument to your POST method. Which will then use the freshly obtain object.
Drawback of this approach is that when using optimistic locking it doesn't work so well anymore because each time you get the most recent version.
Using #SessionAttributes
Annotate your controller with #SessionAttributes this instructs the web handling to store the matching model objects in the session and retrieve them from there before binding.
#SessionAttributes("yourObject")
#Controller
public class YourController { ... }
Now in your POST method add an argument of the type SessionStatus and when everything is well call the isComplete method on that object. This will cleanup any session attributes put in the session by this controller.
public String handlePost(#ModelAttribute YourObject model, BindingResult result, SessionStatus status) {
if (result.hasErrors) {
return "yourView";
} else {
status.isComplete();
yourObjectDao.save(model);
return "redirect:your-new-view";
}
}

#ModelAttribute execution before displaying the JSP

I have defined #ModelAttribute in my controller, which needs to be excuted based on the requested methods output. So when I trying to accessing the my ModelAttribute from JSP, but it is producing the previous result. For example below:
class MyController{
#modelAttribute("Address")
protected getAddress(HttpRequest req){
HttpSession sess = req.getSession();
return sess.getAttribute("Address");// For example now Address is "Test Address"
}
#RequestMapping("sample.do", method=RequestMethod.GET)
public Model requestMethod(......)
{
// after execution of this method
sess.setAttribute("Address","Changed Address");
return model;// request directed to my JSP.
}
}
When I use ${Address} in my JSP, it is displaying "Test Address", I need "Changed Address" in my JSP. But my ModelAttribute is executed after the jsp is loaded. Is it possible to make this possible using #ModelAttribute, if so then how.? . Is there anyother way to acheive this apart from #ModelAttribute.?
#ModelAttribute, on a method, is used to populate the model before the request mappingmethod is called. So if multiple views need to display the address, you can add the same #ModelAttribute-annotated method in all their controllers, and the views will thus find the address in the model and will thus be able to display it.
The problem here is that your request mapping method, called after the #ModelAttribute-annotated method, changes the valud of the address, but doesn't set the new value of the address in the model. So the view still displays the old address, added to the model by the #ModelAttribute-annotated. You shouldn't have many methods changing the address, so resetting the address in the model should be done there, but not everywhere else.
That said, the address comes from the session, so it's already available for all the views anyway, without needing any #ModelAttribute-annotated method (which just stores the same address in the request as well). Just removing the #ModelAttribute-annotated method would still let you access the right address in the views, since views have access to everything stored in the session. #ModelAttribute is useful when your model must contain data that comes from, typically, the database: the method gets the data from the database, and this data is stored in the model (the request) by Spring.
Your flow and use of ModelAttribute maybe incorrect/redundant.
From the official Spring documentation
#ModelAttribute is also used at the method level to provide reference
data for the model (see the populatePetTypes() method below). ..
Note: #ModelAttribute annotated methods will be executed before the
chosen #RequestMapping annotated handler method. ...
getAddress is getting called twice:
once before requestMethod is executed (since it's annotated with RequestMapping)
and again in your jsp.
Each time it's being called it's returning an Address with "Test Address". You should remove the call in your JSP (by removing the modelAttribute on the form). In your case it's redundant since you're already putting an updated version of Address in requestMethod.

Keep object in the view after refreshing when using RedirectAttributes.addFlashAttribute

Thank for Spring 3.1, I can make Post/Redirect/Get happen using RedirectAttributes.addFlashAttribute, But it seem like has small issue
here is the method that persists form object, then redirect to the view to show that form object:
#RequestMapping(value = "/{formType}/onTheEarch", method = RequestMethod.POST)
public String submitAndRedirect(SomeWebForm someWebForm,
#PathVariable("formType") String formType,
final RedirectAttributes redirectAttributes) {
// do something according to formType
// .......
redirectAttributes.addFlashAttribute("webObject", webObject);
String view = "redirect:/formType/toTheMoon";
}
here is the method to direct user to the view that shows form object
#RequestMapping(value = "/{formType}/toTheMoon", method = RequestMethod.GET)
public String submitAndRedirect(#PathVariable("formType") String formType) {
// do something according to formType
// .......
String view = "toTheMoon";
}
So far so good, but one deficiency. When I refresh the view toTheMoon, everything is gone. So the question here is
(1)How does `RedirectAttributes.addFlashAttribute` works?
(2)How can I keep the object from "FlashAttribute" even after refreshing the page?
I know for the second question, we can avoid RedirectAttributes.addFlashAttribute and just pass in any parameters in the URL to implement the RGP pattern, however, I try to avoid this as the parameter value is sensitive and should not expose to user on the browser. So what to do?
As mentioned on spring documentation chapter 17.6
Flash attributes are saved temporarily before the redirect (typically
in the session) to be made available to the request after the redirect
and removed immediately.
If you want the result to persist for few more requests, you can store them in session with some identifier, and pass only the identifier as a request parameter to the result page
Other approach I commonly do is to check whether result model exist on the result page, if not just redirect into an error page (ie: user not meant to press refresh / access the result page directly, if they do just present error). Then do a client-side refresh prevention using javascript

Ajax Request - Call different method on Spring Controller

I've been having a problem regarding using AJAX with Spring MVC. I have a form which has a lot of fields, and each field retrieves data depending on the associated button that was clicked.
So, each one of my buttons needs to call an AJAX request. Each response will be displayed on the associated field.
I wonder if it is possible to call a different method in my Spring controller once I clicked on a different button?
In other words, I want to make multiple ajax requests to the same controller where each request will call a different method in that same controller.
See this example :
// when get account detail is clicked it will call this method
#RequestMapping(method=RequestMethod.POST)
public #ResponseBody String getAccountDetails(#RequestParam(value="accountid") String accountid){
return somefunct.getAccountDetails(accountid);
}
// when get account summary is clicked it will call this method
#RequestMapping(method=RequestMethod.POST)
public #ResponseBody String getAccountSummary(#RequestParam(value="accountid") String accountid){
return somefunct.getAccountSummary(accountid);
}
/* when submit button is clicked... Form is submitted for saving*/
#RequestMapping(method=RequestMethod.POST)
public String submitForm(){
// save here
return "myform";
};*/
Currently, I can have only one AJAX request. How can I modify this code so that I can have different functionality for different AJAX requests?
First, consider that when you retrieve data from a server without modifying the state of that server, the commonly accepted standard is to use the HTTP GET method, not POST. Thus, for your first two methods, you are misusing the HTTP Methods.
Second, you can map individual URL patterns to a specific method using the value property of the RequestMapping annotation.
Third, the most RESTful way to represent your account details resource is to use the PathVariable annotation and include your identifying accountid in the actual path:
#RequestMapping(value="/account/{accountid}/details", method = RequestMethod.GET)
public #ResponseBody String getAccountDetails(#PathVariable(value="accountid") String accountid){
return somefunct.getAccountDetails(accountid);
}
Next, you can represent your account summary using a different URL pattern where the URL is built like a tree, where the first two parts of the path are once again "Account" and the accountid:
// when get account summary is clicked it will call this method
#RequestMapping(value="/account/{accountid}/summary", method=RequestMethod.GET)
public #ResponseBody String getAccountSummary(#PathVariable(value="accountid") String accountid){
return somefunct.getAccountSummary(accountid);
}
Now, your submit method, on the other hand, has side effects. This is just a fancy way of saying that the state of your server will be different at the end of this request, and any GET requests made to that resource will be different than they were prior to the change. The appropriate HTTP method to use when modifying a resource or adding a resource to a collection is the HTTP POST Method. When replacing a collection, the HTTP Method PUT is the generally accepted method of choice.
Another differentiating factor between PUT and POST is that PUT is idempotent, meaning that the same request repeated over and over again doesn't change the state on the server. If hitting the same request multiple times creates more records, then use POST.
Lastly, this request can be mapped to a URL as well. In the example below, I've assumed you are creating a new Account record and inserting a new record in the collection of accounts in the database. Thus, I've used POST. I also modified your parameter list to use PathVariable to take the accountid from the URL path, and I added a RequestBody annotation so that you can send an object in the body of the request, which could be deserialized into a Java object:
/* when submit button is clicked... Form is submitted for saving*/
#RequestMapping(value="/account/{accountid}", method=RequestMethod.POST)
public String submitForm(#PathVariable String accountid, #RequestBody Account account){
// save here
return "myform";
}
For more information on Spring MVC, please check out the Spring documentation on Spring MVC.

Categories

Resources