2013-12-14 15 views
12

Ich entwickle eine Dokument-Management-Anwendung in spring mit jpa und MySQL. Die Anwendung akzeptiert derzeit ein Dokument und seine Metadaten von einem Benutzer-Webformular createOrUpdateDocumentForm.jsp in den Controller DocumentController.java. Die Daten gelangen jedoch nicht in die MySQL-Datenbank. Kann mir jemand zeigen, wie ich meinen Code ändern kann, damit das Dokument und seine Metadaten in der zugrunde liegenden Datenbank gespeichert werden?Dokument nicht speichern im Frühjahr jpa Dokument-Manager-Anwendung

Der Fluss der Daten (einschließlich der PDF-Dokument) über die folgenden Objekte zu gehen scheint:

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 

I relevanten Teile jedes dieser Objekte zusammenfassen wie folgt:

Die jsp der Auslöser folgende Verfahren in DocumentController.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}"; 
    } 
} 

Wenn ich einreichen ein Dokument über das Web-Formular im jsp mit dem controller die System.out.println() Befehle im controller Codeausgabe der folgenden, die anzeigen, dass die Daten in der Tat an den Server gesendet zu werden:

----------- 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: [[email protected] 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 

Das Document.java Modell ist:

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

} 

The ClinicService.java-Code, der von der DocumentController ist aufgerufen:

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

Th e entsprechenden Code in JpaDocumentRepository.java ist:

@PersistenceContext 
private EntityManager em; 

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

die relevanten Teile des SQL-Code schließlich, die die Datenbank enthalten erstellt:

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

Welche Änderungen kann ich an diesem Code machen, so dass es speichert die document in der documents Tabelle der MySQL Datenbank mit jpa?

+0

Ich gehe davon aus, dass es keine Fehler, und dass andere Daten sind auf die DB begangen? –

+0

@ user2310289 Ich habe während dieser Operation keine Fehler in der Eclipse-Konsole gesehen. Ein anderes Modul in dieser Anwendung schreibt Daten zum Hinzufügen einer Person zur DB fest, aber das Modul zum Hinzufügen von Dokumenten schreibt keine Daten fest. Der einzige Fehler tritt beim Laden der App in Tomcat Server, aber die App lädt und dann der Code in der aktuellen Frage ausgeführt wird, ohne einen Fehler zu werfen. Wenn Sie denken, dass der Fehler beim Laden der Anwendung relevant ist, können Sie ihn in diesem Beitrag betrachten: http://stackoverflow.com/questions/20622746/exception-loading-sessions-from-persistent-storage – CodeMed

+0

"The Beim Laden der App in den Tomcat Server kommt nur ein Fehler hinzu "Was ist das? Auch, sind Ihre Form auf Jsp mit diesem Attribut: 'enctype =" multipart/form-data "' –

Antwort

3

Ihre JPA-Mappings scheinen gut zu sein. Offensichtlich benötigt @Lob den Datentyp byte []/Byte []/oder java.sql.Blob. Basierend darauf, sowie Ihren Symptomen und Debugging-Ausdruck scheint es, dass Ihr Code die korrekte Datenmanipulation durchführt (JPA-Anmerkungen gut), aber die Kombination von spring + MySQL ist nicht bindend. Dies deutet auf ein kleines Problem mit Ihrer Spring-Transaktionskonfiguration ODER mit Ihrem MySQL-Datentyp hin.

1. Transaktionales Verhalten

Der entsprechende Code in JpaDocumentRepository.java ist:

@PersistenceContext 
private EntityManager em; 

@Override 
public void save(Document document) { 
    if (document.getId() == null) {this.em.persist(document);} 
    else {this.em.merge(document);} 
} 
  • Sie sind nicht EJBs (also keine 'automatische' mit Container-verwaltete Transaktionen).
  • Sie verwenden JPA in Servlets/Java-Klassen (daher benötigen Sie 'manuelle' Transaktionsabgrenzung - außerhalb des Servlet-Containers; in Ihrem Code oder über die Spring-Konfiguration).
  • Sie Injizieren des EntityManager über @PersistenceContext (dh Container verwaltete Manager Einheit von JTA gesichert, keine Ressource-local-Manager Entity Transaktion, em.getTransaction())
  • Sie haben die 'Eltern' Methode als @Transactional (dh Feder proprietärer gekennzeichnet Transkationen - Annotation später standardisiert in Java EE 7).

Die Anmerkungen und der Code sollten Transaktionsverhalten geben. Haben Sie eine Spring korrekt für JTA-Transaktionen konfiguriert? (Mit JtaTransactionManager, nicht DataSourceTransactionManager die lokale Transaktionen JDBC-Treiber gibt) Spring-XML sollte etwas sehr ähnlich enthalten:

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

verdächtig zusätzlicher Parameter/Einstellungen.

Dies ist die manuell codierte Version dessen, was Spring tun muss (nur zum Verständnis - nicht codieren). Verwendet Usertransaction (JTA), nicht em.getTransaction() vom Typ EntityTransaction (JDBC local):

// 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 Datentyp

Wie here (at bottom) gezeigt, sind MySql Blobs etwas Besonderes im Vergleich zu andere Datenbanken. Die verschiedenen Blobs und ihre maximale Speicherkapazitäten sind:

TINYBLOB - 255 Byte BLOB - 65535 Bytes MEDIUMBLOB - 16.777.215 Bytes (2^24 - 1) LONGBLOB - 4G Bytes (2^32 - 1)

