PDA

View Full Version : [JAVA] pensare in object oriented


tagan
04-11-2014, 10:32
Ciao a tutti.
ho finito di studiare il libro su java8. volevo cimentarmi in un programmino veloce: catalogare i libri che ho a casa, ma da programmatore VB6, mi rimane difficile pensare in OO.
faccio un esempio:
in vb6, si crea una form, 2-3 textbox, 2-3 pulsanti, una listbox e il codice per leggere e aggiornare il db.

in java, si fa allo stesso modo?
cioè, in questi casi, è inutile fare classi tipo:
MioLibro (autore, titolo, genere) e gestire il db con una sola classe:
ProcedurePerDB (con metodi per eseguire insert e update o per restituire un RecordSet)?

bastano 2-3 jtextfield e 2-3 jbutton e l'unica classe ProcedurePerDB?

non so se sono riuscito a spiegarmi!
tagan

wingman87
04-11-2014, 11:19
Creare una classe che rappresenta l'oggetto sul db è molto utile e rende il codice molto pulito e leggibile quindi te lo consiglio.
Tieni poi conto che è bene mantere l'interfaccia grafica il più separata possibile dalla logica del programma. Per farla semplice, nello scrivere la logica, immagina di dover utilizzare lo stesso codice per due interfaccie grafiche differenti. Una volta che hai creato questo strato di astrazione l'interfaccia grafica non deve far altro che sfruttarla.
Per fare un esempio banale: se devi inserire un nuovo libro nel db, che è un operazione banale, potresti pensare di mettere la logica tutta nel click del pulsante dell'interfaccia grafica. Ma immagina che in seguito la stessa logica serva anche altrove, allora la incapsuli in un metodo che prende in input un'entità Libro e a quel punto al click del bottone costruisci un Libro a partire dai dati inseriti nell'interfaccia e lo passi al metodo.

tagan
05-11-2014, 10:05
Grazie wingman87 per la risposta.

ho creato questo programmino e funziona senza problemi.
l'ho impostato nel seguente modo:
ho creato le classi "libro", "autore", genere non mi interessa, è un insieme finito e se devo aggiungere qualcosa lo faccio direttamente sul DB.

libro:
IDlibro, titolo, IDautore, IDgeneri, voto, posizione(sulla libreria (è una String))

autore:
IDautore, nome, cognome

genere;
IDgenere, genere

inoltre una classe generica per l'sql con 2 metodi: seleziona() (da select) e eseguiQuery() per aggiornamenti e un attributo che è una stringa per il comando SQL.
infine una classe che dato un ResultSet (restituito dal metodo seleziona(strSQL)), riempie una jTable.

queste ultime due sono in effetti le uniche classi che potrei riutilizzare per programmi futuri.
Non mi piace una cosa però: la connessione al DB rimane attiva. Non esiste un RecordSet offline in modo da prelevare i dati, chiudere la connessione e non perdere il RecordSet?

la logica del pulsante cerca invece l'ho scritta tutta nel pulsante. non sono riuscito a crearci una classe sopra.
non fa altro che creare una clausola WHERE. La concatena alla stringa SQL che poi viene passata al metodo "seleziona(SQL)" dell'oggetto di tipo "sqlCommand"
la WHERE è strettamente legata alle TextField dell'interfaccia grafica, quindi a meno che non crei una classe che accetti un array formato da TextField, non so come fare e non so se vale la pena farla!

Dove si può migliorare?

wingman87
06-11-2014, 09:21
Io incapsulerei i criteri di ricerca in una nuova classe e nel pulsante cerca riempirei quell'oggetto e lo passerei a un metodo cerca che prende in input quel tipo di oggetto.
Inoltre quando scrivi la query, non concatenare l'input dell'utente alla query ma usa i Prepared Statements (cerca su internet come usarli). In questo modo impari subito ad evitare problemi di sql injection.
Una riflessione generale: il modo in cui imposti tutto il progetto non è solo in un'ottica di un riutilizzo futuro ma soprattutto in un'ottica di leggibilità e manutenibilità.
Scusa ma non ho molto tempo per scrivere oggi, se hai altri dubbi chiedi pure.

zanardi84
06-11-2014, 11:09
Ciao a tutti.
ho finito di studiare il libro su java8. volevo cimentarmi in un programmino veloce: catalogare i libri che ho a casa, ma da programmatore VB6, mi rimane difficile pensare in OO.
faccio un esempio:
in vb6, si crea una form, 2-3 textbox, 2-3 pulsanti, una listbox e il codice per leggere e aggiornare il db.

