Session Synchronization in Spring MVC - java

Good day everybody.
Please help me.
I have application and simple Controller which search data from database and when data founded in database it render them in browser also when data appears on page there is also Render in EXCEL button appears if user wants render it in Excel file. Here is controller:
#Scope("session")
#Controller
public class SearchController {
//Apache log4j logger for this controller to bring info to console of executing inside of this controller
#SuppressWarnings("unused")
private static final Logger logger = LoggerFactory.getLogger(SearchController.class);
#Autowired
private EducationWebServiceInterface educationWebService;
List<FormDate> listForm = null;
#ModelAttribute("listOfDates")
public List<Date> prepareModelDate() {
List<Date> listOfDates = educationWebService.fetchAllDatesService();
return listOfDates;
}
#ModelAttribute("listOfNames")
public List<String> prepareModelNames() {
List<String> listOfNames = educationWebService.fetchAllInstitutionNamesService();
return listOfNames;
}
#ModelAttribute("listOfTypes")
public List<String> prepareModelTypes() {
List<String> listOfTypes = educationWebService.fetchAllInstitutionTypesService();
return listOfTypes;
}
#RequestMapping(value="/search", method=RequestMethod.GET)
public String search(FormBackingObjectSearch fbos, Model model) {
model.addAttribute("fbosAttributes", fbos);
return "search";
}
#RequestMapping(value="/result", method=RequestMethod.GET)
public String resultHTML(#RequestParam String particularDate,
#RequestParam String nameOfInstitution,
#RequestParam String typeOfInstitution,
#ModelAttribute("fbosAttributes") #Validated FormBackingObjectSearch fbos,
BindingResult bindingResult,
Model model) throws Exception {
ValidatorSearch validatorSearch = new ValidatorSearch();
validatorSearch.validate(fbos, bindingResult);
if(bindingResult.hasErrors()) {
return "search";
}
listForm = new ArrayList<FormDate>();
//Case 1:
if(!fbos.getParticularDate().equals("") && !fbos.getNameOfInstitution().equals("") && fbos.getTypeOfInstitution().equals("")) {
listForm = educationWebService.fetchByDateAndNameService(DateChangerUtils.dateConvertation(fbos.getParticularDate()), fbos.getNameOfInstitution());
model.addAttribute("findAttributes", listForm);
//Case 2:
} else if(!fbos.getParticularDate().equals("") && fbos.getNameOfInstitution().equals("") && !fbos.getTypeOfInstitution().equals("")) {
listForm = educationWebService.fetchByDateAndTypeService(DateChangerUtils.dateConvertation(fbos.getParticularDate()), fbos.getTypeOfInstitution());
model.addAttribute("findAttributes", listForm);
//Case 3:
} else if(!fbos.getParticularDate().equals("") && fbos.getNameOfInstitution().equals("") && fbos.getTypeOfInstitution().equals("")) {
listForm = educationWebService.fetchByDateService(DateChangerUtils.dateConvertation(fbos.getParticularDate()));
model.addAttribute("findAttributes", listForm);
//Case 4:
} else {
throw new Exception("Exception occurs because it's not correspond to any case in controller");
}
return "search";
}
#RequestMapping(value="/result.xls", method=RequestMethod.GET)
public String resultXLS(Model model) throws NullPointerException {
if(listForm == null || listForm.isEmpty() == true) {
throw new NullPointerException("Can not create Excel file because no data to create from");
} else {
model.addAttribute("findAttributesXls", listForm);
}
return "xlspage";
} //End of the resultXLS(..) method
} //End of the class
Now let's play with tabs in browser:
My problem is that when I save rendered data in browser tab one as Excel file once and open new tab in browser tab two, find and render some different data in tab two again and after come back to tab one in my browser and again trying to save same data from table one as Excel file I get data from table two(latest I render), but I want to get my old data from tab one
I learn before Servlets and really interested of session synchronization. It my works in my case In servlets it just can be reached by: HttpSession session = request.getSession();
How can I do this in Spring MVC??? And does it will solve my problem??
Please give me advice.
Thank you people.
With all the best.

