无法在 Spring 中将 MultipartFile 转换为 Blob

Posted

技术标签:

【中文标题】无法在 Spring 中将 MultipartFile 转换为 Blob【英文标题】:Cannot convert MultipartFile into Blob in Spring 【发布时间】:2020-04-11 03:17:58 【问题描述】:

我正在尝试将上传的文件另存为 mysql 记录中的 Blob。我是春天的新手。当我上传文件后要保存记录时,当我的POST方法updateCandidate()执行时,我得到了这个异常:

字段“cv”上的对象“candidateForm”中的字段错误:拒绝值 [org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@59c09df6];代码 [typeMismatch.candidateForm.cv,typeMismatch.cv,typeMismatch.java.sql.Blob,typeMismatch];参数 [org.springframework.context.support.DefaultMessageSourceResolvable:代码 [candidateForm.cv,cv];论据 [];默认消息 [cv]];默认消息 [无法将类型“org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile”的属性值转换为属性“cv”所需的类型“java.sql.Blob”;嵌套异常是 java.lang.IllegalStateException:无法将类型“org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile”的值转换为属性“cv”所需的类型“java.sql.Blob”:没有匹配的编辑器或转换找到策略]

出了什么问题?如何解决?

我的实体:

import java.sql.Blob;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;

@Entity
public class Candidate 

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
@Column(unique = true)
private String ssn;
private String name;
private String surname;
private String technology;
private String media;
@Lob
private Blob cv;
private boolean activeCV;

public Long getId() 
    return id;

public void setId(Long id) 
    this.id = id;

public String getSsn() 
    return ssn;

public void setSsn(String ssn) 
    this.ssn = ssn;

public String getName() 
    return name;

public void setName(String name) 
    this.name = name;

public String getSurname() 
    return surname;

public void setSurname(String surname) 
    this.surname = surname;

public String getTechnology() 
    return technology;

public void setTechnology(String technology) 
    this.technology = technology;

public String getMedia() 
    return media;

public void setMedia(String media) 
    this.media = media;

public Blob getCv() 
    return cv;

public void setCv(Blob cv) 
    this.cv = cv;

public boolean isActiveCV() 
    return activeCV;

public void setActiveCV(boolean activeCV) 
    this.activeCV = activeCV;



为我服务:

@Autowired
private CandidateRepository repository;

...

public Optional<Candidate> getCandidate(Long id)
    return repository.findById(id);


public void addOrUpdateCandidate(Candidate candidate) 
    repository.save(candidate);

在我的控制器中:

@Controller
@RequestMapping("/candidates")
public class CandidateController 

@Autowired
private EntityManagerFactory emf;

@Autowired
private CandidateService service;

...

@GetMapping("/updateCandidate/id")
public String showUpdateUserForm(@PathVariable("id") Long id, Model model) 
    Candidate candidate = service.getCandidate(id).get();
    model.addAttribute("candidateForm", candidate);
    return "updateCandidateForm";


@PostMapping("/updateCandidate/updateCandidateResult")
public String updateCandidate(@ModelAttribute("candidateForm") Candidate candidate, @RequestParam("cv") MultipartFile file) throws IOException 
    InputStream iStream = file.getInputStream();
    long size = file.getSize();
    Session session = emf.unwrap(Session.class);
    Blob cv = Hibernate.getLobCreator(session).createBlob(iStream, size);
    candidate.setCv(cv);
    service.addOrUpdateCandidate(candidate);
    return "updateCandidateResult";


我的 updateCandidateForm.jsp:

<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<html>
    <body>

        <form:form method="POST" action="updateCandidateResult" modelAttribute="candidateForm" enctype="multipart/form-data">
        <form:hidden path="id"/>
         <table>
            <tr>
                <td><form:label path="name">Name</form:label></td>
                <td><form:input path="name"/></td>
            </tr>
            <tr>
                <td><form:label path="surname">Surname</form:label></td>
                <td><form:input path="surname"/></td>
            </tr>
            <tr>
                <td><form:label path="ssn">SSN</form:label></td>
                <td><form:input path="ssn"/></td>
            </tr>
            <tr>
                <td><form:label path="technology">Known Technology</form:label></td>
                <td><form:input path="technology"/></td>
            </tr>
            <tr>
                <td><form:label path="media">Found us on</form:label></td>
                <td><form:input path="media"/></td>
            </tr>
            <tr>
                <td><form:label path="cv">Select a cv</form:label></td>
                <td><input type="file" name="cv" /></td>
            </tr>
            <tr>
                <td><form:label path="activeCV">Active CV</form:label></td>
                <td><form:checkbox path="activeCV" /></td>
            </tr>
            <tr>
                <td><input type="submit" value="Submit"/></td>
            </tr>
        </table>
    </form:form>
</body>

在我的 POM 中:

<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

