文档未保存在 Spring jpa 文档管理器应用程序中

Posted

技术标签:

【中文标题】文档未保存在 Spring jpa 文档管理器应用程序中【英文标题】:document not saving in spring jpa document manager application 【发布时间】:2014-01-02 10:10:19 【问题描述】:

我正在使用jpamysqlspring 中开发一个文档管理应用程序。该应用程序当前正在接受来自用户 Web 表单 createOrUpdateDocumentForm.jsp 的文档及其元数据到控制器 DocumentController.java。但是,数据并未进入MySQL 数据库。有人可以告诉我如何更改我的代码,以便将文档及其元数据存储在底层数据库中吗?

数据流(包括pdf文档)似乎要经过以下对象:

createOrUpdateDocumentForm.jsp  //omitted for brevity, since it is sending data to controller (see below)
Document.java  
DocumentController.java  
ClinicService.java
JpaDocumentRepository.java
The MySQL database  

我将每个对象的相关部分总结如下:

jspDocumentController.java 中触发以下方法:

@RequestMapping(value = "/patients/patientId/documents/new", headers = "content-type=multipart/*", method = RequestMethod.POST)
public String processCreationForm(@ModelAttribute("document") Document document, BindingResult result, SessionStatus status, @RequestParam("file") final MultipartFile file) 
    document.setCreated();
    byte[] contents;
    Blob blob = null;
    try 
        contents = file.getBytes();
        blob = new SerialBlob(contents);
     catch (IOException e) e.printStackTrace();
    catch (SerialException e) e.printStackTrace();
    catch (SQLException e) e.printStackTrace();
    document.setContent(blob);
    document.setContentType(file.getContentType());
    document.setFileName(file.getOriginalFilename());
    System.out.println("----------- document.getContentType() is: "+document.getContentType());
    System.out.println("----------- document.getCreated() is: "+document.getCreated());
    System.out.println("----------- document.getDescription() is: "+document.getDescription());
    System.out.println("----------- document.getFileName() is: "+document.getFileName());
    System.out.println("----------- document.getId() is: "+document.getId());
    System.out.println("----------- document.getName() is: "+document.getName());
    System.out.println("----------- document.getPatient() is: "+document.getPatient());
    System.out.println("----------- document.getType() is: "+document.getType());        
    try System.out.println("[[[[BLOB LENGTH IS: "+document.getContent().length()+"]]]]");
    catch (SQLException e) e.printStackTrace();
    new DocumentValidator().validate(document, result);
    if (result.hasErrors()) 
        System.out.println("result.getFieldErrors() is: "+result.getFieldErrors());
        return "documents/createOrUpdateDocumentForm";
    
    else 
        this.clinicService.saveDocument(document);
        status.setComplete();
        return "redirect:/patients?patientID=patientId";
    

当我通过jsp 中的web 表单向controller 提交文档时,System.out.println() 代码中的System.out.println() 命令输出以下内容,这表明数据实际上正在发送到服务器:

----------- document.getContentType() is: application/pdf
----------- document.getCreated() is: 2013-12-16
----------- document.getDescription() is: paper
----------- document.getFileName() is: apaper.pdf
----------- document.getId() is: null
----------- document.getName() is: apaper
----------- document.getPatient() is: [Patient@564434f7 id = 1, new = false, lastName = 'Frank', firstName = 'George', middleinitial = 'B', sex = 'Male', dateofbirth = 2000-11-28T16:00:00.000-08:00, race = 'caucasian']
----------- document.getType() is: ScannedPatientForms
[[[[BLOB LENGTH IS: 712238]]]]  //This indicates the file content was converted to blob

Document.java 模型是:

@Entity
@Table(name = "documents")
public class Document 
    @Id
    @GeneratedValue
    @Column(name="id")
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "client_id")
    private Patient patient;

    @ManyToOne
    @JoinColumn(name = "type_id")
    private DocumentType type;

    @Column(name="name")
    private String name;

    @Column(name="description")
    private String description;

    @Column(name="filename")
    private String filename;

    @Column(name="content")
    @Lob
    private Blob content;

    @Column(name="content_type")
    private String contentType;

    @Column(name = "created")
    private Date created;

    public Integer getId()return id;
    public void setId(Integer i)id=i;

    protected void setPatient(Patient patient) this.patient = patient;
    public Patient getPatient()return this.patient;

    public void setType(DocumentType type) this.type = type;
    public DocumentType getType() return this.type;

    public String getName()return name;
    public void setName(String nm)name=nm;

    public String getDescription()return description;
    public void setDescription(String desc)description=desc;

    public String getFileName()return filename;
    public void setFileName(String fn)filename=fn;

    public Blob getContent()return content;
    public void setContent(Blob ct)content=ct;

    public String getContentType()return contentType;
    public void setContentType(String ctype)contentType=ctype;

    public void setCreated()created=new java.sql.Date(System.currentTimeMillis());
    public Date getCreated() return this.created;

    @Override
    public String toString() return this.getName();
    public boolean isNew() return (this.id == null);


DocumentController 调用的ClinicService.java 代码是:

private DocumentRepository documentRepository;
private PatientRepository patientRepository;

@Autowired
public ClinicServiceImpl(DocumentRepository documentRepository, PatientRepository patientRepository) 
    this.documentRepository = documentRepository;
    this.patientRepository = patientRepository;


@Override
@Transactional
public void saveDocument(Document doc) throws DataAccessException documentRepository.save(doc);

JpaDocumentRepository.java中的相关代码为:

@PersistenceContext
private EntityManager em;

@Override
public void save(Document document) 
    if (document.getId() == null) this.em.persist(document);
    else this.em.merge(document);
  

最后,创建数据库的SQL代码的相关部分包括:

CREATE TABLE IF NOT EXISTS documenttypes (
  id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(80),
  INDEX(name)
);

