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']]
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:
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