I am developing a simple app that use spring boot and jpa and thymeleaf, i need to upload the image to my database but when i click on submit on my page fields get inserted in database except image field. i read the different posts on the website but none of them was really close to my problem and i have no idea why it does not insert the file into the database. i have to say the image field located in recipe entity
controller
#Controller
#RequestMapping("/recipe")
public class RecipeController {
RecipeRepository recipeRepository;
IngredientRepository ingredientRepository;
public RecipeController(RecipeRepository recipeRepository, IngredientRepository ingredientRepository) {
this.recipeRepository = recipeRepository;
this.ingredientRepository = ingredientRepository; //// this is other repo which cause no problem
}
#GetMapping("/insert_recipe")
public String insetRecipe(Model model){
model.addAttribute("addRecipe",new Recipe());
model.addAttribute("addingredient",new Ingredient()); // this is other entity which cause no problem
return "insert_recipe";
}
#PostMapping("/postrecipe")
public String postRecipe(#ModelAttribute("addRecipe")#Valid Recipe recipe, BindingResult result, Model model, #ModelAttribute("addingredient") Ingredient ingredient) {
recipeRepository.save(recipe);
long id=recipe.getId();
Recipe u=recipeRepository.findById(id);
//model.addAttribute("addingredient",recipe);
ingredient.setRecipe(u);
ingredientRepository.save(ingredient);
return "redirect:/recipe/insert_recipe";
}
}
view page
<!DOCTYPE html>
<html xmlns:th="https://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form th:action="#{/recipe/postrecipe}" th:object="${addRecipe}" enctype="multipart/form-data" method="post" >
des: <input type="text" name="descriptiob"/>
serving: <input type="text" name="servings"/>
for ingredient description <input type="text" name="description" th:object="${addingredient}">
upload picture <input type="file" th:name="image">
<input type="submit" value="submit">
</form>
<br/><br/>
</body>
</html>
repo
public interface RecipeRepository extends CrudRepository<Recipe,Long> {
Recipe findById(long is);
}
entity
#Entity
public class Recipe {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String descriptiob;
#Lob
private Byte[] image;
private Integer servings;
//setter and getter method also are in this class
error
Field error in object 'addRecipe' on field 'image': rejected value [org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile#12c96ba6]; codes [typeMismatch.addRecipe.image,typeMismatch.image,typeMismatch.[Ljava.lang.Byte;,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [addRecipe.image,image]; arguments []; default message [image]]; default message [Failed to convert property value of type 'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile' to required type 'java.lang.Byte[]' for property 'image'; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type 'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile' to required type 'java.lang.Byte' for property 'image[0]': PropertyEditor [org.springframework.beans.propertyeditors.CustomNumberEditor] returned inappropriate value of type 'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile']]
GitHub link
Let's have a look at the thymeleaf fragment
upload picture <input type="file" th:name="image">
and the error message we get:
Field error in object 'addRecipe' on field 'image': (...)
Cannot convert value of type '(...) StandardMultipartHttpServletRequest$StandardMultipartFile'
(...)to required type 'java.lang.Byte' for property 'image[0]': PropertyEditor (...)
upload picture <input type="file" th:name="image">
The name image colides with the Recipe field which has a different type (Byte[] than the MultipartFile we are trying to pass in the request).
One way to do it may be:
Step I. Change the th:name="image" to something else (that does not collide with the field names), e.g. th:name="imagefile"
upload picture <input type="file" th:name="imagefile">
Step II. Change the #RequestParam name to imagefile and convert the MultipartFile to the Byte[] before saving it.
#PostMapping("/postrecipe")
public String postRecipe(#ModelAttribute("addRecipe") Recipe recipe,
Model model,
#ModelAttribute("addingredient")#Valid Ingredient ingredient,
BindingResult bindingResult,
#RequestParam("imagefile") MultipartFile file, // changed from 'image'
#RequestParam("unitid") long id) throws IOException {
long myid=id;
recipeRepository.save(recipe);
long ids=recipe.getId();
Recipe u=recipeRepository.findById(ids);
model.addAttribute("addingredient",recipe);
UnitOfMeasure ob=unitOfMeasureRepository.findById(myid);
Byte[] byteObjects = convertToBytes(file); // we have to convert it to Byte[] array
u.setImage(byteObjects);
recipeRepository.save(u); // TODO refactor - save once
ingredient.setRecipe(u);
ingredient.setUnitOfMeasure(ob);
ingredientRepository.save(ingredient);
return "redirect:/recipe/insert_recipe";
}
private Byte[] convertToBytes(MultipartFile file) throws IOException {
Byte[] byteObjects = new Byte[file.getBytes().length];
int i = 0;
for (byte b : file.getBytes()) {
byteObjects[i++] = b;
}
return byteObjects;
}
Additional remarks:
Have a look at how Sfg handles the image upload and the displaying of it in the tutorial repository
It would be better to move the MultiPartFile to Byte[] conversion to the separate service (see the Sfg's repo / tutorial)
Edit:
Answering the question from the comment:
I do not use xampp.
The .bin extension suggests it is a binary file (makes sense as the image file is stored as the byte array).
Below is the snippet which should let you display the image in the browser instead.
IOUtils is from (import org.apache.tomcat.util.http.fileupload.IOUtils;)
#GetMapping("{id}/recipeimage")
public void renderImageFromDb(#PathVariable Long id, HttpServletResponse response) throws IOException {
Recipe recipe = recipeRepository.findById(id).get();
byte[] byteArray = new byte[recipe.getImage().length];
int i = 0;
for (Byte wrappedByte: recipe.getImage()) {
byteArray[i++] = wrappedByte; // auto unboxing
}
response.setContentType("image/jpeg");
InputStream is = new ByteArrayInputStream(byteArray);
IOUtils.copy(is, response.getOutputStream());
}
If you know the id of the recipe, just type localhost:8080/recipe/<recipe id>/recipeimage
Concerning your problem that the input is not bound to the ModelAttribute:
change th:name to name on your input field.
Concerning your type error:
Maybe this could help you: Upload files to the #ModelAttribute using Thymeleaf
You need to use the correct type which is MultipartFile for your image. Consider using another class called e.g. RecipeDto on your Controllers method signature. Map this to your Recipe Entity so you can somehow convert the MultipartFile to a Byte array manually.
edit: org.springframework.web.multipart.MultipartFile#getBytes might do this for you
Concerning DTO:
What are the DAO, DTO and Service layers in Spring Framework?
https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application
Related
I have set up a Controller with two methods. The URL mappings are exactly the same, and the only difference is the mapping annotation method. One is #PostMapping and the other #DeleteMapping written in that order. However, when I try to call the #DeleteMapping method, the #PostMapping method is called.
Note that even though variables in the two URL forms are different, they have the same values. They are just from different HTML pages.
The question is how to call the desired method each time.
#PostMapping method
#Secured({"ROLE_ADMIN", "ROLE_STUDENT"})
#PostMapping("students/{username}/internships/{id}")
public String addInternship(Model model, #PathVariable("username") String username, #PathVariable("id") int id) {
/* DOES SOME STUFF
Student student = userService.getUser(username).getStudent();
Internship internship = userService.getInternship(id);
StudentInternship studentInternship = new StudentInternship(internship, student, "Sent");
internship.setNumberOfPositions(internship.getNumberOfPositions() - 1);
userService.updateInternship(internship);
student.setApplicationNumber(student.getApplicationNumber() + 1);
userService.updateStudent(student);
userService.addStudentInternship(studentInternship);
*/
return "redirect:/internships";
}
#DeleteMapping method
#Secured({"ROLE_ADMIN", "ROLE_STUDENT"})
#DeleteMapping("students/{username}/internships/{id}")
public String removeInternship(Model model, #PathVariable("username") String username, #PathVariable("id") int id) {
/* DOES SOME STUFF
Student student = userService.getUser(username).getStudent();
Internship internship = userService.getInternship(id);
int studentInternshipID = userService.getStudentInternshipByParams(student, internship).getId();
internship.setNumberOfPositions(internship.getNumberOfPositions() + 1);
userService.updateInternship(internship);
student.setApplicationNumber(student.getApplicationNumber() - 1);
userService.updateStudent(student);
userService.removeStudentInternship(studentInternshipID);
*/
return "redirect:/students/" + username + "/internships";
}
HTML
<form:form action="${pageContext.request.contextPath}/students/${username}/internships/${tempInternship.id}" method="POST">
<input type="submit" value="Request" ${disabled}/>
</form:form>
<form:form action="${pageContext.request.contextPath}/students/${tempStudentInternship.student.username}/internships/${tempStudentInternship.internship.id}" method="DELETE">
<input type="submit" value="Dismiss" />
</form:form>
Browsers only support GET and POST as http request methods. The solution is to send your form with the POST method and inject a hidden field inside the same html form called _method with your desired method as a value, in your case here, it is just DELETE. For the case of POST, just write your form as usual.
Example :
<form:form action="${pageContext.request.contextPath}/students/${tempStudentInternship.student.username}/internships/${tempStudentInternship.internship.id}" method="POST">
<input type="hidden" name="_method" value="DELETE"/>
<input type="submit" value="Dismiss" />
</form:form>
Please, have a look at this answer for creating the spring bean and then applying the mentioned form attribute inside spring:form html forms.
Only GET and POST are allowed from a FORM. You need to use AJAX to specify additional types of the request.
Hi you need to use javascript and XMLHttpRequest in order to specify the type of the request.
Here is one example I took randomly from internet:
// Delete a user
var url = "http://localhost:8080/api/v1/users";
var xhr = new XMLHttpRequest();
xhr.open("DELETE", url+'/12', true);
xhr.onload = function () {
var users = JSON.parse(xhr.responseText);
if (xhr.readyState == 4 && xhr.status == "200") {
console.table(users);
} else {
console.error(users);
}
}
xhr.send(null);
I would like to know how to create forms that uses th:object for each object looped in a th:each. For example, I have the following code.
HTML
<th:block th:each="store: ${stores}">
<form th:object="${store}" th:action="#{/modify-store}">
<input th:field="*{idStorePk}"/>
<input th:field="*{name}"/>
<input th:field="*{phoneNumber}"/>
<button type="submit">Modify</button>
</form>
</th:block>
Controller
#RequestMapping(value = "/stores")
public String getIndex(Model model) {
model.addAttribute("stores", storeService.getAllStores());
return "store";
}
So, I would like to add a form for each object, but it seems that it is not possible and I get the following error.
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'store' available as request attribute
So, I decided to add a #ModelAttribute in my controller, but can't get to return the actual store.
#ModelAttribute("store")
public Store getStore(Store store) {
return store;
}
With this approach all my forms have null values. I also tried to add a #PathVariable, but can't see to bind it using th:object. Is there a solution for this?
So for anyone stuck at a similar problem. I find out a work around that might help you out. First, you can't use th:object, it simply won't cut it. Instead, do the following.
<th:block th:each="store: ${stores}">
<form class="store-form" th:action="#{/modify-store}">
<input th:name="idStorePk" th:value="${store.idStorePk}"/>
<input th:name="name" th:value="${store.name}"/>
<input th:name="phoneNumber" th:value="${store.phoneNumber}"/>
<button class="submit-button" type="submit">Modify</button>
</form>
</th:block>
Then just add something similar to the controller.
#PostMapping(value = "/modify-store")
#ResponseBody
public boolean deleteEntry(#ModelAttribute Store store) throws Exception {
// Your code here...
return true;
}
If you want to send it asynchronously then you will need to add some JS code in order for it to work. It should look something like the code below.
const forms = document.querySelectorAll('.store-form');
forms.forEach(form => {
form.addEventListener('submit', event => {
// Stop the normal form submit triggered by the submit button
event.preventDefault();
const formInputs = form.getElementsByTagName("input");
let formData = new FormData();
for (let input of formInputs) {
formData.append(input.name, input.value);
}
fetch(form.action,
{
method: form.method,
body: formData
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.log(error.message))
.finally(() => console.log("Done"));
});
You're sending stores in your controller in model-attribute and on your second controller where you're submitting your form you're using store that's the reason you're getting this error. So correct the spelling error on any one of your controller. Like this :-
#RequestMapping(value = "/stores")
public String getIndex(Model model) {
model.addAttribute("stores", storeService.getAllStores());
return "store";
}
And Your second controller where you're submitting your form will be like this -
#ModelAttribute("stores")
public Store getStore(Store store) {
return store;
}
I am super new to front end development. I have a simple webapp where you can submit a form, and I want to be able to get the form data in a REST endpoint. I am using Spring boot which contains Spring MVC.
The HTML:
<div class="modal-header modal-header-info">
<button type="button" class="close" data-dismiss="modal"
aria-hidden="true">×</button>
<h4 class="modal-title">Add Entity</h4>
</div>
<div class="modal-body">
<form role="form" method="post" action="/createEntity">
<div class="form-group">
<label for="name">Name:</label> <input type="text"
class="form-control" id="name">
</div>
<button type="submit" class="btn btn-info">Submit</button>
</form>
</div>
The Java:
#RequestMapping(value = "/createEntity", method = RequestMethod.POST)
public void createEntity(#RequestBody String payload) {
System.out.println("Hello world!");
System.out.println(payload);
}
I get this error back:
Failed to read HTTP message:
org.springframework.http.converter.HttpMessageNotReadableException:
Required request body is missing: public void main.java.info.spring.data.neo4j.controllers.CreateEntityController.createEntity(java.lang.String)
How do I get the "name" value from the form? The "hello world" attribute prints out if I take out the #RequestBody part.
Edit: my configuration per request:
#EnableTransactionManagement
#Import(RepositoryRestMvcConfiguration.class)
#EnableScheduling
#EnableAutoConfiguration
#ComponentScan(basePackages = { "main.java.info.spring.data.neo4j.services",
"main.java.info.spring.data.neo4j.controllers" })
#Configuration
#EnableNeo4jRepositories(basePackages = "main.java.info.spring.data.neo4j.repositories")
public class MyNeo4jConfiguration extends Neo4jConfiguration {
public static final String URL = System.getenv("NEO4J_URL") != null ? System.getenv("NEO4J_URL")
: "http://localhost:7474";
#Override
public Neo4jServer neo4jServer() {
return new RemoteServer(URL, "neo4j", "LOL my pass");
}
#Override
public SessionFactory getSessionFactory() {
return new SessionFactory("main.java.info.spring.data.neo4j.domain");
}
}
Problem 1: HTML form controls are missing the name attribute
HTML forms are submitted in name-value pairs. Form controls that do not have a name attribute cannot be submitted because their names are not known. The id attribute is only for referencing controls on the client-side.
<input type="text" class="form-control" id="name">
is missing the name attribute. It should be:
<input type="text" class="form-control" id="name" name="name"/>
This will submit the control as a form parameter in the HTTP request body, something like:
name: [the value you specify for the control]
Problem 2: #RequestBody as a controller method argument will give you the entire HTTP request body
public void createEntity(#RequestBody String payload)
means that you want the entire HTTP request body to be passed as a String to the controller method createEntity. If your HTML form is indeed what you have posted, you will get the following value for payload (after you have addressed problem 1):
name: [the value you specify for the control]
If this is what you want, keep the #RequestBody annotation, but I suspect that you are only interested in the value of the name request parameter. If that is the case, you need to change your method declaration to (after you have addressed problem 1):
public void createEntity(#RequestParam String name)
This will map the HTTP request parameter name to the controller method argument name.
Problem 3: Having void as the return type for a controller method will force Spring MVC to look for a view with the same name as the method name
Since you have the controller method declared as:
public void createEntity(#RequestBody String payload)
Spring MVC will look for a view named createEntity after this method exits. If you have a view with that name, well and good. Otherwise, once you have fixed problems 1 and 2, you will get a 404 - Resource not found error.
I am sorry that I cannot share my code. I will try to give an appropriate example for my situation.
Class Person:
public class Community {
private int personCount;
private List<Person> persons;
//getters and setters for all
}
Call Person:
public class Person {
private int name;
private int age;
//getters and setters
}
Now, I want to serialize and send a form with an AJAX call from my JSP.
In JSP, I used
<div th:each="parameter,iterStat : ${persons}" class="personsContainer">
<div th:text="'Name: ' + ${parameter.name}"></div>
...
to traverse through the list.
This is the AJAX call
function updateCommunityPeople(initial) {
var getValuesUrl = /*[[#{/getPeoplesJson}]]*/ "";
var data = $(".personsContainer").parents('form:first').serialize();
data += "&initial=" + initial;
alert("date: "+data);
$.ajax({
'type': "POST",
'url': getValuesUrl,
'dataType': "json",
'data': data,
})
.done(function (data, textStatus, jqXHR) {
alert("Updated successfully");
});
}
My controller method:
#RequestMapping(value = "/getPeoplesJson", method = RequestMethod.POST)
public #ResponseBody String getCommunityPeopleValuesJson(HttpServletRequest request, HttpSession session,
Locale locale, Model model, Device device, Principal principal, #Valid Community post,
BindingResult result)
{
int count = post.getPersonCount();
if (post != null && post.getPersons() != null)
{
//calls to service and so on...
return "true";
}
return false;
}
Here, I am able to retrieve the person count correctly but the whole issue is with the list of Persons. It is null always...
Your list of persons is always null because the jQuery's serialize() function takes into account only form elements. From the official documentation:
Description: Encode a set of form elements as a string for submission.
The .serialize() method creates a text string in standard URL-encoded notation. It can act on a jQuery object that has selected individual form controls, such as <input>, <textarea>, and <select>: $( "input, textarea, select" ).serialize();
It is typically easier, however, to select the <form> itself for serialization
For example, if you had a <select multiple> element and listed the names as options, the serialize function would correctly serialize all selected <option> elements:
<form>
<select multiple th:each="parameter,iterStat : ${persons}" class="personsContainer">
<option selected th:text="'Name: ' + ${parameter.name}"></option>
</select>
</form>
Here is a working example.
Edit
To automatically fill in the persons list from the form parameters or the query string it will need to be in the following format:
personCount=2&persons[0].name=joe&persons[0].age=20&persons[1].name=john&persons[1].age=25`
This means your form should look something like this:
<form>
<div th:each="parameter,iterStat : ${persons}" class="personsContainer">
<input type="text" th:value="${parameter.name}" th:name="'people[' + ${iterStat.index} + '].name'">
<input type="text" th:value="${parameter.age}" th:name="'age[' + ${iterStat.index} + '].age'">
</div>
</form>
Also, the ajax's dataType should be set to application/x-www-form-urlencoded if you're going to use serialize().
I have a web application with spring in which I do some file upload. Under eclipse, using Jetty (the maven plugin) it works perfectly. But when I deploy the application under Tomcat it does not and I get the following Exception :
org.springframework.web.bind.MissingServletRequestParameterException: Required org.springframework.web.multipart.MultipartFile parameter 'file' is not present
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter$ServletHandlerMethodInvoker.raiseMissingParameterException(AnnotationMethodHandlerAdapter.java:545)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveRequestParam(HandlerMethodInvoker.java:336)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:207)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.invokeHandlerMethod(HandlerMethodInvoker.java:132)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.invokeHandlerMethod(AnnotationMethodHandlerAdapter.java:326)
org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter.handle(AnnotationMethodHandlerAdapter.java:313)
org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:875)
Here is my form :
<form method="post" action="../admin/import.html" id="import"
enctype="multipart/form-data">
<div id="importInmates" align="center">
<input type="file" name="file" id="file"
data-dojo-type="dijit.form.Button"
label="<fmt:message key='import.file' />" />
<button data-dojo-type="dijit.form.Button" id="importInmates"
type="submit">
<fmt:message key="import.import" />
</button>
</div>
<input type="hidden" name="importType" value="inmates" />
</form>
And here is the intercepting method :
#RequestMapping(value = IMPORT_PAGE, method = RequestMethod.POST)
public String recieveFile(#RequestParam("importType") String importType,
#RequestParam("file") MultipartFile multipartFile, final HttpSession session)
{
if (multipartFile.getSize() < 0)
{
LOGGER.debug("No file has been uploaded");
return "redirect:.." + IMPORT_PAGE;
}
File file = new File("tmp");
try
{
multipartFile.transferTo(file);
BufferedReader lec = new BufferedReader(new FileReader(file));
LOGGER.debug(lec.readLine());
lec.close();
}
catch (Exception e)
{
LOGGER.error("An exception occured while reading " + importType + " file", e);
}
return "redirect:.." + IMPORT_PAGE;
}
I have added the following bean :
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="100000000"></property>
</bean>
both in applicationContext.xml and mvc-servlet.xml even if I think that only the latter is important.
Any help would be appreciated.
Thanks.
Thanks to #Bart I was able to find the following simple solution :
In the intercepting method, use #ModelAttribute instead of #RequestParam :
#RequestMapping(value = IMPORT_PAGE, method = RequestMethod.POST)
public String recieveFile(#RequestParam("importType") String importType,
#ModelAttribute("file") UploadedFile uploadedFile, final HttpSession session)
{
MultipartFile multipartFile = uploadedFile.getFile();
Where UploadedFile is the following class :
public class UploadedFile
{
private String type;
private MultipartFile file;
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
public void setFile(MultipartFile file)
{
this.file = file;
}
public MultipartFile getFile()
{
return file;
}
}
And it's working !!
Thanks everyone for your help.
Use #RequestPart instead of #RequestParam.
From the source:
Annotation that can be used to associate the part of a "multipart/form-data" request
with a method argument. Supported method argument types include {#link MultipartFile}
in conjunction with Spring's {#link MultipartResolver} abstraction,
{#code javax.servlet.http.Part} in conjunction with Servlet 3.0 multipart requests,
or otherwise for any other method argument, the content of the part is passed through an
{#link HttpMessageConverter} taking into consideration the 'Content-Type' header
of the request part. This is analogous to what #{#link RequestBody} does to resolve
an argument based on the content of a non-multipart regular request.
Note that #{#link RequestParam} annotation can also be used to associate the
part of a "multipart/form-data" request with a method argument supporting the same
method argument types. The main difference is that when the method argument is not a
String, #{#link RequestParam} relies on type conversion via a registered
{#link Converter} or {#link PropertyEditor} while #{#link RequestPart} relies
on {#link HttpMessageConverter}s taking into consideration the 'Content-Type' header
of the request part. #{#link RequestParam} is likely to be used with name-value form
fields while #{#link RequestPart} is likely to be used with parts containing more
complex content (e.g. JSON, XML).