You can access the session in your controller method by adding a parameter HttpSession session
#RequestMapping(value="/result.xls", method=RequestMethod.GET)
public String resultXLS(HttpSession session, Model model) throws NullPointerException {
Object myObj = session.getAttribute("myAttr");
....
}
Another alternative is to have a parameter of type HttpServletRequest in the controller method
#RequestMapping(value="/result.xls", method=RequestMethod.GET)
public String resultXLS(HttpServletRequest request, Model model) throws NullPointerException {
Object myObj = request.getSession(false).getAttribute("myAttr");
....
}
But, Synchronizing the session will not solve your problem. Session synchronization is best suited for the situations where a lot of parallel requests keep coming and modifying the shared data which is stored in session. This is not the case you are talking about.
What you want is something like tab based states which is something you would not get any ready made solution, neither it's a good practice to go for. It will make your session very heavier and your web-application will not scale.

Related

Object exists and can be used on JSP page but I don't know WHY it works

alright so this is a bit tricky to explain well but I will do my best and hopefully someone has an idea.
So this project in built using the Spring Framework. I did not write this code but a previous dev who is no longer here.
here is a snippet from the controller.
#RequestMapping(value = "/affidavit/{id}/{userid}", method = RequestMethod.GET)
public String getProgramAffidavit(#PathVariable("id")
Program program, #PathVariable("userid")Attendee attendee, Model model) {
model.addAttribute("affidavitDetailDtoTest",affidavitDetailDto);
return "affidavit/program_affidavit";
}
I won't bother with the code that constructs the affidavitDetailDto object for now as I don't think it is relevant.
so you will see that this adds an attribute called affidavitDetailDTO to the model.
then over on my view page which is a jsp page I have a form with a model attribute of "affidavitDetailDTO" and my related information.
so now is the part I don't understand. I click the 'submit' button and my page and the form posts and calls the following controller method.
#RequestMapping(value = "/affidavit", method = RequestMethod.POST)
public String post(AffidavitDetailDto affidavitDetail, HttpServletRequest request, HttpServletResponse response, Model model) {
String out;
if(request.getParameter("programID")!= null)
out = request.getParameter("programID");
else
return "redirect: " + request.getContextPath()+ "/home";
//Session Information Section
//gets a list of program sessions and assigns it to affidavitProgramSessions dto list
List<AffidavitProgramSessionDto> affidavitProgramSessions = affidavitDetail.getAffProgramSessionList();
//Iterates through list of session in affidavitProgramSessions
for (Iterator<AffidavitProgramSessionDto> iter = affidavitProgramSessions.listIterator(); iter.hasNext(); ){
//create AffidavitProgramSession DTO object and fill it with value from list
AffidavitProgramSessionDto session = iter.next();
//get the programs session ID and assign it to a string
String programSessionDetailId = session.getProgramSessionDetailId();
logger.debug("programSessionDetailId:: " + programSessionDetailId);
//if there was no program session id then remove the item from list
if(StringUtil.isBlank(programSessionDetailId)) {
iter.remove();
}else{
// set the program session detail from value in the program session detail repo found by program session ID
session.setProgramSessionDetail(psdRepo.findOne(UUID.fromString(programSessionDetailId)));
}
}
//End oF Session Information Section
out = "affidavit/summary";
return out;
}
now on the summary jsp page there is the following loop to spit out the results.
c:forEach items="${affidavitDetailDto.affProgramSessionList}" var="session">
so here is my issues.. how the hell is it working. where is that affidavitDetailDto object coming from?
it isn't the GET method in the controller adding it because I can rename the attribute and the summary page still works.
any ideas?
EDIT
Showing how affidavitDetailDto is created in controller.
in the class this is done
#Controller
public class AffidavitController extends BaseController {
private AffidavitDetailDto affidavitDetailDto;
then in the GET Request.
#RequestMapping(value = "/affidavit/{id}/{userid}", method = RequestMethod.GET)
public String getProgramAffidavit(#PathVariable("id") Program program, #PathVariable("userid")Attendee attendee, Model model) {
affidavitDetailDto = new AffidavitDetailDto();
List<AffidavitProgramSessionDto> affProgramSessionList = affidavitDetailDto.getAffProgramSessionList();
Set<ProgramSessionDetail> programSessionDetails = ps.getProgramSessionDetail();
if(programSessionDetails != null) {
for (ProgramSessionDetail programSessionDetail : programSessionDetails) {
AffidavitProgramSessionDto affidavitProgramSessionDto = new AffidavitProgramSessionDto();
AffidavitAttendeeTypeDetailDto affidavitAttendeeTypeDetailDto = new AffidavitAttendeeTypeDetailDto();
affidavitProgramSessionDto.setAffidavitAttendeeTypeDetailDto(affidavitAttendeeTypeDetailDto);
affProgramSessionList.add(affidavitProgramSessionDto);
}
}
model.addAttribute("affidavitDetailDto",affidavitDetailDto);
Also on the model attribute level.
#ModelAttribute
private void addAttributes(Model model) {
logger.debug("call addAttributes.....");
if(affidavitDetailDto != null && affidavitDetailDto.getAffProgramSessionList().size() != 0) {
logger.debug("add affidavitDetailDto to model......");
model.addAttribute("affidavitDetailDto", affidavitDetailDto);
}
If I change the line
model.addAttribute("affidavitDetailDto", affidavitDetailDto);
to be something like
model.addAttribute("testing1234", affidavitDetailDto);
then the affidavitDetailDto objects STILL work on the summary post page.
Edit 2
This is the form tag that the for each loop runs in.
<form:form acceptCharset="utf-8" method="POST" enctype="multipart/form-data" action="${CTX_PATH}/approvedProgram" modelAttribute="affidavitDetailDto" id="jpFormInput">
however.. I can change the modelAtttrbute to equal "Madeupnothing123" and the jsp page still functions as normal.. this is why I am so confused.

Java MVC Understanding

I have a hard time understanding on how to user MVC pattern in my java based web application.
I have multiple tables (postgreSQL 9.4) and the data stored there is going to be visible to multiple users.
As far as my understanding goes I need a Model (which holds all data from the DB tables)
and any user who wants to view any of this data uses a view to do so.
Right now I have a class that is full of getter methods. Each of those asks the database to provide the stored data from a specific sql table. That data is then retured as a json String into my .jsp page and there I fill this data into a html table (.jsp just hols the table construct)
This is sort of a model, except the fact that data is just pulled from the db and given to the jsp to display.
From there on, how can I assure that all users always have the same view on the data and data is always up to date even when user A makes changes to the data that other users are also looking at??
As I have multiple db tables do I need a model for each table?
EDIT:
The way I would approach this issue would be the following.
I have a class (model) for each database table.
On Application startup I pull the data from the SQL table and store it inside the specific class
If any user wants to view data I have a getter Method in each model class to send this data to the .jsp page for display
If any user does changes to the database (setter method) the data is (after the set method is executed) pulled again so the model is up to date
Then I somehow need the data that is viewed by any user is updated on the .jsp page
Is this going in the same direction as MVC pattern?
EDIT1:
An example
One of my SQL tables looks like this
CREATE TABLE users
(
id serial NOT NULL,
user character varying(50) NOT NULL,
CONSTRAINT users_pkey PRIMARY KEY (id)
)
My Model class looks like this:
public class Users extends HttpServlet implements Servlet{
private List<List<String>> users = new ArrayList<List<String>>();
private void load_userData(){
// Code to pull data from database
/**/
List<List<String>> users = new ArrayList<List<String>>(2);
// instantiate array list
users.add(new ArrayList<String>());
users.add(new ArrayList<String>());
try {
m_ResultSet = database_connection.execute_querry_on_db(query);
while (m_ResultSet.next()) {
users.get(0).add(m_ResultSet.getString("id"));
users.get(1).add(m_ResultSet.getString("user"));
}
} catch (SQLException ex) {
Logger.getLogger(Login.class.getName()).log(Level.SEVERE, null, ex);
}
// store pulled data into model
this.users = users ;
}
private List<List<String>> get_userData(){
// Code to pull data from Model
return users;
}
private void set_userData(){
// make changes to SQL table with an update/insert query
// Update Model with new data since it has been altered
get_userData();
}
#Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
String requested_values = request.getParameter("type");
if("get_users".equals(requested_values)){
String json_users = new Gson().toJson(get_userData());
// write JSON string to response
response.getWriter().write(json_users);
}
}
}
This way the model is always up to date with the actual data from the database.
Since there is model & controller in the same class a possible controller would look like the one below??
Because using a controller like this I have to some "initialisation" in a main area of my code I guess, but that is not clear to me..
public class User_Controller extends HttpServlet implements Servlet{
private User_data model;
public OE_Controller(User_data model){
this.model = model;
}
private List<List<String>> get_userData(){
return model.users;
}
#Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
String requested_values = request.getParameter("type");
if("get_users".equals(requested_values)){
String json_users = new Gson().toJson(get_userData());
// write JSON string to response
response.getWriter().write(json_users);
}
}
}
####### Solution to MVC-"Problem" #######
What do you think about this solution? (as the controller will hand out data directly, instead of calling a method from "main")
Users.java (Model)
public class Users{
private List<List<String>> users = new ArrayList<List<String>>();
public List<List<String>> get_users(){
return users;
}
// Fill model with data from database
public void pull_UserData(){
/**/
List<List<String>> users = new ArrayList<List<String>>(2);
// instantiate array list
users.add(new ArrayList<String>());
users.add(new ArrayList<String>());
try {
m_ResultSet = database_connection.execute_querry_on_db(query);
while (m_ResultSet.next()) {
users.get(0).add(m_ResultSet.getString("id"));
users.get(1).add(m_ResultSet.getString("user"));
}
} catch (SQLException ex) {
Logger.getLogger(Login.class.getName()).log(Level.SEVERE, null, ex);
}
/**/
this.users = users;
}
}
User_Controller.java (Controller)
public class User_Controller extends HttpServlet implements Servlet{
private Users model;
public User_Controller(Users model){
this.model = model;
}
public List<List<String>> get_users(){
return model.get_users();
}
#Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
String requested_values = request.getParameter("type");
if("get_users".equals(requested_values)){
String json_users = new Gson().toJson(get_users());
// write JSON string to response
response.getWriter().write(json_users);
}
}
}
"MAIN"
// create Model
Users model = pull_UserData();
// create Controller
User_Controller controller = new User_Controller(model);
// get Model data
controller.get_oes();
// Even though I will never access Model data from here since
// my Controller will hand out the data via the do_Get() method
// "Main" is just to load the model data once on application startup
// and the model data reload is done after a setter method is executed
public static Users fill_model_Users_data(){
Users users = new Users();
users.pull_UserData();
return users;
}
What is left is to have the data always updated on all session in the frontend.
So each user of my application needs to see the same data. Here I would need a listener that refreshes the .jsp page when another user does changes to the database. How would I do this??
EDIT:
I found some solutions working with a time interval to check for new data but that still leaves me with the problem that there is a data mismatch based on how long the timer interval is & setting the interval to 1ms for example cannot be the optimal solution I guess (there has to be a "smoother" way to do that)
I'd rather have the application to update the .jsp page with new data as soon as there is new data available & even then I'm left with the Race-Condition which user is faster in clicking a button to lets say delete a user (one will succeed & one will fail)
You do not use a controller for communication between model and view as far as I see that. So it's not really MVC.
It is not necessary to have a model for each table, but it is recommended.
The idea itself seems to be solid.
To be more specific:
I have often seen and used the following structures: controller, services and models. The models save the data whereas the services are responsible for pulling it once the user sends a request. The request is handled via the controller, which informs the corresponding service to pull the data and provides the necessary models to the view.
A short example I found (Hibernate/Spring MVC)
UserController (manages all incoming requests and communicates with UserService):
#RequestMapping(value = "user/{id}", method = RequestMethod.GET)
#ResponseBody
public User get(#PathVariable Long id, Locale locale, Model model) throws NotFoundException {
User u = userService.find(id);
if (u != null)
return u;
else
throw new NotFoundException("user not found");
}
#RequestMapping(value = "user", method = RequestMethod.GET)
#ResponseBody
public List<User> list(Locale locale, Model model) {
List<User> l = userService.list();
return l;
}
UserService (takes care of providing the right data for the controller):
public User find(Long key) {
// access on database
return (User) sessionFactory.getCurrentSession().get(User.class, key);
}
public List<User> list() {
// again query the database for a list but not for a single user
// the DAO is just something hibernate specific
return userDAO.list();
}
And then at last there is a class User which is used for communication to the interface (and contains all necessary attributes) - in this example it was used to pass via REST/JSON.
To ensure that the JSP page always shows the latest data, page has to be refreshed using javascript or ajax.

