2013-12-14 7 views
12

Sto sviluppando un'applicazione di gestione documenti in spring utilizzando jpa e MySQL. L'applicazione sta attualmente accettando un documento e i relativi metadati da un modulo Web utente createOrUpdateDocumentForm.jsp nel controller DocumentController.java. Tuttavia, i dati non si stanno facendo strada nel database MySQL. Qualcuno può mostrarmi come modificare il mio codice in modo che il documento e i suoi metadati vengano memorizzati nel database sottostante?documento che non salva in primavera jpa document manager applicazione

Il flusso dei dati (compreso il documento pdf) sembra passare attraverso i seguenti oggetti:

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 

Vorrei riassumere parti rilevanti di ognuno di questi oggetti come segue:

Il jsp innesca la seguente metodo 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}"; 
    } 
} 

Quando invio un documento tramite il modulo web nel jsp alla controller, i comandi System.out.println() nell'output controller codice di seguito, che indicano che i dati sono in realtà sempre inviati al server:

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

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

} 

Il ClinicService.java codice che viene chiamato dal DocumentController è:

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 relativo codice in 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);} 
} 

Infine, le parti pertinenti del codice SQL che crea il database includono:

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

Quali cambiamenti faccio a questo codice in modo che salva il document nella tabella documents del database MySQL utilizzando jpa?

+0

Presumo che non ci siano errori e che gli altri dati sono impegnati al DB? –

+0

@ user2310289 Non ho visto errori nella console di eclipse durante questa operazione. Un altro modulo in questa applicazione esegue il commit dei dati per aggiungere una persona al DB, ma il modulo aggiungi documento non esegue il commit di alcun dato. L'unico errore si verifica durante il caricamento dell'app nel server tomcat, ma l'app viene caricata e quindi il codice nella domanda corrente viene eseguito senza generare alcun errore. Se ritieni che l'errore che si verifica durante il caricamento dell'applicazione sia pertinente, puoi visualizzarlo in questo post: http://stackoverflow.com/questions/20622746/exception-loading-sessions-from-persistent-storage – CodeMed

+0

"The solo errore arriva durante il caricamento dell'app nel server tomcat "Wich is? Inoltre, il tuo modulo è jsp con questo attributo: 'enctype =" multipart/form-data "' –

risposta

3

Le mappature JPA sembrano buone. Ovviamente, @Lob richiede che il tipo di dati sia byte []/Byte []/o java.sql.Blob. Sulla base di questo, oltre ai tuoi sintomi e alla stampa di debug sembra che il tuo codice esegua la corretta manipolazione dei dati (annotazioni JPA buone), ma la combinazione di spring + MySQL non è impegnativa. Questo suggerisce un piccolo problema con la tua primavera transazionale o con il tuo tipo di dati MySQL.

1. transazionale Comportamento