in java, si fa allo stesso modo?
cioè, in questi casi, è inutile fare classi tipo:
MioLibro (autore, titolo, genere) e gestire il db con una sola classe:
ProcedurePerDB (con metodi per eseguire insert e update o per restituire un RecordSet)?

bastano 2-3 jtextfield e 2-3 jbutton e l'unica classe ProcedurePerDB?

non so se sono riuscito a spiegarmi!
tagan

Quì trovi tutte le risposte ai tuoi dubbi. E' la bibbia di java

http://docs.oracle.com/javase/tutorial/java/concepts/object.html

Daniels118
06-11-2014, 12:01
@tagan
Dalla tua risposta mi sembra che stai commettendo degli errori nel gestire i risultati delle query, ma forse mi sbaglio...
Quando dici "non mi piace una cosa però: la connessione al DB rimane attiva", mi fai pensare che vuoi estrarre i record dal resultset un po' alla volta, forse per gestire la paginazione. Questo approccio non va bene perché finché un resultset rimane aperto bloccha delle risorse, causando disservizi o cali delle performance. Il modo corretto di operare è estrarre solo le informazioni che servono (ovvero i dati di UNA pagina, attraverso la clausola LIMIT), e scaricarli subito dal resultset ad una struttura dati interna, per esempio una List di oggetti.

Nota a margine: il fatto di tenere aperta la connessione non è uno svantaggio, anzi, consente di abbattere i tempi di collegamento quando si vogliono fare 2 o più query; a tale scopo sono stati ideati i pool di connessioni.

tagan
06-11-2014, 13:28
facciamo ordine, perché incomincio ad avere dubbi su come java gestisce le connessioni.
ho una classe cosi definita:

public class AccessDB {
public boolean connetti(){...}
public void disconnetti(){...}
public ResultSet rsAccess(String strSQL){
try {
Statement s = conn.createStatement();
ResultSet rs = s.executeQuery(strSQL);
return rs;
} catch (SQLException ex) {
System.err.println(ex)
return null;
}
}
public int eseguiSQL(String strSQL){...}
}

non sto a scrivere tutto il codice, tranne il metodo rsAccess che è quello che mi interessa di più.

poi ho una form principale

