使用Spring Boot、JPA和Thymeleaf将图像上传到数据库。

huangapple go评论127阅读模式
英文:

uploading a image to the database using springboot , jpa ,thymeleaf

问题

@Controller
@RequestMapping("/recipe")
public class RecipeController {
    RecipeRepository recipeRepository;
    IngredientRepository ingredientRepository;

    public RecipeController(RecipeRepository recipeRepository, IngredientRepository ingredientRepository) {
        this.recipeRepository = recipeRepository;
        this.ingredientRepository = ingredientRepository;
    }

    @GetMapping("/insert_recipe")
    public String insetRecipe(Model model) {
        model.addAttribute("addRecipe", new Recipe());
        model.addAttribute("addingredient", new Ingredient());
        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);
        ingredient.setRecipe(u);
        ingredientRepository.save(ingredient);
        return "redirect:/recipe/insert_recipe";
    }
}
<!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>
public interface RecipeRepository extends CrudRepository<Recipe, Long> {
    Recipe findById(long id);
}
@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 methods are also in this class
}
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链接: https://github.com/pooyafils/Recipe

英文:

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(&quot;/recipe&quot;)
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(&quot;/insert_recipe&quot;)
    public String insetRecipe(Model model){
        model.addAttribute(&quot;addRecipe&quot;,new Recipe());
        model.addAttribute(&quot;addingredient&quot;,new Ingredient()); // this is other entity which cause no problem
      return   &quot;insert_recipe&quot;;
    }
    @PostMapping(&quot;/postrecipe&quot;)
    public String postRecipe(@ModelAttribute(&quot;addRecipe&quot;)@Valid Recipe recipe, BindingResult result, Model model, @ModelAttribute(&quot;addingredient&quot;) Ingredient ingredient)  {
        recipeRepository.save(recipe);
        long id=recipe.getId();
        Recipe u=recipeRepository.findById(id);
        //model.addAttribute(&quot;addingredient&quot;,recipe);
        ingredient.setRecipe(u);
        ingredientRepository.save(ingredient);
        return &quot;redirect:/recipe/insert_recipe&quot;;
    }
}

view page

&lt;!DOCTYPE html&gt;
&lt;html xmlns:th=&quot;https://www.thymeleaf.org&quot;&gt;

&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;Title&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;form th:action=&quot;@{/recipe/postrecipe}&quot; th:object=&quot;${addRecipe}&quot;  enctype=&quot;multipart/form-data&quot; method=&quot;post&quot;  &gt;
des:    &lt;input type=&quot;text&quot; name=&quot;descriptiob&quot;/&gt;
    serving:    &lt;input type=&quot;text&quot; name=&quot;servings&quot;/&gt;
   for ingredient description &lt;input type=&quot;text&quot; name=&quot;description&quot; th:object=&quot;${addingredient}&quot;&gt;
    upload picture &lt;input type=&quot;file&quot; th:name=&quot;image&quot;&gt;

    &lt;input type=&quot;submit&quot; value=&quot;submit&quot;&gt;
&lt;/form&gt;
&lt;br/&gt;&lt;br/&gt;

&lt;/body&gt;
&lt;/html&gt;

repo

public interface RecipeRepository extends CrudRepository&lt;Recipe,Long&gt; {
    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 &#39;addRecipe&#39; on field &#39;image&#39;: 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 &#39;org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile&#39; to required type &#39;java.lang.Byte[]&#39; for property &#39;image&#39;; nested exception is java.lang.IllegalArgumentException: Cannot convert value of type &#39;org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile&#39; to required type &#39;java.lang.Byte&#39; for property &#39;image[0]&#39;: PropertyEditor [org.springframework.beans.propertyeditors.CustomNumberEditor] returned inappropriate value of type &#39;org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile&#39;]]

使用Spring Boot、JPA和Thymeleaf将图像上传到数据库。

GitHub link

答案1

得分: 2

让我们看一下Thymeleaf片段:

上传图片 <input type="file" th:name="image">

以及我们得到的错误信息:

对象'addRecipe'中字段'image'的字段错误:(...)
无法将类型为'(...) StandardMultipartHttpServletRequest$StandardMultipartFile'的值转换为属性'image[0]'所需的类型'java.lang.Byte':PropertyEditor(...)
上传图片 <input type="file" th:name="image">

名称image与具有不同类型(Byte[]与我们试图在请求中传递的MultipartFile不同)的Recipe字段发生冲突。

一种方法是:

第一步:将th:name="image"更改为其他内容(不与字段名称冲突),例如th:name="imagefile"

上传图片 <input type="file" th:name="imagefile">

第二步:将@RequestParam名称更改为imagefile,并在保存之前将MultipartFile转换为Byte[]

@PostMapping("/postrecipe")
public String postRecipe(@ModelAttribute("addRecipe") Recipe recipe,
                         Model model,
                         @ModelAttribute("addingredient")@Valid Ingredient ingredient,
                         BindingResult bindingResult,
                         @RequestParam("imagefile") MultipartFile file, // 从'image'更改而来
                         @RequestParam("unitid") long id) throws IOException {
  // ...
}

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;
}

