View Full Version : [JAVA/DB file-based] Implementazione di un'applicazione che utilizza un DB.
LacioDromBuonViaggio
27-04-2009, 11:12
Come da titolo, vorrei fare un programmino semplicissimo che utilizza un database file-based (il database è presente su un file che può essere copiato e scarrozzato ovunque).
Detto questo ho un problema serio di implementazione dovuto alle mie scarse esperienze nella programmazione JAVA-DB {con PHP/MySQL risolverei in 30minuti}.
Ho un database con una semplice tabella contenente degli acquisti (per ora) e devo richiamare le informazioni sul programma, ma come faccio?
IPOTESI 1: Creare una classe 'acquisto' avente come variabili private i campi della tabella e i relativi metodi setVariabile()/getVariabile() per poterle modificare. Poi per caricare il database dovrei creare tante istanze { new Acquisto() } quante sono le righe della tabella?
IPOTESI 2: Anche con JAVA è possibile leggere e scrivere sul database 'seduta stante', quindi basterebbero delle funzioni statiche e dei link agli ID {stile Browser} ?
Putroppo non ho mai visto del codice di un programma che utilizzasse un DB quindi non so proprio come fare..
Non ho ancora scritto nemmeno una riga di codice perchè prima voglio farmi uno schema di quello che andrò a fare (visto che dovrò preparare anche l'interfaccia).
Poi se qualcuno conosce un programmino opensource che fa più o meno la stessa cosa mi piacerebbe esaminarlo, il miglior modo è sempre quello.. :help:
Entrambe le ipotesi sono realistiche, nel senso che si può fare l'uno e l'altro. Il "come" varia a seconda della tecnologia che si vuole usare. Esistono framework di persistenza come hibernate, esistono le API persistence che si appoggiano a framework di persistenza e c'è il package sql nudo e crudo.
La scelta può dipendere dall'esistenza o meno di un supporto JDBC per il database da usare.
Che database usi?
LacioDromBuonViaggio
27-04-2009, 14:11
Entrambe le ipotesi sono realistiche, nel senso che si può fare l'uno e l'altro. Il "come" varia a seconda della tecnologia che si vuole usare. Esistono framework di persistenza come hibernate, esistono le API persistence che si appoggiano a framework di persistenza e c'è il package sql nudo e crudo.
La scelta può dipendere dall'esistenza o meno di un supporto JDBC per il database da usare.
Che database usi?
Tempo fa mi venne consigliato di usare H2 (visto che comunque è SQL e si porta dietro 1MB di jar) dato che ho bisogno di poter copiare e il database per potermelo portare ovunque. Per ora comunque non ho ancora scritto una riga di codice in quanto voglio prima avere bene in mente come operare.
Io vorrei fare una semplice interfaccia con dei campi di testo che una volta compilati vengano inseriti tramite query nel database e viceversa: caricare da database un elenco di 'acquisti' e poterci cliccare sopra in modo da vederne i dettagli. Tutto qua, ma non ho la più pallida idea di come poterlo implementare attraverso la programmazione orientata agli oggetti: che classi utilizzo, per fare che cosa? {Forse essendo troppo abituato a PHP/MySQL non riesco a vedere la cosa da un punto di vista diverso}
H2 supporta le api J2EE persistence. Bene.
Tutorial superespresso alle JPA.
set-up
Scarichi le librerie toplink essentials.
https://glassfish.dev.java.net/downloads/persistence/JavaPersistence.html
Una volta che hai queste librerie quello che puoi fare è fregartene di connessioni e disconessioni e query e lavorare direttamente in Java.
Funziona come nella tua ipotesi 1. Crei una classe che aggrega i dati che infileresti in una riga di una tabella (o in più tabelle correlate, è irrilevante).
Diciamo che, esempio tipico, il dato aggregato è una persona e che le persone hanno un nome. La rappresentazione di questo dato in Java con l'uso della api persistence è:
import javax.persistence.*;
@Entity
public class Persona {
@Id
private int id;
private String name;
public int getId() { return id; }
public String getName() { return name; }
public void setId(int id) { this.id = id; }
public void setName(String name) { this.name = name; }
@Override public int hashCode() { return id; }
@Override public boolean equals(Object that) {
return that instanceof Persona ? ((Persona)that).id == id : false;
}
}
Un campo per ogni valore di cui almeno uno sia univoco, per ogni campo un getter e un setter. La classe è annotata con @Entity e il campo che identifica la chiave è annotato con @Id
Il database è rappresentato da un oggetto di tipo EntityManager. Ottieni un EntityManager con le seguenti istruzioni:
import javax.persistence.*;
...a un certo punto...
EntityManagerFactory factory = Persistence.createEntityManagerFactory("NOME");
EntityManager database = factory.createEntityManager();
Il nome in grassetto lo vediamo dopo.
Con un EntityManager puoi eseguire due tipi di operazioni. Operazioni sulle entità e comandi SQL. Quelle interessanti sono le prime perchè alla fine il tuo database è organizzato e gestito a colpi di oggetti Java.
Ad esempio il "salvataggio" di una persona ipotetica:
Persona p = new Persona();
p.setId(1);
p.setNome("Pippo");
si esegue con:
database.getTransaction().begin();
database.persist(p);
database.getTransaction().commit();
Puoi recuperare una persona conoscendone l'id:
Persona p = database.getReference(Persona.class, 1);
Puoi eseguire query SQL usando createNativeQuery(String) o eseguire query JSQL con createQuery. Le seconde restituiscono oggetti Java. Ad esempio una uqery JSQL per ottenere tutte le persone nel database sarebbe:
Query q = database.createQuer("SELECT p FROM Persona p");
for(Object o : q.getResultList()) {
Persona risultato = (Persona)o;
}
Il nome in grassetto.
La connessione con la base dati è gestita autonomamente dalle API persistence basandosi sul contenuto di un file properties.xml. Questo file deve essere contenuto in una directory di nome META-INF accessibile dal classpath (in pratica nella stessa cartella da cui esegui il programma o nella cartella META-INF del file jar se l'applicazione è impacchettata).
Il file persistece.xml contiene la definizione degli attributi necessari a creare delle unità di persistenza. L'unità di persistenza è quella roba che va a cercare il metodo Persistence.createEntityManagerFactory("NOME"); e "NOME" è appunto il nome dell'unità come dichiarato nel file persistence.xml.
Un esempio di file persistence.xml che permette la gestione di oggetti Persona su un database h2 con le api TopLink è questo:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="PersonaService" transaction-type="RESOURCE_LOCAL">
<class>Persona</class>
<properties>
<property name="toplink.jdbc.driver" value="org.h2.Driver"/>
<property name="toplink.jdbc.url" value="jdbc:h2:prova"/>
<property name="toplink.ddl-generation" value="create-tables"/>
</properties>
</persistence-unit>
</persistence>
In grassetto trovi il nome dell'unità di persistenza, che sarà quello usato dal metodo createEntityManagerFactory. Siccome il file persistence dice che l'unità di persistenza si chiama PersonaService per ottenerla dirò, nel codice:
EntityManagerFactory emf = Persistence.createEntityManagerFactory("PersonaService");
L'altra riga in grassetto è "class". Nel nodo che dichiara un'unità di persistenza occorre dichiarare il nome pienamente qualificato (cioè nomepackage.NomeClass) delle entità gestite dall'unità di persistenza. Noi abbiamo una sola entità, Persona, quindi abbiamo un solo nodo class.
L'ultima parte in evidenza è la stringa di connessione al database. La stringa dipende dal database, per H2 dire "jdbc:h2: prova" significa che si tratta di un database embedded di nome "prova" collocato nella directory di lavoro.
Fine del tutorial su JPA più breve mai realizzato :D.
LacioDromBuonViaggio
27-04-2009, 17:03
Ma quante ne sai? :ave: :ave: :ave:
Credo sia proprio quello di cui ho bisogno! Piuttosto, se volessi dare il tutto a un niubbo ancora più niubbo di me potrei metterglielo in una cartella con qualche file e un JAR dicendogli: "toh, esegui il JAR" ?
L'importante è che inserisca nel JAR tutte le librerie essenziali? Oppure l'importante è che siano nella cartella?
Vorrei evitare installazioni di librerie, modifica di classpath e sbattimenti vari che non siano di installare (ovviamente) la JVM classica..
:mano:
Domandina ricapitolativa: ma quindi quale sarebbe la differenza tra H2 e le JPA?
Altra domandina: oltre a JDK, H2 e JPA mi serve qualcos'altro?
La struttura tipica di un'applicazione java desktop non richiede alcun intervento dell'utente salvo scaricare uno zip che contiene il programma e fare doppio click sul jar che c'è nella cartella creata dalla decompressione di quello zip. Di solito ha questa "faccia":
[programma]/Programma.jar
[programma]/librerienative.dll
[programma]/lib/unjar.jar
[programma]/lib/unaltrojar.jar
[programma]/lib/eccetera.jar
Il collegamento tra Programma.jar e i jar che stanno in lib avviene tramite un attributo del file Manifest usato dal jar. Si usa l'attributo Class-Path del manifesto, che potrebbe avere questa forma:
Manifest-Version: 1.0
Main-Class: quel che è
Class-Path: lib/toplink-essentials-agent.jar lib/toplink-essentials.jar lib/h2-1.1.111.jar
Il file jar ha una sua cartella META-INF e lì dentro ci infileresti il file persistence.xml.
Alla fine ti trovi quindi con un jar, quello del tuo programma, e una cartella lib che contiene gli altri quattro jar. Niente classpath o altre diavolerie.
Volendo potresti anche fondere i quattro jar in un unico jar e avere solo il tuo Programma.jar. Si tratta di file zip, non è un gran problema, basta solo "fondere" i file Manifest.mf che trovi nelle cartelle META-INF di ogni file jar in un unico Manifest da usare poi per il tuo jar.
In tutto ti servono 3 librerie, toplink-essentials.jar, toplink-essentials-agent.jar e h2-1.1.111.jar, più il file persistence.xml da mettere nella cartella META-INF del jar del tuo programma.
LacioDromBuonViaggio
27-04-2009, 17:43
Ottimo, spero di non fare casini {cosa molto probabile}! Tanto inizio con programmini 'giocattolo' di prova..
:sperem:
LacioDromBuonViaggio
28-04-2009, 11:39
Domandina veloce.
Sto scaricando JAVA EE 5 SDK da qui (http://java.sun.com/javaee/downloads/index.jsp) {il terzo}. Le JPA saranno incluse nel pacchetto, o dovrò scaricarle a parte?
Sono incluse ma non so dove siano (se in j2ee.jar o da qualche altra parte. Propendo per l'altra parte).
LacioDromBuonViaggio
12-05-2009, 15:37
Ho dei problemi nel capire come funziona l'accesso al database.
Per poter accedere al database ho bisogno di user e password, ma questi dati chi li decide e quando?
L'utente utilizzatore non dev'essere informato di questi dati, il programma seleziona il database (file creato e modificato in precedenza dallo stesso programma) e lo apre.
{Questo è quello che serve a me. }
Poi ho visto qualche esempio di user e password salvati nel database, ma in questo caso penso ci si riferisca a programmi multi-utenti, con vari gradi di accesso e privilegi. {E non mi interessa}
Solitamente username e password sono gestiti dal modulo di autenticazione del sistema che è controllato dall'amministratore del sistema (cioè dall'utente che ha i privilegi di amministrazione).
Il modulo contiene un elenco protetto di nomi utente con relative password.
Il modulo accetta delle richieste di autenticazione. Significa che io persona qualsiasi posso richiedere al modulo tramite una connessione non autenticata di garantirmi l'accesso agli altri moduli dell'applicazione. Per farlo fornisco al modulo le mie credenziali (nome utente e password). Il modulo di autenticazione le verifica e se l'esito della verifica è positivo mette a mia disposizione il resto dei servizi del programma.
Tutto 'sto giro richiede l'esistenza di una "configurazione iniziale" vale a dire che deve esserci almeno un nome utente ed una password preimpostata - solitamente quella di amministrazione che è introdotta nel sistema dal programma che installa il software.
Detto questo la domanda che mi sorge è: stai realizzando un'applicazione in cui esiste un sistema di servizi fisicamente separati dal client che li usa oppure è una classica applicazione desktop "all in one"?
Perchè nel secondo caso la faccenda è più ostica. Il concetto di autenticazione si basa sull'indisponibilità da parte dell'utente del modulo di autenticazione (e del contenuto a cui questo accede).
Se manca questa separazione la storia dei meccanismi di protezione ci dice che il massimo che puoi fare è ostacolare in misura più o meno grave l'accesso dell'utente alle informazioni di autenticazione. In pratica puoi benissimo scrivere nome utente e password direttamente nel codice.
LacioDromBuonViaggio
12-05-2009, 19:22
Se manca questa separazione la storia dei meccanismi di protezione ci dice che il massimo che puoi fare è ostacolare in misura più o meno grave l'accesso dell'utente alle informazioni di autenticazione. In pratica puoi benissimo scrivere nome utente e password direttamente nel codice.
Il programma accede al database in un solo modo (non ci sono utenti amministratori e/o classi di utenza). Quindi scrivo nome utente nel codice e sono apposto. Ogni database creato (seppur diverso) avrà lo stesso user e la stessa password di accesso.. E così?
LacioDromBuonViaggio
13-05-2009, 10:18
esatto.
Grazie, ma tanto tutti questi vaneggi non credo mi serviranno con le JPA.
Ho un problemino con il persistence.xml.
Premetto che la struttura della cartella è:
Persona.java (uguale alla tua se non che ho inserito il costruttore per velocizzare)
GestioneDB.java (inserisce Persone su DB e legge il DB)
lib (contenente le 3 librerie)
META-INF (contenente il persistence.xml)
Nessun errore in compilazione, ma quando creo una nuova istanza di GestioneDB mi genera questa eccezione:
javax.persistence.PersistenceException: No resource files named META-INF/services/javax.persistence.spi.PersistenceProvider were found. Please make sure that the persistence provider jar file is in your classpath.
at javax.persistence.Persistence.findAllProviders(Persistence.java:167)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:103)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:83)
at GestioneDB.<init>(GestioneDB.java:14)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
at bluej.runtime.ExecServer$3.run(ExecServer.java:814)
alla riga EntityManagerFactory factory = Persistence.createEntityManagerFactory("PersonaService");
Uso BlueJ e affinchè mi vedesse le 3 librerie le ho dovute aggiungere nelle preferenze (altrimenti mi dava errore sull'import), però questa cartella non è una libreria quindi non saprei come aggiungerla.
Creando un JAR del progetto ho visto che crea in automatico la cartella META-INF con all'interno il manifest.mf.
Quindi come glielo faccio vedere 'sto file .xml? Devo per forza creare il jar ogni volta che voglio fare un test?
Oppure ho capito male dove deve andare il file persistence.xml?
Oppure non è quello il problema!
EDIT -- non mi funziona nemmeno se crea un jar e lo apro da terminale.
Ho cercato un pò su google e un tipo che usa NetBeans ha risolto aggiungendo le 2 librerie toplink-essential tramite il comando 'Add Libraries'! Ma io l'avevo già fatto ma non mi funziona lo stesso....
Non è il tuo file persistence che non vede ma il servizio di persistenza di toplink. Questo file qui:
"META-INF/services/javax.persistence.spi.PersistenceProvider"
si trova nel file jar toplink-essentials.jar.
Direi che è un problema di classpath.
Verifica che nel manifest del jar del tuo programma sia specificata l'opzione Class-Path, che qui siano elencate le tre librerie che ti servano e che le stesse siano disponibili al jar (esternamente).
LacioDromBuonViaggio
13-05-2009, 13:23
Non è il tuo file persistence che non vede ma il servizio di persistenza di toplink. Questo file qui:
"META-INF/services/javax.persistence.spi.PersistenceProvider"
si trova nel file jar toplink-essentials.jar.
Direi che è un problema di classpath.
Verifica che nel manifest del jar del tuo programma sia specificata l'opzione Class-Path, che qui siano elencate le tre librerie che ti servano e che le stesse siano disponibili al jar (esternamente).
Si avevo individuato anche che era quel file (ho provato anche a scompattare tutta la libreria direttamente nella directory principale ma niente.
Quindi devo per forza creare il JAR ogni volta che faccio un minimo cambiamento? Non posso copiare 'temporaneamente' le librerie nella classpath--> dove?
Dove dipende dall'IDE che usi. Il classpath è una variabile tranquillamente impostabile all'atto dell'esecuzione/compilazione e di solito gli ide ne approfittano a mani basse creando cartelle e cartelline varie in cui uno deve poi immaginare di infilare le proprie librerie.
Ad esempio in Netbeans metteresti la cartella META-INF con dentro il file persistence.xml nella cartella "src" mentre i tre jar andrebbero aggiunti come librerie con l'apposito "click" su libraries. Dopodichè Netbeans si occupa di rendere disponibile l'ambaradam sia al compilatore che alla jvm.
Senza IDE, data la strutturazione che hai presentato nel post precedente, diresti semplicemente:
java -cp .;lib/* GestioneDB
"-cp" sta per "il classpath è", . è la directory di esecuzione, lib/* è la cartella lib che si trova nella directory di esecuzione e l'asterisco sta per "tutti i jar contenuti in".
LacioDromBuonViaggio
13-05-2009, 13:47
Forse ho fatto qualche passo avanti... forse
Ho creato il jar (togliendo la cartella lib e lasciando esterne le 3 librerie).
Ora ho una cartella contenente il jar del programma e le 3 librerie.
Il manifest.mf è apposto.
Al comando java -jar programma.jar però mi da un errore diverso:
Exception in thread "main" javax.persistence.PersistenceException: No Persistence provider for EntityManager named PersonaService: The following providers:
oracle.toplink.essentials.PersistenceProvider
oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider
Returned null to createEntityManagerFactory.
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:154)
at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:83)
at GestioneDB.<init>(GestioneDB.java:14)
at GestioneDB.main(GestioneDB.java:40)
Il file xml è nella cartella manifest, questo errore significa che ha trovato la classe main ma non trova il provider PersonaService dell'xml?
Oppure manca qualche tag nell'xml?
il tuo file persistence.xml com'è?
LacioDromBuonViaggio
13-05-2009, 15:05
il tuo file persistence.xml com'è?
Pari pari a quello che mi hai suggerito:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="PersonaService" transaction-type="RESOURCE_LOCAL">
<class>Persona</class>
<properties>
<property name="toplink.jdbc.driver" value="org.h2.Driver"/>
<property name="toplink.jdbc.url" value="jdbc:h2:db/persone"/>
<property name="toplink.ddl-generation" value="create-tables"/>
</properties>
</persistence-unit>
</persistence>
Ho cambiato solo le cose in grassetto...
In altri esempi sul web ho visto che certi persistence hanno il tag <provider> prima di quello <class>... Però magari non c'entra nulla..
Non è necessario specificare il provider con toplink.
Dev'essere un problema di percorsi.
Puoi incollare il percorso completo della cartella in cui si trova il tuo progetto? Se il percorso contiene dei nomi con degli spazi puoi provare a spostare il progetto in una cartella il cui percorso assoluto non contiene nomi con spazi (tipo "Document And Settings" ha degli spazi, "c:\prova" non ha spazi)
LacioDromBuonViaggio
13-05-2009, 17:23
Mi sta funzionando come dovrebbe, ho messo tutto in C:\prove e sta funzionando. Quindi probabilmente era un problema di percorsi.
Inoltre, pensando di fare il furbo, avevo aggiunto il costruttore alla classe Persona, ma mi sono reso conto che anche quello dava problemi e ora la classe Persona è uguale alla tua.
Ora ho questa classe:
import javax.persistence.*;
public class GestioneDB
{
public static void main (String argv[])
{
System.out.println("Main in esecuzione");
EntityManagerFactory factory = Persistence.createEntityManagerFactory("PersonaService");
System.out.println("Trovato PersonaService");
EntityManager database = factory.createEntityManager();
System.out.println("Creato il Manager");
//inserimento persona
Persona p = new Persona();
p.setId(3);
p.setName("Pippo");
database.getTransaction().begin();
database.persist(p);
database.getTransaction().commit();
//stampa tabella
Query q = database.createQuery("SELECT p FROM Persona p");
for(Object o : q.getResultList()) {
Persona res = (Persona)o;
System.out.println("ID: "+res.getId()+", Name: "+res.getName());
}
}
}
Alla prima istruzione in grassetto mi scrive queste info:
[TopLink Info]: 2009.05.13 04:58:56.265--ServerSession(18992192)--TopLink, version: Oracle TopLink Essentials - 2.1 (Build b60e-fcs (12/23/2008))
[TopLink Info]: 2009.05.13 04:58:56.437--Not able to detect platform for vendor name [H2]. Defaulting to oracle.toplink.essentials.platform.database.DatabasePlatform]. The database dialect used may not match with the database you are using. Please explicitly provide a platform using property toplink.platform.class.name.
[TopLink Info]: 2009.05.13 04:58:56.515--ServerSession(18992192)--file:/C:/Prove/jap/jap.jar-PersonaService login successful
[TopLink Warning]: 2009.05.13 04:58:56.546--ServerSession(18992192)--Exception [TOPLINK-4002] (Oracle TopLink Essentials - 2.1 (Build b60e-fcs (12/23/2008))): oracle.toplink.essentials.exceptions.DatabaseException Internal Exception: org.h2.jdbc.JdbcSQLException: Table PERSONA already exists;
SQL statement: CREATE TABLE PERSONA (ID NUMBER(10) NOT NULL, NAME VARCHAR(255), PRIMARY KEY (ID)) [42101-111]
Error Code: 42101 Call: CREATE TABLE PERSONA (ID NUMBER(10) NOT NULL, NAME VARCHAR(255), PRIMARY KEY (ID))
Query: DataModifyQuery()
Il primo ok!
Il secondo invece non lo capisco, che non trovi H2 e quindi utilizzi oracle di default (i 3 file .db del database me li crea tranquillamente).
Il terzo è confortante... (finalmente)
Il quarto è normale, ma si potrà evitare? (dirgli di non crearla se esiste già)
Altro 'problema' è che se runno due volte mi da una marea di errori perchè non può inserire un dato con una stessa primary key (giustamente).
Il metodo per inserire progressivamente gli ID, controllando l'ultimo, me lo inventerò in qualche modo, ma mi interessa come gestire questo tipo di eccezioni? E dove trovo tutte le eccezioni che potrebbero generarsi?
Un'altra cosa che mi sono chiesto riguarda la sintassi della query. Se scrivo 'SELECT * FROM Persona' non va, questo perchè, come mi hai detto quel metodo restituisce oggetti?
Nella query 'SELECT p FROM Persona p' quel Persona è riferito alla tabella oppure alla classe Persona?
PS. dai che piano piano stiamo andando avanti!
:yeah:
il secondo indica solo che il driver h2 non definisce una stringa per il produttore del database e toplink lo rimpiazza dicendo che è Oracle. (toplink è di Oracle).
Le query sono query JPQL, non SQL. Si possono anche fare query SQL (che il framework chiama native) ma l'esecuzione di una query SQL anzichè restituire oggetti Java restituisce vettori che contengono i dati nudi e crudi. Che può essere utile o no. Di solito no :D.
http://en.wikipedia.org/wiki/Java_Persistence_Query_Language
Info e Warning puoi tranquillamente lasciarli stare. GLi error sono quelli da tenere d'occhio. "create-table" nel file persistence crea le tabelle solo se non ci sono. Forse lo fa tentando la creazione e gestendo l'eccezione che il db sputa quando la tabella c'è già.
Le eccezioni sono documentate nelle api JPA. Tieni conto che per la maggior parte sono "wrapper" per problemi che si verificano in relazione alle query sul db.
LacioDromBuonViaggio
16-05-2009, 11:49
Sono alle prese con le eccezioni.
Giusto per la cronaca scrivo tutta quella serie di errori che mi da se provo a inserire un valore con una ID uguale.
Error Code: 23001
Call: INSERT INTO PERSONA (ID, NAME) VALUES (?, ?)
bind => [5, Pippo]
Query: InsertObjectQuery(Persona@5)
at oracle.toplink.essentials.internal.ejb.cmp3.transaction.base.EntityTransactionImpl.commit(EntityTransactionImpl.java:120)
at oracle.toplink.essentials.internal.ejb.cmp3.transaction.EntityTransactionImpl.commit(EntityTransactionImpl.java:60)
at GestioneDB.main(GestioneDB.java:21)
Caused by: Exception [TOPLINK-4002] (Oracle TopLink Essentials - 2.1 (Build b60e-fcs (12/23/2008))): oracle.toplink.essentials.exceptions.DatabaseException
Internal Exception: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: PRIMARY_KEY_2 ON PUBLIC.PERSONA(ID); SQL statement:
INSERT INTO PERSONA (ID, NAME) VALUES (?, ?) [23001-111]
Error Code: 23001
Call: INSERT INTO PERSONA (ID, NAME) VALUES (?, ?)
bind => [5, Pippo]
Query: InsertObjectQuery(Persona@5)
at oracle.toplink.essentials.exceptions.DatabaseException.sqlException(DatabaseException.java:311)
at oracle.toplink.essentials.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:654)
--- cut ---
at oracle.toplink.essentials.internal.ejb.cmp3.transaction.base.EntityTransactionImpl.commit(EntityTransactionImpl.java:102)
... 2 more
Caused by: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation:
PRIMARY_KEY_2 ON PUBLIC.PERSONA(ID); SQL statement:
INSERT INTO PERSONA (ID, NAME) VALUES (?, ?) [23001-111]
at org.h2.message.Message.getSQLException(Message.java:107)
at org.h2.message.Message.getSQLException(Message.java:118)
--- cut ---
at org.h2.jdbc.JdbcPreparedStatement.executeUpdate(JdbcPreparedStatement.java:128)
at oracle.toplink.essentials.internal.databaseaccess.DatabaseAccessor.executeDirectNoSelect(DatabaseAccessor.java:647)
... 35 more
In grassetto ho evidenziato le eccezioni! Visto che sono diverse (alcune toplink, altre di h2) mi chiedevo quali devo gestire in modo che il programma vada avanti (magari compare un alert e basta). Oppure il fatto che siano così tante dipende dal fatto che tutto parte dalla prima e le altre sono una conseguenza dovuta alla propagazione dell'eccezione?
Inoltre sono andato qui (http://java.sun.com/javaee/5/docs/api/index.html?javax/persistence/package-summary.html), non trovo la prima eccezione (DatabaseException), ma sicuramente ho sbagliato posto.
Probabilmente i wrapper di cui parlavi sono questi (http://kickjava.com/src/oracle/toplink/essentials/exceptions/DatabaseException.java.htm), però non so cosa importare e che cosa catturare.
Ho provato a mettere la creazione di una persona in un blocco try e a catturare una banalissima RuntimeException ( tanto tutte ereditano da lì, anche se così è troppo facile :p ) e mi segnala questo:
[TopLink Warning]: 2009.05.16 01:01:21.953--UnitOfWork(30472956)--Exception [TOPLINK-4002] (Oracle TopLink Essentials - 2.1 (Build b60e-fcs (12/23/2008))): oracle.toplink.essentials.exceptions.DatabaseException
Internal Exception: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: PRIMARY_KEY_2 ON PUBLIC.PERSONA(ID); SQL statement:
INSERT INTO PERSONA (ID, NAME) VALUES (?, ?) [23001-111]
Error Code: 23001
Call: INSERT INTO PERSONA (ID, NAME) VALUES (?, ?)
bind => [5, Pippo]
Query: InsertObjectQuery(Persona@5)
Eccezione catturata
javax.persistence.RollbackException: Exception [TOPLINK-4002] (Oracle TopLink Essentials - 2.1 (Build b60e-fcs (12/23/2008))): oracle.toplink.essentials.excepti
ons.DatabaseException
Internal Exception: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: PRIMARY_KEY_2 ON PUBLIC.PERSONA(ID); SQL statement:
INSERT INTO PERSONA (ID, NAME) VALUES (?, ?) [23001-111]
Error Code: 23001
Call: INSERT INTO PERSONA (ID, NAME) VALUES (?, ?)
bind => [5, Pippo]
Query: InsertObjectQuery(Persona@5)
ID: 1, Name: Pippo
ID: 2, Name: Pippo
ID: 3, Name: Pippo
ID: 4, Name: Pippo
ID: 5, Name: Pippo
Il programma termina correttamente (stampa il contenuto del DB), in grassetto ho evidenziato la stampa che scrivo dentro il blocco catch, però non capisco il significato delle altre eccezioni: RollbackException (questa è nuova) e poi quelle del JDBC!
Devi gestire unicamente le eccezioni il cui rilascio è dichiarato dal metodo che invochi.
Il problema con le jpa o con qualsiasi altro framework che includa un sottosistema extra java per l'esecuzione di comandi (in questo caso sql e il database sottostante) è che le eccezioni prodotte dal sottosistema non sono generalizzabili quindi devi accertarti che i tuoi enunciati sql non producano eccezioni prima di codificarli o resti in mutande (ingegneristicamente parlando :D).
LacioDromBuonViaggio
16-05-2009, 15:25
Devi gestire unicamente le eccezioni il cui rilascio è dichiarato dal metodo che invochi.
L'eccezione si propaga quando invoco il metodo commit():
database.getTransaction().commit();
Il problema con le jpa o con qualsiasi altro framework che includa un sottosistema extra java per l'esecuzione di comandi (in questo caso sql e il database sottostante) è che le eccezioni prodotte dal sottosistema non sono generalizzabili quindi devi accertarti che i tuoi enunciati sql non producano eccezioni prima di codificarli o resti in mutande (ingegneristicamente parlando :D).
Non so se ho capito bene. In pratica, in questo caso, dovrei far evitare al programma di inserire un ID già presente in tabella, piuttosto che gestire un'eventuale eccezione dovuta alla violazione di un vincolo.
esatto. Il problema è che non sappiamo perchè l'eccezione si verifichi perchè le informazioni sull'eccezione non sono documentate in commit (lo saranno probabilmente nelle API di h2 ma per noi sono totalmente trasparenti).
LacioDromBuonViaggio
18-05-2009, 12:24
Mi hanno consigliato di usare un file .mdb (Access) in quanto farebbe proprio al caso mio (tutto in un unico file, lo porterei dove voglio, ecc ecc). Però come posso controllare che supporti le JPA?
una passata di google dice che jpa dovrebbe funzionare anche con msaccess. Bisogna provare per verificarlo.
LacioDromBuonViaggio
18-05-2009, 15:05
una passata di google dice che jpa dovrebbe funzionare anche con msaccess. Bisogna provare per verificarlo.
Si, controllo sempre prima su google.
Per quanto riguarda JAVA, può interfacciarsi con un file mdb attraverso i Driver Jdbc-Odbc, soltanto che il file dev'essere già creato, o probabilmente io non ho capito come crearlo.
Ho utilizzato i 2 file .java in questa (http://blog.taragana.com/index.php/archive/access-microsoft-access-database-from-java-using-jdbc-odbc-bridge-sample-code/) pagina.
Però come posso verificare che le JPA siano compatibili?
Non so se si potrà sostituire quel driver con il driver H2 senza battere ciglio, inoltre non ho trovato nessuna libreria jar da importare per i file mdb..
EDIT -- Ho provato a sostituire le voci nel file persistence ma mi dice
Internal Exception: java.sql.SQLException: [Microsoft][Driver ODBC Microsoft Access] Impossibile trovare il file "(sconosciuto)".
che è lo stesso errore che mi dava runnando i 2 file sopracitati, a conferma che il file .mdb dev'essere già creato.
prova a sostituire il nome del driver nel file persistence.xml e vedi cosa capita. Che io sappia il file mdb è solo una parte del meccanismo del database access. Se non vado errato occorre impostare un'origine dati nel pannello di controllo (strumenti di amministrazione) di window. Può darsi però che mi sbagli, non sono un grande conoscitore di ms access.
LacioDromBuonViaggio
18-05-2009, 23:10
prova a sostituire il nome del driver nel file persistence.xml e vedi cosa capita. Che io sappia il file mdb è solo una parte del meccanismo del database access. Se non vado errato occorre impostare un'origine dati nel pannello di controllo (strumenti di amministrazione) di window. Può darsi però che mi sbagli, non sono un grande conoscitore di ms access.
Sono andato a controllare in pannello di controllo e ho trovato Origine dati (ODBC), allora ho creato un file mdb nella cartella del programma e, associando i driver e il relativo percorso, al persistence.xml ora mi trova il database.
Peccato che l'istruzione SQL di creazione della tabella persona non vada bene. Mi da questo errore:
[TopLink Warning]: 2009.05.18 11:02:59.043--ServerSession(18992192)--Exception [TOPLINK-4002] (Oracle TopLink Essentials - 2.1 (Build b60e-fcs (12/23/2008))): oracle.toplink.essentials.exceptions.DatabaseException
Internal Exception: java.sql.SQLException: [Microsoft][Driver ODBC Microsoft Access] Errore di sintassi nell'istruzione CREATE TABLE.
Error Code: -3551
Call: CREATE TABLE PERSONA (ID NUMBER(10) NOT NULL, NAME VARCHAR(255), PRIMARY KEY (ID))
Query: DataModifyQuery()
Se entro in Access e copio la query e provo a eseguirla mi da errore di sintassi anche lì.. Che Access abbia un SQL tutto suo?
In ogni caso questo cosa vorrebbe dire? Sono le JPA che creano la tabella a non essere compatibili col driver di MS Access? O magari ho creato un file mdb in modo errato.. :boh:
banryu79
19-05-2009, 09:07
Qui (http://www.blueclaw-db.com/create_table_access_sql.htm)trovi degli esempi di codice SQL per la CREATE TABLE in Access.
LacioDromBuonViaggio
19-05-2009, 10:49
Qui (http://www.blueclaw-db.com/create_table_access_sql.htm)trovi degli esempi di codice SQL per la CREATE TABLE in Access.
Grazie, il fatto è che la sintassi per la creazione non la decido io, ma le JPA che lo fanno in automatico. Ora bisognerebbe controllare se si può in qualche modo modificare questa sintassi automatica.
In questa query
CREATE TABLE PERSONA (ID NUMBER(10) NOT NULL, NAME VARCHAR(255), PRIMARY KEY (ID))
MS Access rileva l'errore subito dopo NUMBER, forse a significare che non riconosce quel tipo.
La soluzione potrebbe essere di impostare nel codice della classe un tipo compatibile con MS Access in modo che la query abbia esito positivo. Però con Access non mi fa vedere l'SQL di CREATE TABLE, quindi non so che tipi utilizza.
Resta il fatto che mi sono dovuto creare precedentemente un file .mdb vuoto. Ci sarà un modo per farlo creare dal programma? (Altrimenti dovrei portarmi dietro anche quello e copiarlo ogni volta che voglio creare un nuovo DB).
------ EDIT
Riguardo alle primary key di cui parlavamo tempo fa ho trovato questa pagina (http://www.oracle.com/technology/products/ias/toplink/jpa/howto/id-generation.html) molto interessante. Solamente che probabilmente H2 non permette sequenze particolari:
Exception Description: SEQ_GEN_IDENTITY: platform DatabasePlatform doesn't support NativeSequence.
Se infatti lascio @Id @GeneratedValue private int id; come se fosse in 'TABLE' funziona, ma ogni volta che runno di nuovo il programma gli ID salgono di 50 interi :eek:, avendo così una sequenza del genere:
prima sequenza di inserimento:
ID 1
ID 2
chiudo il programma, lo riapro e inserisco di nuovo:
ID 51
ID 52
Ho notato che si crea una tabella SEQUENCE per motivi suoi :boh: ! In ogni caso vorrei sapere dove devo mettere le istruzioni begin() persist() e commit(), se le metto tutte e tre in un metodo chiamato create (che inserisce due valori) e richiamo il metodo 2 volte consecutivamente ci sono dei malfunzionamenti (vengono aggiunte due righe soltanto tralaltro con gli indici invertiti).
LacioDromBuonViaggio
21-05-2009, 12:17
Ho risolto adottando una soluzione 'artigianale': prima di inserire una nuova riga cerca l'oggetto con ID più alto in modo che il nuovo abbia id_trovato+1.
Comunque mi è stato suggerito questo:
Puoi trattare gli oggetti creati inserendoli all'interno di una Collection in modo da facilitare la loro gestione.
Anche l'update, in tal senso, puòà risultare ancora più semplice poichè per impedire di accedere continuativamente al file system in scrittuta puoi "svuotare" il contenuto della collection nel file ed ottenere così la versione aggiornata.
Questo approccio ti consente:
1. La lettura da file la fai solo all'inizio (va tutto nella collection).
2. La scrittura viene fatta solo alla fine (nel file)
Puoi gestire tramite la classe e le collection i crtieri di ordinamento, comparazione e ricerca senza operare sui file e perderne in prestazioni.
E' una cosa molto interessante (anche se non è che ho questa grandissima mole di dati da gestire), però mi chiedevo come posso gestire le collection.
Una collection per ogni tipo di oggetto (Collection Persone -> class Persone)? In questo caso come gestisco le eventuali query di JOIN tra più tabelle?
Tra 1 e 2 c'è un terzo punto:
1.5: il programma termina in modo inatteso e l'utente perde tutti i dati elaborati nella sessione corrente.
Mica male come idea.
LacioDromBuonViaggio
21-05-2009, 16:04
Tra 1 e 2 c'è un terzo punto:
1.5: il programma termina in modo inatteso e l'utente perde tutti i dati elaborati nella sessione corrente.
Mica male come idea.
Vabbè in molti programmi se non si salva si perdono le modifiche. Però facciamo come dici tu (anche perchè l'ho già iniziato così :D )
Dato che H2 contiene il database in una cartella (cosa che non mi va molto bene), avrei pensato di fare così:
Il database (o meglio la cartella del DB) viene creato nella directory del programma (impostata dal file persistence.xml). Quando salvo e chiudo vorrei che la cartella contenente i file del database sia compressa in un semplice archivio e salvata in un posto a scelta dell'utente.
Quando riaprirò il programma richiamando il file compresso, la cartella dovrà essere decompressa (nella directory del programma) e il database aperto.
Questo comporta un problemino:
- La cartella del database (che risiede nella directory del programma) dev'essere temporanea: creata quando si decomprime l'archivio e cancellata una volta salvato il lavoro e chiuso il programma. JAVA permette la cancellazione di file dall'hard disk?
La risposta alla domanda "perchè cancellarla?" è proprio per il fatto che nel caso in cui il programma trovasse nella directory del programma un database già creato in precedenza si renderebbe conto che l'ultima sessione è terminata in modo anomalo e chiederebbe di ricaricare il database non salvato (un pò come il salvataggio automatico di word).
Quindi un altro problemino che non saprei come risolvere è:
- Come controllare se il database è già presente nella directory del programma? (si può usare l'eccezione o il warning 'TABELLA ALREADY EXIST'? Oppure con un eventuale tool di decompressione, che ancora non conosco, il quale notifica che il file è già presente?)
Adesso devo uscire di volata ma quando torno ti faccio vedere un database logger. Se non hai un numero eccessivo di dati da gestire (diciamo un terzo della RAM del sistema ospite) potrebbe essere la soluzione che cerchi.
LacioDromBuonViaggio
21-05-2009, 16:22
Adesso devo uscire di volata ma quando torno ti faccio vedere un database logger. Se non hai un numero eccessivo di dati da gestire (diciamo un terzo della RAM del sistema ospite) potrebbe essere la soluzione che cerchi.
Mi pare che mel'avevano già provato a consigliare, ma mi veniva un pò male perchè non potevo utilizzare query SQL..
Si può provare comunque.. Attendo! :oink:
Mi pare che mel'avevano già provato a consigliare, ma mi veniva un pò male perchè non potevo utilizzare query SQL..
Ah ecco. Non me lo ricordavo più. Sarà una punta di alzheimer o il fatto che ormai 'sti logger ho cercato di rifilarli un po' dappertutto :D. "I'm a strong believer in RAM" :D:
LacioDromBuonViaggio
25-05-2009, 11:49
Domanda sul file persistence.xml
Per ora ho questo con una sola entità:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="PersonaService" transaction-type="RESOURCE_LOCAL">
<class>Persona</class>
<properties>
<property name="toplink.jdbc.driver" value="org.h2.Driver"/>
<property name="toplink.jdbc.url" value="jdbc:h2:db/persone"/>
<property name="toplink.ddl-generation" value="create-tables"/>
</properties>
</persistence-unit>
</persistence>
Se volessi, per esempio, creare una nuova entità 'Impiego' dovrei aggiungere <class>Impiego</class> e basta?
Anche per quanto riguarda il codice, devo aggiungere qualche cosa in più per gestire anche Impiego?
Volendo mettere tutti i file che gestiscono il database in un package DBManager come dovrei modificare il file persistence?
Ho questa struttura:
\DBManager
+ PersonaService.class (contiene i metodi per l'inserimento di persone e la query di tutte le persone)
+ Persona.class (Entità persona)
\META-INF
+ MANIFEST.MF
+ persistence.xml
Test.class (contiene le stringhe EntityManagerFactory emf = Persistence.createEntityManagerFactory("PersonaService"); e EntityManager em = emf.createEntityManager(); )
Se i file PersonService.class e Persona.class sono nella stessa cartella di Test funzionano, se invece li aggiungo al package DBManager (e da Test.java importo il DBManager.* mi dice che non trova il provider).. :muro:
LacioDromBuonViaggio
26-05-2009, 11:28
Per ora ho messo tutto nella stessa cartella..
Con i package è un casino (sarò io che non ho l'impostazione giusta).
Ora ho un problema in quanto devo stampare i dati del database su una JTable che come costruttore prende un array di oggetti (Object [][]) oppure un TableModel.
La query findAll ritorna per forza una Collection, invece la JTable o il tableModel vuole un ArrayList per gestire i getter e i setter. Ma non c'è verso di fare il cast. E dire che sono sempre tanti oggetti Persona tutti uguali.. :muro:
Potresti anche consigliarmi qualcosa da leggere sui logger? Giusto per farmi un'idea nel caso potessero tornarmi utili.
Eccomi.
Allora, sì basta aggiungere un nuovo nodo "class" nel file persistence unit. Vale a dire che c'è un "class" per ogni @Entity.
Per il package tieni conto che il valore del nodo class è il nome pienamente qualificato della classe.
Se la classe Persona si trova nel package default (quello senza nome) allora sarà:
<class>Persona</class>
Se Persona si trova nel package it.pim.pum.pam allora sarà:
<class>it.pim.pum.pam.Persona</class>
La tabella.
La query jpa creata usando jpql resituisce risultati multipli in forma di lista di oggetti Java. Il modello standard di una tabella prende i dati in forma "raw" nel senso che richiede l'impostazione dei valori di ogni cella di ogni riga. C'è proprio una questione strutturale che impedisce il semplice cast, al di là della semplice incompatibilità dei tipi.
Prendendo come riferimento un DefaultTableModel ed un'entità Persona con tre campi (id, nome, cognome) un modo per riempire il modello con i dati delle persone restituite dal metodo getResultList di Query potrebbe essere:
DefaultTableModel model = new DefaultTableModel(0, 3);
List<?> persone = query.getResultList();
for(Object o : persone) {
Persona p = (Persona)o;
String[] data = {
String.valueOf(p.getId()), p.getNome(), p.getCognome(),
};
model.addRow(p);
}
JTable table = new JTable(model);
Per il logger non ho documentazione di riferimento. Io esaminai a suo tempo la tecnica prendendo spunto da un post di James Gosling sul suo blog che citava semplicemente la possibilità di farlo (c'era poi un progetto di traduzione della documentazione standard di Java che effettivamente usava un logger come database ma non so che fine abbia fatto).
banryu79
27-05-2009, 12:41
C'è un piccolo errore:
DefaultTableModel model = new DefaultTableModel(0, 3);
List<?> persone = query.getResultList();
for(Object o : persone) {
Persona p = (Persona)o;
String[] data = {
String.valueOf(p.getId()), p.getNome(), p.getCognome(),
};
model.addRow(data);
}
JTable table = new JTable(model);
Massssiiii, dai, stai a guardare 'ste piccolezze. Stringhe, array, persone, è la stessa cosa! :D Alla faccia della precisione scientifica :D.
banryu79
28-05-2009, 09:14
Alla faccia della precisione scientifica :D
No-no, io l'errore lo segnalato per 'spirito morale' non per precisione scientifica :Prrr:
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.