public class Main extends javax.swing.JFrame {
AccessDB myConn = new AccessDB("D:\\Java\\Libreria\\Libreria.accdb");
....
....
....

carico l'oggetto myConn all'avvio del programma, ma non apro la connessione al DB.

poi ho un pulsante e qui scrivo il codice

myConn.connetti();
ResultSet myRS = myConn.rsAccess("select * from tblLibri");
while(myRS.next()){
....//popolo la JTable
....
}


la connessione di myConn e myRS rimangono aperte o si chiudono quando si esce dall'evento del pulsante?
perché se premo il jButton più volte di seguito, non mi da errore di connessione già aperta!

inlotre, il ResultSet della classe AccessDB, metodo rsAccess, deve essere chiuso? ma se lo chiudo prima del return, perdo il set di record!
su internet, usano i Vetcor, creano il RS, lo copiano nel Vector, chiudono il RS e poi il metodo restituisce la lista. è il modo più corretto questo?

Grazie, tagan

Daniels118
06-11-2014, 13:55
la connessione di myConn e myRS rimangono aperte o si chiudono quando si esce dall'evento del pulsante?
Questa domanda ha una risposta poco evidente che spiego di seguito.
L'uscita da una routine (in questo caso quella del pulsante) non chiude la connessione, né il resultset.
Per quanto riguarda il resultset, siccome non chiami il metodo close() prima di uscire dalla routine esso continua ad esistere, sebbene avendolo conservato in una variabile locale non hai più modo di referenziarlo.
Quando dopo qualche tempo si attiva il garbage collector, esso si accorge che il resultset non è più referenziato e invoca su di esso il metodo finalize. Non ho mai visto il codice sorgente di questo metodo, ma sono sicuro (o quanto meno me lo auguro) che al suo interno venga richiamato il metodo close(), che di fatto chiude il resultset.
Lo stesso discorso non si applica alla connessione, non perché sia speciale, ma solo perché l'hai dichiarata a livello di classe (non si vede, ma lo deduco dal codice), quindi resta referenziata finché l'istanza di AccessDB non viene distrutta.

A breve le altre risposte.

Daniels118
06-11-2014, 14:20
perché se premo il jButton più volte di seguito, non mi da errore di connessione già aperta!
Per questo serve il codice del metodo connetti().
inlotre, il ResultSet della classe AccessDB, metodo rsAccess, deve essere chiuso? ma se lo chiudo prima del return, perdo il set di record!
su internet, usano i Vetcor, creano il RS, lo copiano nel Vector, chiudono il RS e poi il metodo restituisce la lista. è il modo più corretto questo?
Come ti avevo già scritto prima, i resultset vanno sempre chiusi prima possibile per evitare di bloccare risorse, il metodo che hai citato è corretto.
Puoi anche restituire il resultset e fare queste operazioni nel chiamante, ma devi usare sempre lo stesso criterio, ovvero quello di estrarre i dati e chiudere il r.s. prima possibile, ad ogni modo trovo molto più elegante rendere queste operazioni "atomiche" tenendole tutte all'interno di un unico metodo.

tagan
06-11-2014, 14:32
Puoi anche restituire il resultset e fare queste operazioni nel chiamante, ma devi usare sempre lo stesso criterio, ovvero quello di estrarre i dati e chiudere il r.s. prima possibile, ad ogni modo trovo molto più elegante rendere queste operazioni "atomiche" tenendole tutte all'interno di un unico metodo.

posto la classe.

package libreria;
import java.sql.*;
import java.util.logging.Level;
import java.util.logging.Logger;

public class AccessDB {
private final String pathdb;
private boolean connesso;
public Connection conn;

public AccessDB(String pathdb){
this.pathdb=pathdb;
}

public boolean connetti(){
try {
conn = DriverManager.getConnection("jdbc:ucanaccess://"+pathdb);
connesso = true;
} catch (SQLException ex) {
System.err.println(ex);
connesso = false;
}
return connesso;
}

public void disconnetti(){
try {
conn.close();
connesso=false;
} catch (SQLException ex) {
System.err.println(ex);
}
}

public boolean isConnesso(){
return connesso;
}

public ResultSet rsAccess(String strSQL){
try {
Statement s = conn.createStatement();
ResultSet rs = s.executeQuery(strSQL);
return rs;
} catch (SQLException ex) {
System.err.println(ex);
return null;
}
}

public int eseguiSQL(String strSQL){
int n = 0;
try {
Statement s = conn.createStatement();
n = s.executeUpdate(strSQL);
} catch (SQLException ex) {
System.err.println(ex);
}
return n;
}
}


nel metodo connetti, effettivamente, non ho fatto nessun test per vedere se la connessione era già aperta.

quindi, sarebbe meglio creare anche metodi per chiudere il resultset e lo statement.
o meglio ancora, usare un modo per creare resultset offline.

PS: uso "jdbc:ucanaccess" perché ho installato Java8 e il driver jdbc-odbc non è più disponibile!

Daniels118
07-11-2014, 09:09
perché se premo il jButton più volte di seguito, non mi da errore di connessione già aperta!
Con il codice alla mano posso ora risponderti, in pratica avviene lo stesso discorso fatto prima per l'oggetto resultset: l'istanza di AccessDB rimane sempre la stessa, ma il campo conn che mantiene il riferimento alla connessione viene valorizzato ogni volta con una nuova connessione. Siccome è lecito avere più connessioni attive non viene generata alcuna eccezione, semmai si dovesse raggiungere il limite massimo di connessioni attive l'eccezione verrebbe catturata e conn continuerebbe a puntare all'ultima connessione creata, che va comunque bene. Resta comunque il fatto che ogni tanto queste connessioni "disperse" vengono distrutte dal garbage collector, salvando il tuo programma.

Tutto questo discorso per dirti che alla fine funziona, ma - non ti offendere - è gestito male.

Come ti ho già ripetuto più volte non dovresti restituire il resultset, ma un oggetto che *contenga* l'insieme dei risultati, va bene una qualunque classe che implementi l'interfaccia Iterable, per esempio il già citato Vector o LinkedList. In tal modo puoi chiudere il resultset e la connessione prima di uscire dal metodo.
Ripeto però che sarebbe meglio tenere aperta la connessione, aprendola una sola volta all'avvio del programma e chiudendola al termine.

*Nota: contrariamente a quanto suggerisce il nome, la classe ResultSet non contiene l'insieme dei risultati, ma un riferimento al cursore del vero resultset, che è una struttura dati che risiede sul database.
Ti consiglio di leggere le prime righe della documentazione, potrebbero essere illuminanti: ResultSet (http://docs.oracle.com/javase/7/docs/api/java/sql/ResultSet.html)

PS. anche le istanze di Statement vanno chiuse.

tagan
07-11-2014, 09:28
giusto. non ci avevo pensato!
capito tutto.

...
Tutto questo discorso per dirti che alla fine funziona, ma - non ti offendere - è gestito male.
...

no tranquillo, non mi offendo mai. ne ero consapevole che era gestito male. d'altronde, sono circa 2 sett che faccio qualcosa in java e (per ora) è solo per "hobby".
in azienda continuano col VB6 e non vogliono schiodarsi da li!!!

devo solo capire come funzionano i vector e i linkedlist. studio un po, tanto poi tornerò con la nuova versione della classe e qualche nuovo problema :)

grazie.

Daniels118
07-11-2014, 10:26
Per essere precisi vorrei riformulare l'ultima affermazione:
contrariamente a quanto suggerisce il nome, la classe che implementa ResultSet non contiene necessariamente l'insieme dei risultati, ma più probabilmente un riferimento al cursore del vero resultset, che è una struttura dati che risiede sul database.
Questa precisazione è necessaria perché ResultSet non è una classe ma un'interfaccia, e l'implementazione può variare in base al driver. Ad ogni modo chi realizza il driver non è tenuto a scaricare subito l'intero resultset dal db, per questo deve essere il programmatore che lo utilizza ad occuparsene.

Ti do un consiglio per i tuoi studi: se guardi la documentazione di una classe ti accorgi che c'è un piccolo paragrafo intitolato "All implemented interfaces". Qui sono elencate le interfacce implementate dalla classe. Se guardi la documentazione delle interfacce capisci qual'è la loro funzione; le classi che le implementano ereditano le loro funzioni.
Prendi ad esempio Vector (http://docs.oracle.com/javase/7/docs/api/java/util/Vector.html), essa implementa Iterable (http://docs.oracle.com/javase/7/docs/api/java/lang/Iterable.html). Questa interfaccia <<allows an object to be the target of the "foreach" statement>>, ovvero consente di estrarre gli elementi in un ciclo "foreach". Guardando il paragrafo "All Known Implementing Classes" vedi che l'interfaccia è implementata - tra le altre - da Vector. Confrontiamo ora Vector con un'altra classe che implementa Iterable, prendiamo ad esempio LinkedList (http://docs.oracle.com/javase/7/docs/api/java/util/LinkedList.html).
La prima differenza che notiamo è che implementano delle interfacce diverse (oltre a Iterable che è comune alle due), ma a noi intressano solo le funzionalità offerte dall'interfaccia Iterable. Allora perché dovremmo scegliere Vector piuttosto che LinkedList? Una delle differenze che possiamo notare è che Vector è dotato di una "capacità", che è si espandibile, ma per essere aumentata richiede una ricollocazione (che è un'operazione molto onerosa). Una LinkedList invece è molto più dinamica. Ma allora LinkedList è sempre migliore? La risposta è no, LinkedList è buona quando la quantità degli elementi varia frequentemente, in caso contrario Vector è molto più performante.
La scelta quindi va fatta consapevolmente dopo aver studiato le varie classi.
Ma il succo di questo discorso è di non soffermarsi sulle singole classi, bensì avere una panoramica di quali sono le classi che implementano le funzionalità che ci interessano, e il modo migliore per identificarle è (secondo la mia opinione) partire dalle interfacce che soddisfano le nostre esigenze.

tagan
07-11-2014, 11:34
considerando le tabelle dei DB, che variano giornalmente la loro dimensione, allora conviene le LinkedList.
A tal proposito, ho fatto qualche prova e modificato la classe in questo modo (ci sto ancora lavorando)

public LinkedList<String[]> rsAccess(String strSQL){
LinkedList<String[]> rsOUT = new LinkedList<String[]>();
try {
Statement s = conn.createStatement();
ResultSet rs = s.executeQuery(strSQL);
ResultSetMetaData rsmd= rs.getMetaData();
String element[]=new String[rsmd.getColumnCount()]; //do a element la dimensione pari al numero di campi della tabella.
while (rs.next()){
for (int i=1;i<=rsmd.getColumnCount();i++){
element[i-1]=rs.getString(i);// registro il contenuto del record
}
System.out.print("ele_di_RS: "); //controllo i dati. codice da eliminare in seguito
for (String ele1 : element) {
System.out.print(ele1 + " - ");
}
System.out.println();
rsOUT.addLast(element); //aggiungo il record alla lista
}

System.out.println();
for (String[] ele : rsOUT) {
System.out.print("ele_di_LK: ");
for (String ele1 : ele) {
System.out.print(ele1 + " - ");
}
System.out.println();
}

return rsOUT;
} catch (SQLException ex) {
System.err.println(ex);
return null;
}
}


mi spiegate perché ho questo output?

ele_di_RS: 1 - primo - record -
ele_di_RS: 2 - secondo - record -

ele_di_LK: 2 - secondo - record -
ele_di_LK: 2 - secondo - record -


nella lista ci sono 2 valori identici?
deve sbaglio?

Daniels118
07-11-2014, 13:55
considerando le tabelle dei DB, che variano giornalmente la loro dimensione, allora conviene le LinkedList.
No no, non c'entra nulla!
Il risultato della query è una fotografia della situazione nel momento in cui la query è stata eseguita, se poi in seguito la tabella sul db cambia la LinkedList non se ne accorge! Ogni volta che fai una query restituirai una nuova istanza di LinkedList (o quello che è), senza dover fare alcun ridimensionamento.
Siccome quando fai una select il numero di righe nel resultset è noto già prima di leggere il primo record, conviene utilizzare un Vector rispetto ad una LinkedList, o meglio ancora un array che ha una dimensione fissa (chi vorrebbe modificare i risultati di una query?).

Daniels118
07-11-2014, 14:02
nella lista ci sono 2 valori identici?
deve sbaglio?
Questa istruzione va inserita dentro al ciclo while:
String element[]=new String[rsmd.getColumnCount()];
altrimenti scrivi i campi sempre sullo stesso "element", che poi vai ad inserire due volte nella lista (eh si, una lista può contenere due volte lo stesso elemento!), guarda caso l'elemento "duplicato" è l'ultimo estratto.

tagan
07-11-2014, 17:07
No no, non c'entra nulla!....

si infatti ho detto una stupidaggine!

tagan
07-11-2014, 17:21
Questa istruzione va inserita dentro al ciclo while:
String element[]=new String[rsmd.getColumnCount()];
altrimenti scrivi i campi sempre sullo stesso "element", che poi vai ad inserire due volte nella lista (eh si, una lista può contenere due volte lo stesso elemento!), guarda caso l'elemento "duplicato" è l'ultimo estratto.

ecco vedi, devo mettermi in testa che sono puntatori e non vere e proprie variabili in stile VB6!
nella lista, non metto i valori del recordset, ma metto il puntatore a una struttura esterna, giusto?

a questo punto, meglio ancora potrei (usare un vector), ma usare una classe che mi definisce il libro (il piccolo programmino è un gestore di libri di casa)
e usare

Libro element=new Libro(); //dentro il blocco while,
//poi, come ultima istruzione del blocco while
rsOUT.addLast(element)


Usando la classe Libro, il LinkedList come deve essere istanziato? in questo modo?

LinkedList<Libro> rsOUT = new LinkedList<Libro>();


Ultima cosa:
come mai NetBeans 8, quando provo a usare "Vector" mi da un "consiglio" dicendo "Obsolete Collection"?

Grazie, tagan

Daniels118
09-11-2014, 10:00
Più che puntatori in Java bisogna parlare di "riferimenti", comunque il concetto l'hai centrato. Non credere che vb6 sia tanto diverso da java sotto questo aspetto, anche in vb6 ci sono gli oggetti e due variabili diverse possono puntare allo stesso oggetto, così come in java esistono tipi di dato nativi (per esempio "int").

Il codice che hai postato è perfetto.

Per quanto riguarda il fatto che Vector è obsoleta non ne sapevo nulla, così ho fatto una ricerca sul web, puoi vedere qui: Why is Java Vector class considered obsolete or deprecated? (http://stackoverflow.com/questions/1386275/why-is-java-vector-class-considered-obsolete-or-deprecated)