附加备注:

  • 查看Sfg如何处理图像上传并在tutorial repository中显示图像。
  • 最好将MultiPartFileByte[]的转换移到单独的服务中(参见Sfg的repo / tutorial)。

编辑:
回答评论中的问题:
我不使用xampp。
.bin扩展名表明它是一个二进制文件(因为图像文件存储为字节数组,所以有道理)。

以下是应该让您在浏览器中显示图像的代码片段。

IOUtils 来自(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; // 自动拆箱
    }

    response.setContentType("image/jpeg");
    InputStream is = new ByteArrayInputStream(byteArray);
    IOUtils.copy(is, response.getOutputStream());
}

如果您知道食谱的id,只需键入localhost:8080/recipe/<recipe id>/recipeimage

英文:

Let's have a look at the thymeleaf fragment

upload picture &lt;input type=&quot;file&quot; th:name=&quot;image&quot;&gt;

and the error message we get:

Field error in object &#39;addRecipe&#39; on field &#39;image&#39;: (...)
Cannot convert value of type &#39;(...) StandardMultipartHttpServletRequest$StandardMultipartFile&#39;
(...)to required type &#39;java.lang.Byte&#39; for property &#39;image[0]&#39;: PropertyEditor (...)
upload picture &lt;input type=&quot;file&quot; th:name=&quot;image&quot;&gt;

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=&quot;image&quot; to something else (that does not collide with the field names), e.g. th:name=&quot;imagefile&quot;

upload picture &lt;input type=&quot;file&quot; th:name=&quot;imagefile&quot;&gt;

Step II. Change the @RequestParam name to imagefile and convert the MultipartFile to the Byte[] before saving it.

    @PostMapping(&quot;/postrecipe&quot;)
    public String postRecipe(@ModelAttribute(&quot;addRecipe&quot;) Recipe recipe,
                             Model model,
                             @ModelAttribute(&quot;addingredient&quot;)@Valid Ingredient ingredient,
                             BindingResult bindingResult,
                             @RequestParam(&quot;imagefile&quot;) MultipartFile file, // changed from &#39;image&#39;
                             @RequestParam(&quot;unitid&quot;) long id) throws IOException {
      long myid=id;
        recipeRepository.save(recipe);
        long ids=recipe.getId();
        Recipe u=recipeRepository.findById(ids);
        model.addAttribute(&quot;addingredient&quot;,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 &quot;redirect:/recipe/insert_recipe&quot;;
    }

    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(&quot;{id}/recipeimage&quot;)
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(&quot;image/jpeg&quot;);
    InputStream is = new ByteArrayInputStream(byteArray);
    IOUtils.copy(is, response.getOutputStream());
}

If you know the id of the recipe, just type localhost:8080/recipe/&lt;recipe id&gt;/recipeimage

答案2

得分: 0

关于您的问题,即输入未绑定到ModelAttribute:

将您的输入字段上的th:name更改为name。

关于您的类型错误

也许这可以帮助您:https://stackoverflow.com/questions/47476733/upload-files-to-the-modelattribute-using-thymeleaf

您需要使用正确的类型,即MultipartFile来处理您的图像。考虑在您的控制器方法签名中使用另一个类,例如RecipeDto。将其映射到您的Recipe实体,以便您可以手动将MultipartFile转换为字节数组。

编辑:org.springframework.web.multipart.MultipartFile#getBytes可能会为您执行此操作。


关于DTO:

https://stackoverflow.com/questions/35078383/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

英文:

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: https://stackoverflow.com/questions/47476733/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:
https://stackoverflow.com/questions/35078383/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

huangapple
  • 本文由 发表于 2020年8月19日 03:51:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/63475703.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定