编辑 1(问题): 是否有办法阻止 Spring 在“提交时间”尝试将 MultipartFile 转换为 Blob,从而更快地触发此操作,让 POST 方法已经管理候选对象(已设置 Blob 字段)对象?

编辑 2: 正如 JB Nizet 所建议的那样,我尝试使用具有 MultipartFile 类型字段 CV 的支持 POJO 来临时存储我通过表单(文本字段 + 文件字段)发布的内容,并且我不再得到那个异常,因为在“提交时间”填充的对象具有上传文件类型的 cv 字段:

import org.springframework.web.multipart.MultipartFile;

public class CandidatePOJO 

private Long id;
private String ssn;
private String name;
private String surname;
private String technology;
private String media;
private MultipartFile cv;
private boolean activeCV;

@Override
public String toString() 
    return "CandidatePOJO [id=" + id + ", ssn=" + ssn + ", name=" + name + ", surname=" + surname + ", technology="
            + technology + ", media=" + media + ", cv=" + cv + ", activeCV=" + activeCV + "]";

public Long getId() 
    return id;

public void setId(Long id) 
    this.id = id;

public String getSsn() 
    return ssn;

public void setSsn(String ssn) 
    this.ssn = ssn;

public String getName() 
    return name;

public void setName(String name) 
    this.name = name;

public String getSurname() 
    return surname;

public void setSurname(String surname) 
    this.surname = surname;

public String getTechnology() 
    return technology;

public void setTechnology(String technology) 
    this.technology = technology;

public String getMedia() 
    return media;

public void setMedia(String media) 
    this.media = media;

public MultipartFile getCv() 
    return cv;

public void setCv(MultipartFile cv) 
    this.cv = cv;

public boolean isActiveCV() 
    return activeCV;

public void setActiveCV(boolean activeCV) 
    this.activeCV = activeCV;


在控制器中,现在我首先关心的是看是否可以正确实例化 pojo,所以我的 GET-POST 对是:

@GetMapping("/updateCandidate/id")
public String showUpdateUserForm(@PathVariable("id") Long id, Model model) 
    CandidatePOJO candidatePOJO = new CandidatePOJO();
    candidatePOJO.setId(id);
    model.addAttribute("candidateForm", candidatePOJO);
    return "updateCandidateForm";


@PostMapping("/updateCandidate/updateCandidateResult")
public String updateCandidate(@ModelAttribute("candidateForm") CandidatePOJO candidatePOJO) 
    System.out.println("CANDIDATE POJO");
    System.out.println(candidatePOJO.toString());   // here I notice id = null

    /* MultipartFile to Blob conversion */
    //  MultipartFile file = candidatePOJO.getCv();
    //  InputStream iStream = file.getInputStream();
    //  long size = file.getSize();
    //  Session session = emf.unwrap(Session.class);
    //  Blob cv = Hibernate.getLobCreator(session).createBlob(iStream, size);

    /* instantiating the entity object to be freezed in db */
    // Candidate candidate = new Candidate();
    // set all data from candidatePOJO..
    //      candidate.setCv(cv);

    //  service.addOrUpdateCandidate(candidate);
    return "updateCandidateResult";

我得到了一个没有设置 id 的 CandidatePOJO 对象。我无法将 id 从 GET 传递到 POST。有谁知道怎么回事?

编辑 3: 很多天后,我选择了支持 POJO 的解决方案,令人难以置信的是,我注意到 ID 是从 GET 方法传递到 POST 方法的(我没有改变任何东西,我只是像我一样执行了经典的 Maven 项目清理当我发布我的问题时)。不幸的是,我现在面临另一个异常(当然,在恢复之前在 POST 方法中注释的代码之后):

javax.persistence.PersistenceException: Hibernate 无法将 EntityManagerFactory 解包为 'org.hibernate.Session'

如何解决?

编辑 4: 上面的异常解决了替换:

Session session = emf.unwrap(Session.class);

与:

EntityManager em = emf.createEntityManager();
Session session = (Session) em.getDelegate();

【问题讨论】:

这个异常说明了一切:你的方法有一个 Candidate 作为参数,因此 Spring 应该从请求的参数中填充它的属性。但它无法从您发送的多部分文件中填充 Blob 类型的属性。只是不要将您的持久实体用于您的 CandidateForm。使用专用类,它代表客户端发送的内容,而不是数据库行包含的内容。 我无法使用 Blob 作为该字段类型。因此,如果我做得好,它会在 POST 开始执行时尝试转换,并在无法执行时停止,因此它甚至无法详细说明我的编码转换(?)。你能写一些代码吗? :) 创建一个类。一个简单的 POJO,对于应该由表单发送的每个参数以及适当的类型都有 getter 和 setter。使用该类而不是候选者作为您的命令。将此命令对象中的数据复制到您的实体,应用必要的转换。 你好@JBNizet,正如你所说,我使用了 POJO。如果我编码正确,系统会抛出同样的异常,这证实了我认为不是对象类型(实体 bean、POJO)会造成混淆。如果确实我编码错误,我想弄清楚什么是正确的做法。我想向您展示我是如何编辑我的 GET 和 POST 的。我不知道如何打开这里的聊天框。请原谅我在英语方面可能出现的错误。谢谢 好吧,我遗漏了一些东西。现在它部分工作。我可以让系统实例化候选 POJO(cv 类型不是 Blob 而是 MultipartFile),但它没有设置 id,所以我的 POST 检索了一个未设置字段的对象 【参考方案1】:

通过使用支持 POJO,我终于解决了。事实上,我之前已经通过代码解决了。帖子中的代码是正确的(EDIT 2)。当我经常进行Maven项目清理时,并没有发现错误。我在最后几个小时内执行了它,令人难以置信的是我的代码有效。我猜不出魔法 :D 我不知道到底发生了什么。通过代码,我以两种不同的方式解决了问题。第一:

@Autowired
private EntityManagerFactory emf;

// ........

@GetMapping("/updateCandidate/id")
public String showUpdateUserForm(@PathVariable("id") Long id, Model model) 
    Candidate candidate = service.getCandidate(id).get();

    CandidatePOJO candidatePOJO = new CandidatePOJO();
    candidatePOJO.setId(id);
    candidatePOJO.setName(candidate.getName());
    candidatePOJO.setSurname(candidate.getSurname());
    candidatePOJO.setSsn(candidate.getSsn());
    candidatePOJO.setMedia(candidate.getMedia());
    candidatePOJO.setTechnology(candidate.getTechnology());
    candidatePOJO.setActiveCV(candidate.isActiveCV());

    model.addAttribute("candidateForm", candidatePOJO);
    return "updateCandidateForm";


@PostMapping("/updateCandidate/updateCandidateResult")
public String updateCandidate(@ModelAttribute("candidateForm") CandidatePOJO candidatePOJO) throws IOException 
    MultipartFile file = candidatePOJO.getCv();
    InputStream iStream = file.getInputStream();
    long size = file.getSize();
    EntityManager em = emf.createEntityManager();
    Session session = (Session) em.getDelegate();
    Blob cv = Hibernate.getLobCreator(session).createBlob(iStream, size);

    Candidate candidate = new Candidate();
    candidate.setId(candidatePOJO.getId());
    candidate.setName(candidatePOJO.getName());
    candidate.setSurname(candidatePOJO.getSurname());
    candidate.setSsn(candidatePOJO.getSsn());
    candidate.setMedia(candidatePOJO.getMedia());
    candidate.setTechnology(candidatePOJO.getTechnology());
    candidate.setActiveCV(candidatePOJO.isActiveCV());
    candidate.setCv(cv);

    service.addOrUpdateCandidate(candidate);
    return "updateCandidateResult";

第二个(使用相同的 GET):

@PostMapping("/updateCandidate/updateCandidateResult")
public String updateCandidate(@ModelAttribute("candidateForm") CandidatePOJO candidatePOJO) throws IOException, SerialException, SQLException 
    MultipartFile file = candidatePOJO.getCv();
    Blob cv = new SerialBlob(file.getBytes());

    Candidate candidate = new Candidate();
    candidate.setId(candidatePOJO.getId());
    candidate.setName(candidatePOJO.getName());
    candidate.setSurname(candidatePOJO.getSurname());
    candidate.setSsn(candidatePOJO.getSsn());
    candidate.setMedia(candidatePOJO.getMedia());
    candidate.setTechnology(candidatePOJO.getTechnology());
    candidate.setActiveCV(candidatePOJO.isActiveCV());
    candidate.setCv(cv);

    service.addOrUpdateCandidate(candidate);
    return "updateCandidateResult";

作为 Spring 新手,我还不知道其中的区别。我将不胜感激。此外,我迟早要修改此代码,以摆脱对 POJO 的支持并仅使用实体对象:如果有人能解决我最初的问题,我将永远感激不尽!

【讨论】:

以上是关于无法在 Spring 中将 MultipartFile 转换为 Blob的主要内容,如果未能解决你的问题,请参考以下文章

无法在 Spring Boot 中将 ProblemHandler 设置为 ObjectMapper

无法使用 Hibernate 在 Spring Boot 中将 MongoDB 设置为自动递增

Spring Boot 应用程序无法在 Docker 中将事件发布到 Kafka

无法在 Spring Data Cloud Spanner 中将 java.sql.Timestamp 转换为 com.google.cloud.Timestamp

Spring rest 控制器“MultipartFile[] multipartFile”总是接收 [] 或 null 文件

如何在 Spring Boot 中将属性注入测试类?