Wenn (2) stellt sich heraus, um Ihr Problem zu sein:

  • Erhöhung der MySQL-Typ oder
  • LONGBLOB MEDIUMBLOB
  • untersuchen, warum Sie nicht eine Fehlermeldung angezeigt hat (v wichtig). Wurde Ihre Protokollierung ordnungsgemäß konfiguriert? Hast du Protokolle überprüft?
+0

Wont Arbeit, weil er bereits '@ Transactional' verwaltet seine Transaktionen –

+0

Vielen Dank. Habe diesen Frühlingsschnipsel nicht gesehen. Habe die Antwort geändert. –

+0

Thé Lob ist nicht das Problem, wenn er es entfernt, wird er immer noch das Problem haben. Das Snippet ist, wenn er versucht, die Sitzung aus dem EntityManager und verwenden Sie es, um das Objekt zu speichern, erhalten Sie einen Fehler und eine Ausnahme . –

3

Ich bin kein Hibernate-mit-Annotations-Experte (ich benutze es seit 2004, aber mit XML-Konfiguration). Wie auch immer, ich denke, dass Sie Annotationen falsch mischen. Sie haben angegeben, dass das file Feld nicht mit @Transient beibehalten werden soll, aber Sie haben auch gesagt, dass es ein @Lob ist, was bedeutet, dass Sie es beibehalten möchten. Sieht so aus, als ob @Lob gewinnt und Hibernate versucht, das Feld mithilfe des Feldnamens in eine Spalte aufzulösen.

Nehmen Sie die @Lob und ich denke, dass Sie eingestellt werden.

+0

+1 Vielen Dank, dass Sie auf eine mögliche Lösung hingewiesen haben. Ich habe Ihren Vorschlag ausprobiert und einen weiteren Fehler in der Datei business-config.xml aufgedeckt. Ich habe Links zu dem Stack-Trace und der Datei business-config.xml als Bearbeitung zu meinem ursprünglichen Beitrag oben hinzugefügt. Können Sie bitte den Fehler finden? Ich kann nicht sagen, ob Ihre Antwort mein Problem behebt, bis die Anwendung ausgeführt wird. – CodeMed

+0

Ich habe es funktioniert, ohne dass der Fehler bei der Transaktion war. Es wurde geteilt. –

1

Dies ist keine direkte Antwort auf Ihre Frage (sorry, aber ich bin kein Fan von Ruhezustand, kann Ihnen also nicht wirklich helfen), aber Sie sollten eine NoSQL-Datenbank wie MongoDB anstelle von MySQL für einen Job in Betracht ziehen so was. Ich habe beide ausprobiert und die NoSQL-Datenbanken sind viel besser zu dieser Art von Anforderung passen.

Sie werden feststellen, dass es in Situationen wie diesem viel besser funktioniert als MySQL und mit SpringData MongoDB können Sie sehr einfach Java-Objekte speichern und laden, die automatisch MongoDB-Objekten zugeordnet werden.

5

@CodeMed, es dauerte eine Weile, aber ich konnte das Problem reproduzieren.Es könnte ein Konfigurationsproblem sein: @PersistenceContext könnte zweimal gescannt werden, es könnte von Ihrem Root-Kontext und Ihrem Web-Kontext gescannt werden. Dies führt dazu, dass die @PersistenceContext freigegeben wird, daher werden Ihre Daten nicht gespeichert (Spring erlaubt das nicht). Ich fand es komisch, dass keine Nachrichten oder Protokolle angezeigt wurden. wenn Sie diesen Schnipsel unten anprobiert Sie Save (Document Dokument) Sie den tatsächlichen Fehler sehen:

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

Um das Problem zu lösen, können Sie folgendes tun (vermeiden Sie die @PersistenceContext werden zweimal gescannt):

1- Vergewissern Sie sich, dass alle Controller in einem separaten Paket sind wie com.mycompany.myapp.controller, und in Ihrem Web-Kontext, um die Komponente-Scan als <context:component-scan annotation-config="true" base-package="com.mycompany.myapp.controller" />

2- Achten Sie darauf verwenden, die andere Komponente als die Controller-Paket in differents Paket andere sind zum Beispiel: com.mycompany.myapp.dao, com.mycompany.myapp.service .... und dann in Ihrem Root-Kontext verwenden, um die Komponente-Scan als <context:component-scan annotation-config="true" base-package="com.mycompany.myapp.service, com.mycompany.myapp.dao" />

Oder zeigen Sie mir deins Frühling xml Konfigurationen und Ihre web.xml, werde ich Sie in die richtige Richtung

+0

Um es klar zu machen, ist die Dokumentdatei (Lob) selbst nicht das Problem. Ich entfernte alle Attribute außer der Id und dem Namen, ich konnte es immer noch nicht speichern. Die Konfigurationsänderung hat es funktioniert. Ich habe die Attribute hinzugefügt, alle werden ohne Probleme gespeichert. –

+0

+1 danke. jemand anderes hat eine ganze App geschrieben und sie auf GitHub hochgeladen. Ich hätte das Kopfgeld ein paar Wege geteilt, aber S.O. gab mir nicht die Schnittstelle, um das zu tun. Sieht so aus, als hättest du bisher 4 Upvotes bekommen. Danke nochmal. – CodeMed

2

erstellt die Beispielanwendung, die die Datei und speichert in MySQL-Datenbank, wenig Info Speichern von Datei wie die Verwendung von Blob/Clob ist sehr alte Art und Weise zu tun.now alle Datenbanken aktualisiert, um im Byte-Array-Format speichern, kann diese Probe hilfreich für Ihre Anforderung .

Technologien verwendet: spring 3.2 + 4.x

Github Link zu überwintern: https://github.com/uttesh/SpringHibernateUploader

Verwandte Themen