无法在 Spring 中将 MultipartFile 转换为 Blob

Cannot convert MultipartFile into Blob in Spring

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

Field error in object 'candidateForm' on field 'cv': rejected value [org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@59c09df6]; codes [typeMismatch.candidateForm.cv,typeMismatch.cv,typeMismatch.java.sql.Blob,typeMismatch]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [candidateForm.cv,cv]; arguments []; default message [cv]]; default message [Failed to convert property value of type 'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile' to required type 'java.sql.Blob' for property 'cv'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile' to required type 'java.sql.Blob' for property 'cv': no matching editors or conversion strategy found]

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

我的实体:

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 转换为 "Submit-time" 处的 Blob,从而更快地触发此操作,让 POST 方法已经管理了 Candidate(已经设置了 Blob 字段) ) 对象?

编辑 2: 正如 JB Nizet 所建议的,我尝试使用支持 POJO,它具有 MultipartFile 类型的字段 CV,通过表单(文本字段 + 文件字段)临时存储我 post 的内容,但我没有得到那个异常了,因为在 "Submit-time" 处填充的对象具有上传文件类型的 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;
}
}

controller中,现在我首先关心的是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 编辑我的问题时所做的那样清洁)。不幸的是,我现在面临另一个异常(当然,在恢复之前在 POST 方法中注释的代码之后):

javax.persistence.PersistenceException: Hibernate cannot unwrap EntityManagerFactory as 'org.hibernate.Session'

如何解决?

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

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

与:

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

BY USING SUPPORT POJO,我终于解决了。确实我已经通过代码解决了。 post 中的代码是正确的 (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 的支持并仅使用实体对象:如果有人能解决我最初的问题,我将永远感激不已!