使用 springboot 、 jpa 、thymeleaf 将图像上传到数据库

问题描述 投票:0回答:3

我正在开发一个使用 spring boot、jpa 和 thymeleaf 的简单应用程序,我需要将图像上传到我的数据库,但是当我单击页面上的“提交”时,除图像字段之外的字段都会插入到数据库中。我阅读了网站上的不同帖子,但没有一个真正接近我的问题,我不知道为什么它不将文件插入数据库。我不得不说位于菜谱实体中的图像字段

控制器

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

查看页面

<!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 is);


}

实体

@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

错误

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

enter image description here

GitHub 链接

java spring spring-boot jpa
3个回答
2
投票

让我们看看百里香叶片段

upload picture <input type="file" th:name="image">

以及我们收到的错误消息:

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">

名称

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

一种方法可能是:

步骤 I. 将

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

upload picture <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, // 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;
    }

补充说明:

  • 看看 Sfg 如何处理图像上传并在 教程存储库中显示它
  • 最好将
    MultiPartFile
    Byte[]
    转换移动到单独的服务(请参阅 Sfg 的存储库/教程)

编辑:

回答评论中的问题: 我不使用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; // auto unboxing
    }

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

如果您知道菜谱 ID,只需输入

localhost:8080/recipe/<recipe id>/recipeimage


0
投票

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

将 th:name 更改为输入字段中的名称。

关于您的类型错误

也许这可以帮助您:使用 Thymeleaf 将文件上传到 @ModelAttribute

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

编辑:org.springframework.web.multipart.MultipartFile#getBytes 可能会为你做到这一点


关于DTO: Spring框架中的DAO、DTO和Service层是什么?

https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application


0
投票
// You can even make it better using an HTML file!
            
 
            
            @Table("photo") public class Photo { public Photo(Integer id, String fileName) { this.id = id; this.fileName = fileName; } public Photo() { } @Id private Integer id; @NotEmpty private String fileName; private String contentType; @JsonIgnore private byte[] data; // Getters and Setters } 
            

            
// Make sure your repository extends from CrudRepository:
            

            public interface PhotoRepository extends CrudRepository<Photo, Integer> { List<Photo> findPhotoByFileName(String filename); }


// Here are the services for handling the file system and photo operations:
            
             
            @Service public class PhotoService{ private final PhotoRepository photoRepository; public PhotoService(PhotoRepository photoRepository) { this.photoRepository = photoRepository; } public Collection<Photo> get() { return (Collection<Photo>) photoRepository.findAll(); } public Photo get(Integer id) { return photoRepository.findById(id).orElse(null); } public void remove(Integer id) { photoRepository.deleteById(id); } public Photo save(String fileName, String contentType, byte[] data) { Photo photo = new Photo(); photo.setContentType(contentType); photo.setFileName(fileName); photo.setData(data); photoRepository.save(photo); return photo; } } 
            
          
            
// Here are the controllers for download and general photo operations:
                        
            @RestController public class DownloadController { private final PhotoService photoService; public DownloadController(PhotoService photoService) { this.photoService = photoService; } @GetMapping("/download/{id}") public ResponseEntity<byte[]> download(@PathVariable Integer id) { Photo photo = photoService.get(id); if (photo == null) { throw new ResponseStatusException(HttpStatus.NOT_FOUND); } byte[] data = photo.getData(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.valueOf(photo.getContentType())); ContentDisposition build = ContentDisposition .builder("inline") .filename(photo.getFileName()) .build(); headers.setContentDisposition(build); return new ResponseEntity<>(data, headers, HttpStatus.OK); } } 
            
            @RestController public class PhotoController { private final PhotoService photoService; public PhotoController(PhotoService photoService) { this.photoService = photoService; } @GetMapping("/") public String hello() { return "Hello World!"; } @GetMapping("/photo") public Collection<Photo> get() { return photoService.get(); } @GetMapping("/photo/{id}") public Photo get(@PathVariable Integer id) { Photo photo = photoService.get(id); if (photo == null) throw new ResponseStatusException(HttpStatus.NOT_FOUND); return photo; } @DeleteMapping("/photo/{id}") public void delete(@PathVariable Integer id) { photoService.remove(id); } @PostMapping("/photo") public Photo create(@RequestPart("data") MultipartFile file) throws IOException { return photoService.save(file.getOriginalFilename(), file.getContentType(), file.getBytes()); } } </pre>
            
            
// Here’s a simple HTML file for uploading images:
            
         
<!DOCTYPE html> 
<html lang="en"> <head> <meta charset="UTF-8"></head> 
<body><input id="fileupload" type="file" name="fileupload"/> <button id="upload-button" onclick="uploadFile()">Upload</button> <script> async function uploadFile() { let formData = new FormData(); formData.append("data", fileupload.files[0]); await fetch('http://localhost:8080/photo', { method: "POST", body: formData }).then(result => result.text()).then(text => alert(text)); } </script> </body> </html> 
            
 // Finally, here’s the SQL schema for the database:
            
CREATE TABLE photo 
    ( id BIGINT PRIMARY KEY AUTO_INCREMENT, file_name VARCHAR(255), content_type VARCHAR(255), data BINARY(1000000) ); 
 
© www.soinside.com 2019 - 2024. All rights reserved.