Check For Modal Attribute Existence In Controller Method before adding

I have a spring controller that I want a method to handle a certain request and then redirect to another one with keeping some value attached, so I will use RedirectAttributes on the first one and #ModalAttribute on the second, but the thing is I will not always have this modal attribute existing so I want to add it only if it exists.
#RequestMapping("/main")
public String getMain(Model model,HttpSession session,#ModalAttribute List<Loans> loansList){
if(session.getAttribute("user") != null){
if(session.getAttribute("current_start")!=null){
model.addAttribute("loans",loanDao.findAll((Integer) session.getAttribute("current_start")));
} else {
model.addAttribute("loans",loanDao.findAll(0));
session.setAttribute("current_start",0);
}
model.addAttribute("loan",new Loan());
model.addAttribute("countries",countryDao.findAll());
model.addAttribute("types",typeDao.findAll());
session.setAttribute("total_loans_number", loanDao.findCount());
return "main";
} else {
return "redirect:index";
}
}
and the redirecting one one is
#RequestMapping(value = "/search")
public String searchLoans(Model model,RedirectAttributes redirectAttributes,
#RequestParam String keyword){
redirectAttributes.addAttribute("loansList",loanDao.findAll(keyword));
return "redirect:/main";
}
but here the #ModalAttribute fails because it sometimes does not exist,sometimes I request main with out the loansList, how to make a condition to add it only if it exists ? or how to do this correctly ?
you can let spring populate your model attributes using #ModalAttribute annotation on methods:
#ModalAttribute("results")
public List<Loans> populateLoans() {
return new ArrayList<Loans>();
}
#RequestMapping("/main")
public String getMain(Model model,HttpSession session,#ModalAttribute("results") List<Loans> loansList){
if (CollectionUtils.isNotEmpty(loanList)) {
// do something if the loan list is not empty.
}
}