CREATE TABLE IF NOT EXISTS patients (
  id INT(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  first_name VARCHAR(30),
  middle_initial VARCHAR(5), 
  last_name VARCHAR(30),
  sex VARCHAR(20), 
  date_of_birth DATE,
  race VARCHAR(30), 
  INDEX(last_name)
);

CREATE TABLE IF NOT EXISTS documents (
  id int(11) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  client_id int(4) UNSIGNED NOT NULL,
  type_id INT(4) UNSIGNED, 
  name varchar(200) NOT NULL,
  description text NOT NULL,
  filename varchar(200) NOT NULL,
  content mediumblob NOT NULL, 
  content_type varchar(255) NOT NULL,
  created timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (client_id) REFERENCES patients(id),
  FOREIGN KEY (type_id) REFERENCES documenttypes(id)
);  

我对此代码做了哪些更改,以便它使用jpadocument 保存在MySQL 数据库的documents 表中?

【问题讨论】:

我假设没有错误并且其他数据已提交到数据库? @user2310289 在此操作期间,我没有在 Eclipse 控制台中看到错误。此应用程序中的另一个模块确实提交数据以将人员添加到数据库,但添加文档模块不提交任何数据。唯一的错误是在 tomcat 服务器中加载应用程序时出现的,但应用程序确实加载了,然后当前问题中的代码运行而没有抛出任何错误。如果您认为在加载应用程序期间发生的错误是相关的,您可以在此帖子中查看它:***.com/questions/20622746/… “唯一的错误是在 tomcat 服务器中加载应用程序时出现的” 哪个是?另外,您在 jsp 上的表单是否具有此属性:enctype="multipart/form-data" 在尝试给出答案之前,我想请您发送em.flush() 并告诉我们这是否有效。 另外,您能否指定您正在使用的 JPA 提供程序,Hibernate?同时,作为一个假设,我建议你尝试这样的@Lob 组合:你可以使用byte[] 类型而不是Blob 类型。如果不想加载这个,可以添加注解@Basic(fetch = FetchType.LAZY)。所以一切看起来像:@Column(name="content") @Lob @Basic(fetch = FetchType.LAZY) private byte[] content; 【参考方案1】:

@CodeMed,我花了一段时间,但我能够重现该问题。这可能是一个配置问题:@PersistenceContext 可能会被扫描两次,它可能会被您的根上下文和 Web 上下文扫描。这会导致@PersistenceContext 被共享,因此它不会保存您的数据(Spring 不允许这样做)。我觉得奇怪的是没有显示任何消息或日志。如果您在 Save(Document document) 上尝试了下面的这个 sn-p,您将看到实际错误:

Session session = this.em.unwrap(Session.class);
session.persist(document);

要解决该问题,您可以执行以下操作(避免@PersistenceContext 被扫描两次):

1- 确保您的所有控制器都在一个单独的包中,例如 com.mycompany.myapp.controller,并在您的网络上下文中使用组件扫描作为 <context:component-scan annotation-config="true" base-package="com.mycompany.myapp.controller" />

2- 确保其他组件位于控制器包以外的不同包中,例如:com.mycompany.myapp.daocom.mycompany.myapp.service .... 然后在您的根上下文中使用组件扫描作为 <context:component-scan annotation-config="true" base-package="com.mycompany.myapp.service, com.mycompany.myapp.dao" />

或者给我看你的spring xml配置和你的web.xml,我会给你指出正确的方向

【讨论】:

明确一点,文档文件(Lob)本身不是问题。我删除了除 ID 和名称之外的所有属性,但我仍然无法保存它。配置更改使其工作。我添加了属性,所有属性都被保存了,没有任何问题。 +1 谢谢。其他人编写了一个完整的应用程序并将其上传到 github。我会以几种方式分配赏金,但是 S.O.没有给我执行此操作的界面。不过,到目前为止,您似乎获得了 4 个赞成票。再次感谢您。【参考方案2】:

您的 JPA 映射看起来不错。显然,@Lob 要求数据类型为 byte[] / Byte[] / 或 java.sql.Blob。基于此,加上您的症状和调试打印输出,您的代码似乎在进行正确的数据操作(JPA 注释很好),但是 spring + MySQL 的组合并没有提交。这表明您的 spring 事务配置或 MySQL 数据类型存在小问题。

1.交易行为

JpaDocumentRepository.java中的相关代码为:

@PersistenceContext
private EntityManager em;

@Override
public void save(Document document) 
    if (document.getId() == null) this.em.persist(document);
    else this.em.merge(document);
  
您没有使用 EJB(因此没有“自动”容器管理的事务)。 您在 Servlets/java 类中使用 JPA(因此您需要“手动”事务划分 - 在 servlet 容器之外;在您的代码中或通过 Spring 配置)。 您正在通过 @PersistenceContext 注入实体管理器(即由 JTA 支持的容器管理实体管理器,而不是实体管理器资源本地事务,em.getTransaction()) 您已将“父”方法标记为 @Transactional(即 spring 专有事务 - 注释后来在 Java EE 7 中标准化)。

注释和代码应该提供事务性行为。您是否为 JTA 事务正确配置了 Spring? (使用 JtaTransactionManager,而不是为 JDBC 驱动程序提供本地事务的 DataSourceTransactionManager)Spring XML 应该包含与以下内容非常相似的内容:

<!-- JTA requires a container-managed datasource -->
<jee:jndi-lookup id="jeedataSource" jndi-name="jdbc/mydbname"/> 

<!-- enable the configuration of transactional behavior based on annotations -->
<tx:annotation-driven transaction-manager="txManager"/>

<!-- a PlatformTransactionManager is still required -->
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" >
  <!-- (this dependency "jeedataSource" must be defined somewhere else) -->
  <property name="dataSource" ref="jeedataSource"/>  
</bean>

对其他参数/​​设置持怀疑态度。

这是 Spring 必须做的手动编码版本(仅供理解 - 不要编码)。使用 UserTransaction (JTA),而不是 EntityTransaction 类型的 em.getTransaction()(JDBC 本地):

// inject a reference to the servlet container JTA tx
@Resource UserTransaction jtaTx;

// servlet container-managed EM
@PersistenceContext private EntityManager em; 

public void save(Document document) 
    try 
        jtaTx.begin();
        try 
            if (document.getId() == null) this.em.persist(document);
            else this.em.merge(document);
            jtaTx.commit();
         catch (Exception e) 
             jtaTx.rollback();
             // do some error reporting / throw exception ...
        
     catch (Exception e) 
        // system error - handle exceptions from UserTransaction methods
        // ...
    

2。 MySQL 数据类型

如图here (at bottom) 所示,MySql Blob 与其他数据库相比有点特殊。各种 Blob 及其最大存储容量为:

TINYBLOB - 255 字节 BLOB - 65535 字节 MEDIUMBLOB - 16,777,215 字节 (2^24 - 1) LONGBLOB - 4G 字节 (2^32 – 1)

如果 (2) 是你的问题:

将 MySQL 类型增加到 MEDIUMBLOB 或 LONGBLOB 调查您没有看到错误消息的原因(v 重要)。您的日志记录是否正确配置?你检查过日志吗?

【讨论】:

行不通,因为他已经有@Transactional 管理他的交易 谢谢。没看到那个春天的sn-p。已修改答案。 Lob 不是问题,如果他删除它,他仍然会有问题。sn-p 是如果他尝试从 entityManager 获取会话并使用它来保存对象,你会得到一个错误和一个异常。 我同意似乎存在事务管理问题——即他可能正在运行 JDBC 本地事务,但 EM 是受管理的(由 JTA 支持)——因此回答第 (1) 部分。但我'同意不同意:)' LOB 是一个潜在的额外问题。 64kB 对于 PDF 来说相当小 - 因此回答第 (2) 部分。 您的第 1 部分可能会解决问题,我没有尝试过,但我认为他不需要那么多配置,我设法让它在没有所有配置的情况下工作,而只是 entityManagerFactory bean。第 2 部分与当前问题无关。他根本没有保存,也没有错误日志。如果是空间问题,我很确定代码会抛出异常【参考方案3】:

我不是 Hibernate-with-annotations 专家(我从 2004 年开始使用它,但使用 XML 配置)。无论如何,我认为您错误地混合了注释。您已经表明您不希望 file 字段与 @Transient 保持一致,但您还说它是 @Lob,这意味着您确实希望它保持不变。看起来@Lob 获胜,Hibernate 正在尝试使用字段名称将字段解析为列。

取下@Lob,我想你会准备好的。

【讨论】:

+1 感谢您指出可能的解决方案。我尝试了您的建议,但它在 business-config.xml 中暴露了另一个错误。我添加了指向堆栈跟踪和 business-config.xml 的链接,作为对上面原始帖子的编辑。你能看看它找出错误吗?在我运行应用程序之前,我无法判断您的回答是否能解决我的问题。 我没有那个就让它工作,错误在于交易。已分享。【参考方案4】:

这不是您问题的直接答案(抱歉,我不是 hibernate 的粉丝,所以无法真正帮助您)但是您应该考虑使用 NoSQL 数据库(例如 MongoDB)而不是 MySQL 来完成类似的工作这。我都试过了,NoSQL 数据库更适合这种要求。

您会发现在这种情况下,它的性能比 MySQL 好得多,而且 SpringData MongoDB 允许您非常轻松地保存和加载自动映射到 MongoDB 的 Java 对象。

【讨论】:

以上是关于文档未保存在 Spring jpa 文档管理器应用程序中的主要内容,如果未能解决你的问题,请参考以下文章

存储资源管理器未列出 Cosmos DB 文档

Spring Data JPA 参考文档二

保存对象时未填充 Spring Boot JPA@CreatedDate @LastModifiedDate

关闭应用程序后删除 Spring JPA 数据

使用啥事务管理器? (JPA,春季)

GraphQL 文档资源管理器未加载我的架构