Lavorare sulle basi dati con JDBC

di il
105 risposte

105 Risposte - Pagina 7

  • Re: Lavorare sulle basi dati con JDBC

    Ho provato a scrivere dentro un file .json i dati del db come consigliatomi da Migliorabile e poi a caricarli con jackson-databind (dovrei usare questa libreria) ma il codice mi da un java.lang.NullPointerException proprio in questo punto:
    Classe DBMSConnessione:
        DBMS db;
        public DBMSConnessione() {
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                this.db = objectMapper.readValue(new FileReader("/DBMS.json"), DBMS.class);
                System.out.println("Credenziali del DBMS lette dal file con successo.");
            } catch (Exception e){
                throw new DBMSEccezione("Non è stato possibile recuperare le credenziali del DBMS dal file.", e);
            }
        }
        private final String url = db.getUrl(); // qui c'è l'eccezione.........
        private final String user = db.getUser();
        private final String password = db.getPassword();
    Classe DBMS:
    package DBMS;
    
    public class DBMS {
    
        private String url;
        private String user;
        private String password;
    
        public String getUrl() {
            return url;
        }
    
        public void setUrl(String url) {
            this.url = url;
        }
    
        public String getUser() {
            return user;
        }
    
        public void setUser(String user) {
            this.user = user;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
    }
    Il json è scritto correttamente:
    {
    	"name":"jdbc:postgresql://192.168.99.103:5432/gis",
    	"surname":"eb",
    	"active":"password"
    }
    Ho posizionato DBMS.json accanto alla classe Main.java.
    Dove sbaglio?
  • Re: Lavorare sulle basi dati con JDBC

    giannino1995 ha scritto:


    ma il codice mi da un java.lang.NullPointerException proprio in questo punto:
    Giannino, lo so che la mia seguente domanda sarà "spinosa" (e scomoda) per te: tu conosci quale è il flusso generale di esecuzione durante la fase di creazione e inizializzazione di un oggetto??

    Per dirlo nel tuo caso: sai QUANDO viene eseguita questa inizializzazione?
    private final String url = db.getUrl();

    Questa purtroppo è una di quelle cose che DEVE essere ben chiara a chi programma in Java ....

    giannino1995 ha scritto:


    Ho posizionato DBMS.json accanto alla classe Main.java.
    Se usi /DBMS.json, se lo aspetterebbe nella radice del disco ....
    Quindi è comunque sbagliato o perlomeno da rivalutare perché inappropriato...

    giannino1995 ha scritto:


    Il json è scritto correttamente:
    {
    	"name":"jdbc:postgresql://192.168.99.103:5432/gis",
    	"surname":"eb",
    	"active":"password"
    }
    Non risulta comunque corretto/corrispondente nei confronti della classe DBMS.
  • Re: Lavorare sulle basi dati con JDBC

    Quando si istanzia un oggetto java prima vengono create le variabili della classe e poi vengono eseguiti i costruttori ed infine i metodi. E' così?
    Ho provato a riscrivere tutto come da tuo consiglio ed in effetti ora funziona tutto alla perfezione. Grazie mille.
    Non so sinceramente da dove avessi preso quel .json, sul mio pc è diverso ma non importa.
    public DBMS DBMSConnessione() {
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                DBMS db = objectMapper.readValue(new FileReader("D:/.../IntelliJ-P/TestPostgreSQL/src/DBMS.json"), DBMS.class);
                System.out.println("Credenziali del DBMS lette dal file con successo.");
                return db;
            } catch (Exception e){
                throw new DBMSEccezione("Non è stato possibile recuperare le credenziali del DBMS dal file.", e);
            }
    
        }
    
        public Connection connect() {
            try {
                DBMS db = DBMSConnessione();
                String url = db.getUrl();
                String user = db.getUser();
                String password = db.getPassword();
                Connection conn = DriverManager.getConnection(url, user, password);
                System.out.println("Connesso al server PostgreSQL con successo.");
                return conn;
            } catch (Exception e) {
                throw new DBMSEccezione("Impossibile connettersi al server PostgreSQL.", e);
            }
        }
    L'unico cosa che mi preoccupa è che java deve leggere un file ad ogni connessione e questo sovraccarica in modo sproporzionato il server. Il software funziona ma temo che sia una porcheria.
    Nella webapp potrò scrivere questo, giusto:
    DBMS db = objectMapper.readValue(new FileReader("/WEB-INF/DBMS.json"), DBMS.class);
    Se devo scrivere C: allora non ci siamo proprio neppure su questo punto.
    Il json corretto è questo:
    {
    	"url": "jdbc:postgresql://192.168.99.103:5432/gis",
    	"user": "eb",
    	"password": "password"
    }
    Altra cosa che mi preoccupa è non poter usare qualche metodo per mascherare la password. Per le password degli utenti uso una funzione particolare di java ma credo che sia troppo per me occuparmi anche di questo aspetto.
    Grazie di tutto sei davvero disponibile e gentilissimo
  • Re: Lavorare sulle basi dati con JDBC

    giannino1995 ha scritto:


    Quando si istanzia un oggetto java prima vengono create le variabili della classe e poi vengono eseguiti i costruttori ed infine i metodi. E' così?
    NO.

    Innanzitutto la prima istruzione di un costruttore deve sempre essere una invocazione super( .... ); o this( .... ); (con o senza argomenti, dipende ovviamente dal costruttore che si vuole invocare). Se non scrivi una di queste due invocazioni esplicitamente, il compilatore mette come prima istruzione una invocazione super(); (senza argomenti!)

    Quindi nel tuo caso di prima il codice reale diventa:
        DBMS db;
        public DBMSConnessione() {
            super();            // <---- inserito dal compilatore!
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                this.db = objectMapper.readValue(new FileReader("/DBMS.json"), DBMS.class);
                System.out.println("Credenziali del DBMS lette dal file con successo.");
            } catch (Exception e){
                throw new DBMSEccezione("Non è stato possibile recuperare le credenziali del DBMS dal file.", e);
            }
        }
        private final String url = db.getUrl(); // qui c'è l'eccezione.........
        private final String user = db.getUser();
        private final String password = db.getPassword();
    Quando fai new DBMSConnessione() quindi avverrebbe tecnicamente/teoricamente questo:

    1) PRIMA viene innanzitutto eseguita la invocazione super() che invoca il super-costruttore. Se la super-classe è Object (come presumo nel tuo caso), il costruttore di Object viene eseguito, che non fa nulla di speciale. Se la super-classe facesse altro di particolare la logica di questi 3 passi si ripeterebbe a sua volta.

    2) POI, appena dopo che la chiamata super() termina, vengono eseguiti tutti gli inizializzatori delle variabili di istanza e tutti gli instance initialization block. In ordine rigorosamente testuale, cioè come si presentano nel sorgente dall'alto verso il basso.

    Quindi in sequenza:
    db resta a valore di default (null)
    url = db.getUrl()
    user = db.getUser()
    password = db.getPassword()

    3) POI solo dopo che le variabili di istanza sono state inizializzate e gli (eventuali) instance initialization block eseguiti, allora il codice nel tuo costruttore prosegue, quindi viene eseguito:
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                this.db = objectMapper.readValue(new FileReader("/DBMS.json"), DBMS.class);
                System.out.println("Credenziali del DBMS lette dal file con successo.");
            } catch (Exception e){
                throw new DBMSEccezione("Non è stato possibile recuperare le credenziali del DBMS dal file.", e);
            }
    Credo (spero..) che ci arrivi da solo: alla luce di questi passi, perché al db.getUrl() hai un NullPointerException?

    giannino1995 ha scritto:


    L'unico cosa che mi preoccupa è che java deve leggere un file ad ogni connessione e questo sovraccarica in modo sproporzionato il server.
    No, non è affatto così "sproporzionato". E' la Connection che a crearla è più "costosa".

    E comunque nessuno vieta di fare logiche più "furbe" del tipo:

    - rileggo il file ma solo dopo che è passato (almeno) es. 1 minuto dall'ultima lettura
    o meglio ancora
    - rileggo il file ma solo se risulta "modificato" dopo l'ultima lettura

    giannino1995 ha scritto:


    Il software funziona ma temo che sia una porcheria.
    Giannino, stai facendo una ESERCITAZIONE. Oltretutto avendo mille dubbi a più livelli e dimostrando anche lacune abbastanza grosse.
    I risultati sono ovviamente quelli che sono per un tale livello ..... e dovrebbero/dovranno servirti per migliorare ... perlomeno per far sì che in successive esercitazioni non "caschi" nelle stesse questioni ...

    giannino1995 ha scritto:


    DBMS db = objectMapper.readValue(new FileReader("/WEB-INF/DBMS.json"), DBMS.class);
    No. Per FileReader il "/" iniziale vuol dire "radice del file-system" (non la context-root della webapp, di cui ovviamente non "sa" nulla).

    giannino1995 ha scritto:


    Se devo scrivere C: allora non ci siamo proprio neppure su questo punto.
    La questione semmai è: vuoi che il file di configurazione sia "dentro" la webapp ... o "fuori"?

    Se fuori, si intende ovunque sul file-system, allora hai almeno 2 possibilità:
    - te lo aspetti e lo rintracci in una locazione "notevole" es. da qualche parte sotto la "home" dell'utente
    - lo metti dove vuoi e poi "configuri" il path nella webapp, ad esempio con un context param nel web.xml o un file di properties sempre nella webapp.

    giannino1995 ha scritto:


    Altra cosa che mi preoccupa è non poter usare qualche metodo per mascherare la password.
    Anche qui, nessuno vieta di usare forme di crittografia. Chiaramente ci vuole poi una "chiave" di decifratura e la si può o generare lato codice con un algoritmo che dà sempre la stessa chiave oppure la si "occulta" un pochino in qualche modo nelle classi.

    Non è una sicurezza totale in senso assoluto ma perlomeno impedisce ai "pischelli" di carpire le password aprendo file di configurazione a destra e a manca per sbirciarne il contenuto ..

    giannino1995 ha scritto:


    ma credo che sia troppo per me occuparmi anche di questo aspetto.
    Sì, come "esercitazione" non credo serva ..
  • Re: Lavorare sulle basi dati con JDBC

    Grazie andbin, conoscevo il funzionamento di super() ma non sapevo che il compilatore ne aggiungesse uno automaticamente in quel modo.
    Vorrei lasciare il file .json dentro la webapp, possibilmente al sicuro dentro WEB-INF per cui il percorso che devo inserire qual è? Se sono in una servlet posso usare questo codice ma se sono in una classe qualsiasi come mi muovo?
    
    ServletContext context = getServletContext();
    String fullPath = context.getRealPath("/WEB-INF/users.txt");
    File fileUtenti = new File(fullPath);
    
    Ho fatto un po’ di ricerche su google ma non sono stato fortunato.
    Il discorso di leggere il file ogni 60 secondi credo che sia una cosa interessante forse ancora di più quello di leggere il file solo se è stato modificato ma per capire se la data di creazione di un file è stata modificata bisogna sempre e comunque usare il disco. Non sarebbe meglio leggere il file una volta sola e stop, eventualmente 2 se ciò che si ha in memoria non va più bene (è stato cambiato il file .json dall’amministratore del sito)? In termini di codice java questa seconda opzione non sarebbe meglio? Che ne pensi?
    
    package DBMS;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    
    import java.io.FileReader;
    import java.sql.*;
    
    public class DBMSConnessione {
    
        DBMS dbms = null;
    
        static {
            try {
                Class.forName("org.postgresql.Driver");
            } catch (ClassNotFoundException e) {
                System.out.println("Driver JDBC non disponibile.");
                throw new ExceptionInInitializerError(e);
            }
        }
    
        public DBMS DBMSConnessione() {
            try {
                ObjectMapper objectMapper = new ObjectMapper();
                DBMS db = objectMapper.readValue(new FileReader("C:/.../IntelliJ-P/TestPostgreSQL/src/DBMS.json"), DBMS.class);
                System.out.println("Credenziali del DBMS lette dal file con successo.");
                this.dbms = db;
                return db;
            } catch (Exception e){
                throw new DBMSEccezione("Non è stato possibile recuperare le credenziali del DBMS dal file.", e);
            }
    
        }
    
        public Connection connect() {
            try {
                DBMS db = this.dbms;
                String url;
                String user;
                String password;
                Connection conn;
                try {
                    url = db.getUrl();
                    user = db.getUser();
                    password = db.getPassword();
                    conn = DriverManager.getConnection(url, user, password);
                } catch(Exception e) {
                    db = DBMSConnessione();
                    url = db.getUrl();
                    user = db.getUser();
                    password = db.getPassword();
                    conn = DriverManager.getConnection(url, user, password);
                }
                System.out.println("Connesso al server PostgreSQL con successo.");
                return conn;
            } catch (Exception e) {
                throw new DBMSEccezione("Impossibile connettersi al server PostgreSQL.", e);
            }
        }
    
        public void close(AutoCloseable c) {
            try {
                if (c != null) {
                    c.close();
                    if(c instanceof ResultSet) System.out.println("ResultSet chiuso con successo.");
                    if(c instanceof PreparedStatement) System.out.println("PreparedStatement chiuso con successo.");
                    if(c instanceof Connection) System.out.println("Connessione chiusa con successo.");
                }
            } catch (Exception e) {
                System.out.println("Impossibile rilasciare la risorsa. Il server rischia di essere sovraccaricato.");
            }
        }
    
        public void close(AutoCloseable... c) {
            for(AutoCloseable i : c) close(i);
        }
    
    }
    
  • Re: Lavorare sulle basi dati con JDBC

    giannino1995 ha scritto:


    conoscevo il funzionamento di super() ma non sapevo che il compilatore ne aggiungesse uno automaticamente in quel modo.
    Altra lacuna ...

    giannino1995 ha scritto:


    Se sono in una servlet posso usare questo codice ma se sono in una classe qualsiasi come mi muovo?
    Generalmente gli si può dare "in mano" il ServletContext. Ma si possono anche scegliere altre soluzioni.

    giannino1995 ha scritto:


    Ho fatto un po’ di ricerche su google ma non sono stato fortunato.
    Ricerche per cosa?

    giannino1995 ha scritto:


    Il discorso di leggere il file ogni 60 secondi credo che sia una cosa interessante
    Basta tenersi il tempo "precedente".

    giannino1995 ha scritto:


    ma per capire se la data di creazione di un file è stata modificata bisogna sempre e comunque usare il disco.
    lastModified() di java.io.File. Che non è (generalmente) assolutamente un grosso problema.

    giannino1995 ha scritto:


    Non sarebbe meglio leggere il file una volta sola e stop, eventualmente 2 se ciò che si ha in memoria non va più bene
    Lo ripeto: stai facendo una esercitazione. Potrebbe andare anche bene se lo leggi 1 volta e mai più fino a quando si riavvia il server (o redeploy della webapp). Se non ci sono "specifiche" dettate espressamente .... fai come vuoi.

    giannino1995 ha scritto:


                    if(c instanceof ResultSet) System.out.println("ResultSet chiuso con successo.");
                    if(c instanceof PreparedStatement) System.out.println("PreparedStatement chiuso con successo.");
                    if(c instanceof Connection) System.out.println("Connessione chiusa con successo.");
    Evita queste bischerate. Usa il class name dell'oggetto.
  • Re: Lavorare sulle basi dati con JDBC

    Sempre grazie.
    L'URL se la scrivo così funziona:
    DBMS db = objectMapper.readValue(new FileReader("src/DBMS.json"), DBMS.class);
    Nella webapp troverò qualcosa di analogo.
    Così va meglio?
    if(c.getClass().getName().contains("PgResultSet")) System.out.println("ResultSet chiuso con successo.");
    if(c.getClass().getName().contains("PreparedStatement")) System.out.println("PreparedStatement chiuso con successo.");
    if(c.getClass().getName().contains("Connection")) System.out.println("Connessione chiusa con successo.");
  • Re: Lavorare sulle basi dati con JDBC

    giannino1995 ha scritto:


    DBMS db = objectMapper.readValue(new FileReader("src/DBMS.json"), DBMS.class);
    No, neanche. Quando una applicazione gira poi per conto suo la src, i sorgenti insomma "non esistono" più.

    giannino1995 ha scritto:


    Così va meglio?
    if(c.getClass().getName().contains("PgResultSet")) System.out.println("ResultSet chiuso con successo.");
    if(c.getClass().getName().contains("PreparedStatement")) System.out.println("PreparedStatement chiuso con successo.");
    if(c.getClass().getName().contains("Connection")) System.out.println("Connessione chiusa con successo.");
    Noooo. Stampa il NOME della classe, non logiche strane con il nome della classe!!
  • Re: Lavorare sulle basi dati con JDBC

    Allora cosa posso scrivere?
    Io vorrei la context-root della webapp o del programma desktop classico.
    Non saprei che altro scrivere in quei if, entrambe le soluzioni che ho postato funzionano.
    Intendi una cosa i questo genere:
    if(c.getClass().getName().equals("org.postgresql.jdbc.PgResultSet")) System.out.println("ResultSet chiuso con successo.");
    if(c.getClass().getName().equals("org.postgresql.jdbc.PgPreparedStatement")) System.out.println("PreparedStatement chiuso con successo.");
    if(c.getClass().getName().equals("org.postgresql.jdbc.PgStatement")) System.out.println("Statement chiuso con successo.");
    if(c.getClass().getName().equals("org.postgresql.jdbc.PgConnection")) System.out.println("Connessione chiusa con successo.");
    Se si non mi piace molto perché nella stringa c'è la parolina postgresql. Sarebbe altro codice da editare in fase di test in caso di cambio del db.
  • Re: Lavorare sulle basi dati con JDBC

    giannino1995 ha scritto:


    Allora cosa posso scrivere?
    Io vorrei la context-root della webapp o del programma desktop classico.
    Te l'ho già detto: una (UNA delle possibili) opzione è dare il ServletContext dove serve.

    giannino1995 ha scritto:


    Non saprei che altro scrivere in quei if, entrambe le soluzioni che ho postato funzionano.
    Uhm, in effetti non ci stavo pensando ma il class name dell'oggetto ovviamente è quello specifico del driver. Non è il nome della interfaccia di JDBC (es. Connection). Quindi meno significativo/utile.

    Il test con instanceof PUO' in effetti anche andare bene. Ma NON farlo lì come stavi facendo per fare 3 println distinti. Fai un metodo a parte:
    private static String getAutoCloseableType(AutoCloseable c) {
        if (c instanceof Connection) return "Connection";
        if (c instanceof PreparedStatement) return "PreparedStatement";
        if (c instanceof ResultSet) return "ResultSet";
        return c.getClass().getName();   // default
    }
    System.out.format("%s chiuso con successo.%n", getAutoCloseableType(c));

    Tu dirai: cosa cambia? CAMBIA, perché non hai "sporcato" la tua logica del close con altro che non c'entra con il close (cioè appunto dedurre il nome del tipo). Ed è tra l'altro anche riutilizzabile eventualmente per altro.
  • Re: Lavorare sulle basi dati con JDBC

    Grazie, si getAutoCloseableType() in effetti è molto meglio.
    Cosa vuol dire "dare il ServletContext"?
    Se uso il procedimento che uso nelle Servlet, getServletContext() non riesco ad importarlo.
    Nelle classi semplici come ottengo il context-root della webapp?
  • Re: Lavorare sulle basi dati con JDBC

    giannino1995 ha scritto:


    Nelle classi semplici come ottengo il context-root della webapp?
    Gli passi il ServletContext.
  • Re: Lavorare sulle basi dati con JDBC

    ServletContext context = getServletContext();
    String percorsoDbms = context.getRealPath("/WEB-INF/BDMS.json");
    File dbms = new File(percorsoDbms);
    DBMS db = objectMapper.readValue(new FileReader(dbms), DBMS.class);
    IntelliJ quel getServletContext() non me lo importa. Ho provato altri get... ma niente. Io sono in una semplice classe della webapp, non in una servlet.
  • Re: Lavorare sulle basi dati con JDBC

    Ops... solo le servlet possono accedere a WEB-INF... dunque cosa faccio?
    Se estendo la classe in cui si trova quel codice ottengo un java.lang.NullPointerException... ma comunque non mi va di farlo...
    La fai troppo semplice ma non è così...
  • Re: Lavorare sulle basi dati con JDBC

    Vuoi avere il ServletContext in DBMSConnessione? E chi istanzia DBMSConnessione? Chi lo fa ha il ServletContext a disposizione?
    Allora:

    public DBMSConnessione(ServletContext context) {

    E chi crea il DBMSConnessione, gli passa appunto il ServletContext.
Devi accedere o registrarti per scrivere nel forum
105 risposte