Spring Partial Update Object Data Binding

We are trying to implement a special partial update function in Spring 3.2. We are using Spring for the backend and have a simple Javascript frontend. I've not been able to find a straight-forward solution to our requirements, which is The update() function should take in any number of field:values and update the persistence model accordingly.
We have in-line editing for all of our fields, so that when the user edits a field and confirms, an id and the modified field get passed to the controller as json. The controller should be able to take in any number of fields from the client (1 to n) and update only those fields.
e.g., when a user with id==1 edits his displayName, the data posted to the server looks like this:
{"id":"1", "displayName":"jim"}
Currently, we have an incomplete solution in the UserController as outlined below:
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#RequestBody User updateUser) {
dbUser = userRepository.findOne(updateUser.getId());
customObjectMerger(updateUser, dbUser);
userRepository.saveAndFlush(updateUuser);
...
}
The code here works, but has some issues: The #RequestBody creates a new updateUser, fills in the id and the displayName. CustomObjectMerger merges this updateUser with the corresponding dbUser from the database, updating the only fields included in updateUser.
The problem is that Spring populates some fields in updateUser with default values and other auto-generated field values, which, upon merging, overwrites valid data that we have in dbUser. Explicitly declaring that it should ignore these fields is not an option, as we want our update to be able to set these fields as well.
I am looking into some way to have Spring automatically merge ONLY the information explicitly sent into the update() function into the dbUser (without resetting default/auto field values). Is there any simple way to do this?
Update: I've already considered the following option which does almost what I'm asking for, but not quite. The problem is that it takes update data in as #RequestParam and (AFAIK) doesn't do JSON strings:
//load the existing user into the model for injecting into the update function
#ModelAttribute("user")
public User addUser(#RequestParam(required=false) Long id){
if (id != null) return userRepository.findOne(id);
return null;
}
....
//method declaration for using #MethodAttribute to pre-populate the template object
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#ModelAttribute("user") User updateUser){
....
}
I've considered re-writing my customObjectMerger() to work more appropriately with JSON, counting and having it take into consideration only the fields coming in from HttpServletRequest. but even having to use a customObjectMerger() in the first place feels hacky when spring provides almost exactly what I am looking, minus the lacking JSON functionality. If anyone knows of how to get Spring to do this, I'd greatly appreciate it!
I've just run into this same problem. My current solution looks like this. I haven't done much testing yet, but upon initial inspection it looks to be working fairly well.
#Autowired ObjectMapper objectMapper;
#Autowired UserRepository userRepository;
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#PathVariable Long id, HttpServletRequest request) throws IOException
{
User user = userRepository.findOne(id);
User updatedUser = objectMapper.readerForUpdating(user).readValue(request.getReader());
userRepository.saveAndFlush(updatedUser);
return new ResponseEntity<>(updatedUser, HttpStatus.ACCEPTED);
}
The ObjectMapper is a bean of type org.codehaus.jackson.map.ObjectMapper.
Hope this helps someone,
Edit:
Have run into issues with child objects. If a child object receives a property to partially update it will create a fresh object, update that property, and set it. This erases all the other properties on that object. I'll update if I come across a clean solution.
We are using #ModelAttribute to achive what you want to do.
Create a method annotated with#modelattribute which loads a user based on a pathvariable throguh a repository.
create a method #Requestmapping with a param #modelattribute
The point here is that the #modelattribute method is the initializer for the model. Then spring merges the request with this model since we declare it in the #requestmapping method.
This gives you partial update functionality.
Some , or even alot? ;) would argue that this is bad practice anyway since we use our DAOs directly in the controller and do not do this merge in a dedicated service layer. But currently we did not ran into issues because of this aproach.
I build an API that merge view objects with entities before call persiste or merge or update.
It's a first version but I think It's a start.
Just use the annotation UIAttribute in your POJO`S fields then use:
MergerProcessor.merge(pojoUi, pojoDb);
It works with native Attributes and Collection.
git: https://github.com/nfrpaiva/ui-merge
Following approach could be used.
For this scenario, PATCH method would be more appropriate since the entity will be partially updated.
In controller method, take the request body as string.
Convert that String to JSONObject. Then iterate over the keys and update matching variable with the incoming data.
import org.json.JSONObject;
#RequestMapping(value = "/{id}", method = RequestMethod.PATCH )
public ResponseEntity<?> updateUserPartially(#RequestBody String rawJson, #PathVariable long id){
dbUser = userRepository.findOne(id);
JSONObject json = new JSONObject(rawJson);
Iterator<String> it = json.keySet().iterator();
while(it.hasNext()){
String key = it.next();
switch(key){
case "displayName":
dbUser.setDisplayName(json.get(key));
break;
case "....":
....
}
}
userRepository.save(dbUser);
...
}
Downside of this approach is, you have to manually validate the incoming values.
I've a customized and dirty solution employs java.lang.reflect package. My solution worked well for 3 years with no problem.
My method takes 2 arguments, objectFromRequest and objectFromDatabase both have the type Object.
The code simply does:
if(objectFromRequest.getMyValue() == null){
objectFromDatabase.setMyValue(objectFromDatabase.getMyValue); //change nothing
} else {
objectFromDatabase.setMyValue(objectFromRequest.getMyValue); //set the new value
}
A "null" value in a field from request means "don't change it!".
-1 value for a reference column which have name ending with "Id" means "Set it to null".
You can also add many custom modifications for your different scenarios.
public static void partialUpdateFields(Object objectFromRequest, Object objectFromDatabase) {
try {
Method[] methods = objectFromRequest.getClass().getDeclaredMethods();
for (Method method : methods) {
Object newValue = null;
Object oldValue = null;
Method setter = null;
Class valueClass = null;
String methodName = method.getName();
if (methodName.startsWith("get") || methodName.startsWith("is")) {
newValue = method.invoke(objectFromRequest, null);
oldValue = method.invoke(objectFromDatabase, null);
if (newValue != null) {
valueClass = newValue.getClass();
} else if (oldValue != null) {
valueClass = oldValue.getClass();
} else {
continue;
}
if (valueClass == Timestamp.class) {
valueClass = Date.class;
}
if (methodName.startsWith("get")) {
setter = objectFromRequest.getClass().getDeclaredMethod(methodName.replace("get", "set"),
valueClass);
} else {
setter = objectFromRequest.getClass().getDeclaredMethod(methodName.replace("is", "set"),
valueClass);
}
if (newValue == null) {
newValue = oldValue;
}
if (methodName.endsWith("Id")
&& (valueClass == Number.class || valueClass == Integer.class || valueClass == Long.class)
&& newValue.equals(-1)) {
setter.invoke(objectFromDatabase, new Object[] { null });
} else if (methodName.endsWith("Date") && valueClass == Date.class
&& ((Date) newValue).getTime() == 0l) {
setter.invoke(objectFromDatabase, new Object[] { null });
}
else {
setter.invoke(objectFromDatabase, newValue);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
In my DAO class, simcardToUpdate comes from http request:
simcardUpdated = (Simcard) session.get(Simcard.class, simcardToUpdate.getId());
MyUtil.partialUpdateFields(simcardToUpdate, simcardUpdated);
updatedEntities = Integer.parseInt(session.save(simcardUpdated).toString());
The main problem lies in your following code:
#RequestMapping(value = "/{id}", method = RequestMethod.POST )
public #ResponseBody ResponseEntity<User> update(#RequestBody User updateUser) {
dbUser = userRepository.findOne(updateUser.getId());
customObjectMerger(updateUser, dbUser);
userRepository.saveAndFlush(updateUuser);
...
}
In the above functions, you call some of your private functions & classes (userRepository, customObjectMerger, ...), but give no explanation how it works or how those functions look like. So I can only guess:
CustomObjectMerger merges this updateUser with the corresponding
dbUser from the database, updating the only fields included in
updateUser.
Here we don't know what happened in CustomObjectMerger (that's your function, and you don't show it). But from what you describe, I can make a guess: you copy all the properties from updateUser to your object at database. This is absolutely a wrong way, since when Spring map the object, it will fill all the data. And you only want to update some specific properties.
There are 2 options in your case:
1) Sending all the properties (including the unchanged properties) to the server. This may cost a little more bandwidth, but you still keep your way
2) You should set some special values as the default value for the User object (for example, id = -1, age = -1...). Then in customObjectMerger you just set the value that is not -1.
If you feel the 2 above solutions aren't satisfied, consider parsing the json request yourself, and don't bother with Spring object mapping mechanism. Sometimes it just confuse a lot.
Partial updates can be solved by using #SessionAttributes functionality, which are made to do what you did yourself with the customObjectMerger.
Look at my answer here, especially the edits, to get you started:
https://stackoverflow.com/a/14702971/272180
I've done this with a java Map and some reflection magic:
public static Entidade setFieldsByMap(Map<String, Object> dados, Entidade entidade) {
dados.entrySet().stream().
filter(e -> e.getValue() != null).
forEach(e -> {
try {
Method setter = entidade.getClass().
getMethod("set"+ Strings.capitalize(e.getKey()),
Class.forName(e.getValue().getClass().getTypeName()));
setter.invoke(entidade, e.getValue());
} catch (Exception ex) { // a lot of exceptions
throw new WebServiceRuntimeException("ws.reflection.error", ex);
}
});
return entidade;
}
And the entry point:
#Transactional
#PatchMapping("/{id}")
public ResponseEntity<EntityOutput> partialUpdate(#PathVariable String entity,
#PathVariable Long id, #RequestBody Map<String, Object> data) {
// ...
return new ResponseEntity<>(obj, HttpStatus.OK);
}

When do #SessionAttributes in SpringMVC get removed? (With code sample)

Under what exact circumstances do #SessionAttributes get cleared? I've discovered some confusing behaviour when trying to use two models in a page.
When I do a GET followed by a POST using this controller...
#Controller
#RequestMapping("/myPage*")
#SessionAttributes(value = {"object1", "object2"})
public class MyController {
#RequestMapping(method = RequestMethod.GET)
public String get(Model model) {
model.addAttribute("object1", new Object1());
model.addAttribute("object2", new Object2());
return "myPage";
}
#RequestMapping(method = RequestMethod.POST)
public String post(#ModelAttribute(value = "object1") Object1 object1) {
//do something with object1
return "myPage";
}
}
...object2 gets cleared from the Model. It no longer exists as a #SessionAttribute and cannot be accessed on my view page.
However if the signature of the second method is modified to this...
public String post(#ModelAttribute(value = "object1") Object1 object1,
#ModelAttribute(value = "object2") Object2 object2) {
...then object2 does not get cleared from the model and is available on my view page.
The javadoc for #SessionAttributes says:
... attributes will be removed once
the handler indicates completion of
its conversational session.
But I don't see how I have indicated completion of the conversational session in the first example but not in the second example.
Can anyone explain this behaviour or is it a bug?
You indicate completion of the conversation by calling
SessionStatus.setComplete
public void post(...., SessionStatus status) {
status.setComplete();
}
That said, I don't see why you should be loosing one model attribute and not the other.
Have you tried doing something like:
#ModelAttribute("object1")
public Object object1() { return new Object(); }
#ModelAttribute("object2")
public Object object2() { return new Object(); }
And see how that compares to putting the attributes in the model by hand.
You can remove single session level ModelAttribute like this:
Given ModelMap model, HttpSession session and you do:
if (categoryId != null)
model.addAttribute("categoryId", categoryId);
else {
model.remove("categoryId");
session.removeAttribute("categoryId");
}

Categories