Il codice rilevante in 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);} 
} 
  • Non stai usando EJB (quindi no 'automatica' transazioni gestite dal contenitore).
  • Si sta utilizzando JPA nelle classi Servlets/java (quindi è necessaria la demarcazione della transazione 'manuale' - all'esterno del servlet container, nel codice o tramite Spring config).
  • si sta iniettando il gestore di entità tramite @PersistenceContext (cioè entity manager gestito dal contenitore sostenuta da JTA, non un gestore di risorse locale transazione Entità, em.getTransaction())
  • Hai segnato il tuo metodo di 'genitori' come @Transactional (cioè proprietaria primavera trascrizioni - annotazione più tardi standardizzata in Java EE 7).

Le annotazioni e il codice devono dare un comportamento transazionale. Hai una Spring configurata correttamente per le transazioni JTA? (Utilizzando JtaTransactionManager, non DataSourceTransactionManager che dà driver JDBC transazioni locali) XML primavera dovrebbe contenere qualcosa di molto simile a:

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

sospettare di parametri aggiuntivi/impostazioni.

Questa è la versione codificata manualmente di ciò che Spring deve fare (per capire solo - non codificarlo). Utilizza UserTransaction (JTA), non em.getTransaction() di tipo EntityTransaction JDBC (locale):

// 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 Tipo di dati

Come mostrato here (at bottom), MySql macchie sono un po 'speciale rispetto a altri database. I vari Blobs ei loro capacità massime di stoccaggio sono:

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

Se (2) risulta essere il problema:

  • aumento il tipo MySQL per MEDIUMBLOB o LONGBLOB
  • indagare il motivo per cui non è stato visualizzato un messaggio di errore (v importante). La tua registrazione è stata configurata correttamente? Hai controllato i log?
+0

Non funziona, perché ha già '@ Transactional' gestendo le sue transazioni –

+0

Grazie. Non ho visto quel frammento di primavera. Avere una risposta modificata –

+0

Thé Lob non è un problema, se lo rimuove, avrà ancora il problema. Lo snippet è se cerca di ottenere la sessione da entityManager e usarlo per salvare l'oggetto, si otterrà un errore e un'eccezione . –

3

Non sono un esperto di Hibernate con annotazioni (l'ho usato dal 2004, ma con la configurazione XML). Ad ogni modo, sto pensando che stai mescolando le annotazioni in modo errato. Hai indicato che non desideri che il campo rimanga invariato con @Transient, ma hai anche affermato che si tratta di un valore @Lob, il che implica che vuoi mantenerlo inalterato. Sembra che @Lob stia vincendo e Hibernate stia cercando di risolvere il campo in una colonna usando il nome del campo.

Decolla @Lob e penso che sarai impostato.

+0

+1 Grazie per aver segnalato una possibile soluzione. Ho provato il tuo suggerimento e ha esposto un altro errore in business-config.xml. Ho aggiunto collegamenti allo stack trace e business-config.xml come modifica al mio post originale sopra. Potete per favore guardarci dentro per trovare l'errore? Non riesco a capire se la tua risposta risolve il mio problema fino a quando non riesco a far funzionare l'applicazione. – CodeMed

+0

L'ho fatto funzionare senza, l'errore era con la transazione. È stato condiviso –

1

Questa non è una risposta diretta alla tua domanda (scusa ma non sono un fan dell'ibernazione quindi non posso aiutarti), ma dovresti considerare l'utilizzo di un database NoSQL come MongoDB piuttosto che MySQL per un lavoro come questo. Ho provato entrambi e il database NoSQL è molto più adatto a questo tipo di requisiti.

Si scoprirà che in situazioni come questa funziona molto meglio di quanto MySQL possa fare e SpringData MongoDB consente di salvare e caricare molto facilmente oggetti Java che vengono automaticamente associati a quelli di MongoDB.

5

@ CodeMed, mi ci è voluto un po ', ma sono riuscito a riprodurre il problema.Potrebbe trattarsi di un problema di configurazione: @PersistenceContext potrebbe essere scansionato due volte, potrebbe essere scansionato dal tuo contesto di root e dal tuo contesto web. Ciò causa la condivisione dello @PersistenceContext, pertanto non sta salvando i dati (Spring non lo consente). Ho trovato strano che nessun messaggio o registro dove visualizzato. se si è tentato questo frammento di codice di seguito su Risparmi (documento Document) si vedrà l'errore effettivo:

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

Per risolvere il problema, è possibile effettuare le seguenti operazioni (evitare il @PersistenceContext da sottoporre a scansione due volte):

1- assicurarsi che tutti il ​​controller sono in un pacchetto separato come com.mycompany.myapp.controller, e nella vostra web-contesto utilizzare il componente-scan come <context:component-scan annotation-config="true" base-package="com.mycompany.myapp.controller" />

2- assicurarsi che gli altri componenti sono in package differenti diverso da quello del pacchetto di controllo , ad esempio: com.mycompany.myapp.dao, com.mycompany.myapp.service .... e quindi nella directory principale del contesto utilizzare il componente-scan come <context:component-scan annotation-config="true" base-package="com.mycompany.myapp.service, com.mycompany.myapp.dao" />

O mostrami le tue configurazioni primavera xml e la vostra web.xml, io puntare a destra direzione

+0

Per rendere più chiaro, il file di documento (Lob) non è il problema. Ho rimosso tutti gli attributi tranne l'ID e il nome, non ero ancora in grado di salvarlo. La modifica alla configurazione ha funzionato. Ho aggiunto gli attributi, tutti vengono salvati senza alcun problema. –

+0

+1 grazie. qualcun altro ha scritto un'intera app e l'ha caricata su github. Avrei diviso la taglia in alcuni modi, ma S.O. non mi ha dato l'interfaccia con cui farlo. Sembra che tu abbia ottenuto finora 4 upvotes. Grazie ancora. – CodeMed

2

ha creato l'applicazione di esempio che prenderà il file e lo memorizzerà nel database mysql, le piccole informazioni che memorizzano il file come usando blob/clob sono molto vecchie. Tutti i database aggiornati per archiviarli in formato array di byte, questo esempio può essere utile al tuo requisito .

tecnologie utilizzate: primavera 3.2 + ibernazione 4.x

github Link: https://github.com/uttesh/SpringHibernateUploader

Problemi correlati