I have a form whose purpose is to allow a user to create a new entry in a database table. The form is very large, ~50 fields in total. I'm needing a way get all of these values to my controller, though I don't see an easy way. Every solution I've seen is to have a #RequestParam('field'), but with around 50 fields that is a little crazy. Maybe if using a #RequestParam Map<T, T> is possible?
What I tried at first was to create an AJAX POST call to
baseapplication.com/add?field1=value1&field2=value2&...&field50=value50
but then the servlet complained about not finding the add.jsp file. This is sort of reasonable because that file does not exist, but I created a controller mapping #RequestMapping(value="/add") so I shouldn't actually need that file. I have another method which creates an AJAX GET call to /search with some url parameters, and that works fine. There is also no search.jsp file.
This problem is hard to explain, I hope I did a halfway decent job. Let's look at some code now, with omissions because dealing with ~50 form fields is very lengthy.
Starting with the JavaScript, which initiates this whole process:
ctx = "baseapplication.com"
$('#addNewRecordBtn').on('click', function(){
var m_insId = document.getElementById('m_insId');
//This repeats for every field
var url = ctx + '/add?insuredId=' + m_insId /** + all other fields **/;
addCase(url);
});
function addCase(url) {
$.ajax({
url: url,
type: 'POST'
}).success(function(data){
alert("Successfully added row");
}).fail(function(jzXHR, textStatus, errorThrown){
alert(jzXHR);
alert(textStatus);
alert(errorThrown);
});
}
So the flow of this is as follows: The user clicks on the addNewRecordBtn button, which fires the first function. This function grabs the value of every field in the form, then builds a URL with parameters for each of these values. The addCase() function is then called, which creates an AJAX POST (not sure what to call this?) to the URL that was just built. This function does not succeed, the error alerts provide zero information, but the server console claims Failed to find resource /WEB-INF/views/add.jsp
Now we move into the controller.
#Controller
public class ApplicationController {
#Autowired
SpecialClaimsCaseManager caseManager;
#RequestMapping(value="/add")
public void addRow(#RequestParam Map<String, String> requestParams) {
SpecialClaimsCase newCase = new SpecialClaimsCase();
newCase.setInsuredId(requestParams.get("insuredId"));
//Repeat this for all parameters
caseManager.addNewSpecialClaimsCase(newCase);
}
The caseManager.addNewSpecialClaimsCase(newCase) call just creates a DTO object out of this model object, and then adds that new object to the database via some Hibernate magic. I don't know much about that side, other than it works.
So, I'm not sure if I'm going about this the right way. I hear there's a way to map a model object to a JSP form using Spring's tag library, but that would require a ton of rewriting as the form is huge. I'm also using Bootstrap to build the interface, and I'm not sure if Bootstrap and Spring's tag library mix well. I can't imagine why not.
I'm not sure if I need to be using AJAX here or not. I went with it because I don't want the page to have to reload or anything. I'm not usually a web developer, so I am sure I am lacking some fundamental knowledge here.
My main question is: given my sitatuation, what is the best way to get this massive form of information into my controller?
Thanks in advance for reading this wall of text and code and for any assistance you can offer!
Create a domain class that has all of these needed fields and generate getters and setters and also a constructor. Once you get all these fields/some of these fields POST as json to your controller. The appropriate controller will then call the required service and then the DAO will handle the persistence part.
In short, send the data you need as a JSON object. The json will be made as java object and the operation on the same will be performed.
Here is the controller
#Controller
#RequestMapping(value = "/students/association")
public class StudentDepartmentController {
#Autowired
private StudentService studentService;
#Autowired
private StudentDepartmentService studentDepartmentService;
#RequestMapping(value = "/add-department", method = RequestMethod.POST)
public ResponseEntity<StudentDepartment> createStudentDepartmentAssociation(
#RequestBody final StudentDepartment studentDepartment) {
StudentDepartment newStudentDepartment;
// check if the student exists
Student student = studentService.getStudentByUuid(studentDepartment
.getStudentUuid().getUuid());
if (null == student) {
throw new IllegalArgumentException("No students found!");
}
// check the status of student
if (student.getStatus() == Liveliness.INACTIVE) {
throw new IllegalArgumentException(
"cannot create an association with an inactive student! activate student first");
}
// check for valid department
if (null == studentDepartment.getDepartment().getName()) {
throw new IllegalArgumentException("No such Department");
}
// check if the association already exists
if (null != findOneAssociationAgainstStudent(student)) {
throw new IllegalArgumentException(
"cannot create student department association, as "
+ student.getUsn()
+ " already present in another association ( "
+ studentDepartment.getDepartment().getName()
+ " )");
}
try {
newStudentDepartment = studentDepartmentService
.createNewAssociation(studentDepartment);
} catch (DataIntegrityViolationException ex) {
throw new AutomationTransactionException(
"cannot create student department association, as "
+ student.getUsn()
+ " already present in another association ( "
+ studentDepartment.getDepartment().getName()
+ " )", ex);
}
return new ResponseEntity<StudentDepartment>(newStudentDepartment,
HttpStatus.CREATED);
}
private StudentDepartment findOneAssociationAgainstStudent(Student student) {
return studentDepartmentService.findOneAssociation(student);
}
private StudentDepartment findOne(Uuid uuid) {
String studentDepartmentUuid = uuid.getUuid();
return findOne(studentDepartmentUuid);
}
private StudentDepartment findOne(String uuid) {
return studentDepartmentService.findOne(uuid);
}
#RequestMapping(value = "/delete-association", method = RequestMethod.DELETE)
public ResponseEntity<String> deleteStudentDepartmentAssociationByUuid(
#RequestBody final StudentDepartment studentDepartment) {
// check if association exists
StudentDepartment association = findOne(studentDepartment.getUuid());
if (null == association) {
throw new IllegalArgumentException("No such association found!");
}
studentDepartmentService.deleteAssociation(association);
return new ResponseEntity<String>("success", HttpStatus.OK);
}
}`
the #RequestBody annotation helps you to make the json object into java object.
with this, you can take the payload as json, and get the java object and send the json back to the UI with ResponseEntity<Class> annotation
Related
I need to give the input as only name which is in the WorkflowCloneRequestDTO class and all the instances need to be copied by their own. I'm trying to post the name from the postman and the id through the url which already exists.
But the data is not cloning. I'm not understanding where the problem is
This is the controller class:
#PostMapping("{id}/duplicate")
#ResponseBody
public WorkflowViewDTO duplicateWorkFlow(#PathVariable Long id, #RequestBody #Valid WorkflowCloneRequestDTO cloneRequest, BindingResult bindingResult) {
if (bindingResult.hasErrors()) {
throw new ControllerException(bindingResult, Constants.INPUT_VALIDATION_ERROR);
}
return workFlowService.cloneWorkflow(id, cloneRequest);
}
This the service class:
public WorkflowViewDTO cloneWorkflow(Long id, WorkflowCloneRequestDTO workflowCloneRequestDTO) {
Workflow workflow = workflowDAO.findById(id);
if (workflow == null) throw new ServiceException(ErrorEnum.WORKFLOW_NOT_FOUND);
Workflow duplicateWorkFlow = SerializationUtils.clone(workflow);
duplicateWorkFlow.setId(null);
//duplicateWorkFlow.setId(null);
duplicateWorkFlow.setName(workflowCloneRequestDTO.getName());
duplicateWorkFlow.setIsActive(true);
duplicateWorkFlow = workflowDAO.save(duplicateWorkFlow);
return duplicateWorkFlow.getView(WorkflowViewDTO.class);
}
Perhaps you should reconsider using SerializationUtils.clone at all. This is a slow way to copy data, and it looks like you do not need a copy outside of cloneWorkflow.
If you really need a copy, consider creating a copy constructor for Workflow or use setters and getters to extract necessary data.
I think in terms of REST, the ID should be placed into the URL, something like:
https://example.com/module/[ID]
and then I call GET, PUT, DELETE on that URL. That's kind of clear I think. In Spring MVC controllers, I'd get the ID with #PathVariable. Works.
Now, my practical problem with Spring MVC is, that if I do this, I have to NOT include the ID as part of the form (as well), Spring emits warnings of type
Skipping URI variable 'id' since the request contains a bind value with the same name.
otherwise. And it also makes kind of sense to only send it once, right? What would you do if they don't match??
That would be fine, but I do have a custom validator for my form backing bean, that needs to know the ID! (It needs to check if a certain unique name is already being used for a different entity instance, but cannot without knowing the ID of the submitted form).
I haven't found a good way to tell the validator that ID from #PathVariable, since the validation happens even before code in my controller method is executed.
How would you solve this dilemma?
This is my Controller (modified):
#Controller
#RequestMapping("/channels")
#RoleRestricted(resource = RoleResource.CHANNEL_ADMIN)
public class ChannelAdminController
{
protected ChannelService channelService;
protected ChannelEditFormValidator formValidator;
#Autowired
public ChannelAdminController(ChannelService channelService, ChannelEditFormValidator formValidator)
{
this.channelService = channelService;
this.formValidator = formValidator;
}
#RequestMapping(value = "/{channelId}/admin", method = RequestMethod.GET)
public String editChannel(#PathVariable Long channelId, #ModelAttribute("channelForm") ChannelEditForm channelEditForm, Model model)
{
if (channelId > 0)
{
// Populate from persistent entity
}
else
{
// Prepare form with default values
}
return "channel/admin/channel-edit";
}
#RequestMapping(value = "/{channelId}/admin", method = RequestMethod.PUT)
public String saveChannel(#PathVariable Long channelId, #ModelAttribute("channelForm") #Valid ChannelEditForm channelEditForm, BindingResult result, Model model, RedirectAttributes redirectAttributes)
{
try
{
// Has to validate in controller if the name is already used by another channel, since in the validator, we don't know the channelId
Long nameChannelId = channelService.getChannelIdByName(channelEditForm.getName());
if (nameChannelId != null && !nameChannelId.equals(channelId))
result.rejectValue("name", "channel:admin.f1.error.name");
}
catch (EmptyResultDataAccessException e)
{
// That's fine, new valid unique name (not so fine using an exception for this, but you know...)
}
if (result.hasErrors())
{
return "channel/admin/channel-edit";
}
// Copy properties from form to ChannelEditRequest DTO
// ...
// Save
// ...
redirectAttributes.addFlashAttribute("successMessage", new SuccessMessage.Builder("channel:admin.f1.success", "Success!").build());
// POST-REDIRECT-GET
return "redirect:/channels/" + channelId + "/admin";
}
#InitBinder("channelForm")
protected void initBinder(WebDataBinder binder)
{
binder.setValidator(formValidator);
}
}
I think I finally found the solution.
As it turns out Spring binds path variables to form beans, too! I haven't found this documented anywhere, and wouldn't have expected it, but when trying to rename the path variable, like #DavidW suggested (which I would have expected to only have a local effect in my controller method), I realized that some things got broken, because of the before-mentioned.
So, basically, the solution is to have the ID property on the form-backing object, too, BUT not including a hidden input field in the HTML form. This way Spring will use the path variable and populate it on the form. The local #PathVariable parameter in the controller method can even be skipped.
The cleanest way to solve this, I think, is to let the database handle the duplicates: Add a unique constraint to the database column. (or JPA by adding a #UniqueConstraint)
But you still have to catch the database exception and transform it to a user friendly message.
This way you can keep the spring MVC validator simple: only validate fields, without needing to query the database.
Could you not simply disambiguate the 2 (URI template variables vs. parameters) by using a different name for your URI template variable?
#RequestMapping(value = "/{chanId}/admin", method = RequestMethod.PUT)
public String saveChannel(#PathVariable Long chanId, #ModelAttribute("channelForm") #Valid ChannelEditForm channelEditForm, BindingResult result, Model model, RedirectAttributes redirectAttributes)
{
[...]
What ever you said is correct the correct way to design rest api is to mention the resource id in path variable if you look at some examples from the swagger now as open api you could find similar examples there
for you the correct solution would be to use a custom validator like this
import javax.validation.Validator;`
import org.apache.commons.lang3.StringUtils;`
import org.springframework.validation.Errors;`
importorg.springframework.validation.beanvalidation.CustomValidatorBean;`
public class MyValidator extends CustomValidatorBean {`
public void myvalidate(Object target,Errors errors,String flag,Profile profile){
super.validate(target,errors);
if(StringUtils.isEmpty(profile.name())){
errors.rejectValue("name", "NotBlank.profilereg.name", new Object[] { "name" }, "Missing Required Fields");
}
}
}
This would make sure all the fields are validated and you dont need to pass the id in the form.
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.
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);
}
in My jsp I am using spring form tags to bind and update data.
my scenario is to show default values when user enters the page. these default values should come from database depending on some conditions.
But when user edits the data and submit, I want to save them in databse without updating the default values.
Any suggestion is greatly appreciated!
Standard approach to a form page is to have a controller with two methods, one for GET and one for POST. You'll also need an object to bind to the form.
The method that handles the GET sets up the bind object and puts it in the model, then returns the view for the form page. The bind object here can be created with default values which you can get from anywhere. Your bind object will probably have some key, like an ID. The default one can have a zero/default key.
The method that handles the POST takes the bind object as a parameter. It probably validates the values then inserts into the database. Its likely the database will generate the key/id.
Here's an example;
#Controller
public class PersonController {
#RequestMapping(value="/person.do", method=RequestMethod.GET)
public ModelAndView setup() {
ModelAndView response = new ModelAndView("person");
//Create default bind object, can get values
//from database if you like. Here they're just
//hard coded.
Person person = new Person();
person.setName("Joe Bloggs");
response.addObject("person", person);
return response;
}
#RequestMapping(value="/person.do", method=RequestMethod.POST)
public ModelAndView post(#ModelAttribute("person") Person person,
BindingResult result) {
Validator.validate(person, result);
if (result.hasErrors()) {
ModelAndView response = new ModelAndView("person");
response.addObject("person", person);
return response;
} else {
personDao.store(person);
}
return new ModelAndView("redirect:nextPage.do");
}
}
The form will be populated with any values you supply in the backing object. I don't understand the second part of your question.