Connessione al database: devono essere passati come parametro?


11

Abbiamo un sistema in base al quale la connessione al database viene ottenuta una volta utilizzando un metodo comune e viene passata attraverso la classe pertinente da utilizzare. Ci sono dubbi sul fatto che passare la connessione al database come parametro a classi diverse causerebbe problemi, quindi sto controllando qui per vedere se questo è effettivamente praticabile e ci sono modelli migliori per farlo?

So che ci sono alcuni strumenti ORM per fare la persistenza, ma non possiamo ancora approfondire questo.

Qualsiasi feedback è gradito, grazie.


A che tipo di problemi ti riferisci? Chi ha questi dubbi? (Non tu, suppongo.)
Greg Hewgill

1
Problemi come far dimenticare allo sviluppatore di chiudere la connessione, in genere sto solo cercando di vedere se è una buona pratica passare la connessione al database a vari metodi come parametro. Ya i dubbi provengono da un altro sviluppatore.
ipohfly,

Risposte:


8

Sì, è sicuro passare una connessione. Gestisci la connessione in un blocco di controllo esterno. Non c'è nulla di pericoloso.

Ciò che non è sicuro è scrivere codice che non garantisce che la connessione sia correttamente disposta in modo tempestivo. Dimenticare di ripulire una risorsa non è correlato a passarla in giro. Potresti anche scrivere facilmente il codice che lascia una connessione sospesa senza passarlo da nessuna parte.

In C ++, si è protetti da RAII se si allocano in pila o si utilizzano i puntatori intelligenti. In C # fai una dura regola che tutti gli oggetti usa e getta (come le connessioni) siano dichiarati in un blocco "using". In Java ripulisci con la logica try-finally. Avere revisioni del codice su tutto il codice del livello dati per garantire ciò.

Il caso d'uso più comune è quando si hanno diverse operazioni che possono essere combinate in molte permutazioni. E ciascuna di queste permutazioni deve essere una transazione atomica (tutte hanno successo o rollback). quindi è necessario passare la transazione (e quindi la connessione corrispondente) a tutti i metodi.

Supponiamo di avere molte azioni foobar () che possono essere combinate in vari modi come transazioni atomiche.

//example in C#
//outer controlling block handles clean up via scoping with "using" blocks.
using (IDbConnection conn = getConn())
{
    conn.Open();
    using (IDbTransaction tran = conn.BeginTransaction())
    {
        try
        {//inner foobar actions just do their thing. They don't need to clean up.
            foobar1(tran);
            foobar2(tran);
            foobar3(tran);
            tran.Commit();
        }
        catch (Exception ex)
        { tran.Rollback(); }
    }
}//connection is returned to the pool automatically

A proposito, vorrai aprire le connessioni il più tardi possibile, eliminarle il prima possibile. I tuoi compagni di squadra potrebbero avere ragione se stai trattando le connessioni come membri dell'oggetto, introducendole come stato non necessario e lasciando le connessioni aperte molto più a lungo del necessario. Ma l'atto di passare una connessione o transazione come parametro non è intrinsecamente sbagliato.

BTW. A seconda del supporto della tua lingua per le funzioni di prima classe, puoi eseguire un elenco di azioni foobar (). Quindi una funzione potrebbe gestire tutte le permutazioni delle azioni. Eliminazione della duplicazione del blocco di controllo esterno per ogni permutazione.


contrassegnare questa come risposta in quanto mi dà più idea di come sia la situazione
ipohfly

6

Sembra che tu sia dopo l' iniezione di dipendenza . Cioè, la connessione in pool viene creata una volta e iniettata ovunque sia necessaria. Certamente passare la connessione tramite un parametro di metodo è un modo per iniettare dipendenza, ma un contenitore IoC come Guice, PicoContainer o Spring è un altro modo (più sicuro) per farlo.

L'uso di DI significa che puoi avvolgere ordinatamente la logica attorno alla creazione, apertura, utilizzo e chiusura della connessione, lontano dalla tua logica aziendale principale.

Spring JDBC e altri sono altri esempi di questo tipo di comportamento per te


Emm non sta davvero guardando l'iniezione di dipendenza. Sto solo cercando di scoprire se in genere è una buona pratica farlo, e se non lo è, qual è il modo migliore di gestire la connessione al database (DI è comunque un modo per farlo).
ipohfly,

-1. Una connessione non si adatta a un sistema multiutente. Potrebbe sembrare che funzioni a causa del basso volume dell'utente e dell'esecuzione rapida. Con il pool, è meglio creare un'istanza di un oggetto connessione per azione anche in un singolo sistema utente.
mike30,

2

Passare intorno alle cose del database piuttosto che ai dati può portare a problemi. In tale misura, ogni volta che è pratico, non passare una cosa del database a meno che non si possa garantire un'igiene adeguata del database.

Il problema con il passaggio di cose nel database è che può essere sciatto. Ho visto più di un bug nel codice con qualcuno che passa attorno a una connessione al database, che qualcuno prende quindi un set di risultati e si blocca in un oggetto locale (il set di risultati, ancora connesso al database) e quindi lega un cursore nel database per un tempo significativo. Un'altra istanza che qualcuno ha passato un set di risultati a qualcun altro (che è stato poi nascosto) e quindi il metodo che ha passato il set di risultati lo ha chiuso (e l'istruzione) causando errori quando altri metodi hanno provato a lavorare con il set di risultati che non era più.

Tutto ciò deriva dal non rispetto del database, della connessione, dell'istruzione, del set di risultati e dei loro cicli di vita.

Per evitarlo, esistono modelli e strutture esistenti che giocano più bene con i database e non dispongono di database per cui le cose devono uscire dalle classi in cui sono confinati. I dati entrano, i dati escono, il database rimane inserito.


1
Le connessioni +1 db dovrebbero avere il minor tempo possibile. Aprilo, usalo, chiudilo il più velocemente possibile. Oggi ci sono molte implementazioni di pool di connessioni, quindi usare una connessione per più operazioni è una falsa economia. E un invito per bug o problemi di prestazioni (blocco dei tavoli, utilizzo delle risorse di connessione)
jqa

Quali sono i nomi di alcuni di questi schemi e strutture esistenti?
Daniel Kaplan,

@tieTYT Quello principale che viene in primo piano è l' oggetto di accesso ai dati che serve a nascondere il database dal resto dell'applicazione. Vedi anche Livello di accesso ai dati e Mappatura relazionale degli oggetti

Quando penso a quegli schemi, sento che hanno un livello di astrazione più alto di quello che sta chiedendo. Diciamo che hai un modo per ottenere un Rootda un Dao. Ma poi ti rendi conto che vuoi anche un modo per ottenere un Nodesenza estrarre l'intero Rootoggetto con esso. Come si fa a far sì che il RootDao chiami il Nodecodice Dao (cioè: riutilizzare), ma assicurarsi che il NodeDao chiuda la connessione solo quando il NodeDao viene chiamato direttamente e mantiene aperta la connessione quando Rootviene chiamato il Dao?
Daniel Kaplan,

1
Volevo solo aggiungere che se non si è in modalità di commit automatico, il passaggio di una connessione potrebbe portare a una situazione in cui un oggetto aggiorna il database, quindi un altro oggetto (possibilmente non correlato) ottiene la connessione, ha un errore e finisce rollback delle modifiche del primo oggetto. Questi tipi di errori possono essere molto difficili da eseguire il debug.
TMN,

2

Il passaggio di Connectionistanze in giro di solito non è un problema, anche se nella maggior parte delle situazioni solo le implementazioni DAO dovrebbero avere a che fare con esse. Ora, con il tuo problema che riguarda le connessioni che non vengono chiuse dopo l'uso, in realtà è facile da risolvere: l' Connectionoggetto deve essere chiuso allo stesso livello in cui è aperto, cioè nello stesso metodo. Personalmente uso il seguente modello di codice:

final Connection cnx = dataSource.getConnection();
try {
    // Operations using the instance
} finally {
    cnx.close();
}

In questo modo mi assicuro che tutte le connessioni siano sempre chiuse, anche se viene generata un'eccezione all'interno del blocco. In realtà vado fino a quando utilizzo esattamente lo stesso schema Statemente ResultSetistanze, e tutto è andato liscio fino ad ora.

Modifica 29-03-2018: come indicato dall'utente1156544 nei commenti seguenti, a partire da Java 7 dovrebbe essere preferito l'uso del costrutto try-with-resources. Usandolo, il modello di codice che ho fornito nella mia risposta iniziale può essere semplificato in questo modo:

try (final Connection cnx = dataSource.getConnection()) {
    // Operations using the instance
}

1
Uso qualcosa di simile. Ho la funzione doInTransaction (attività DbTask), in cui DbTask è la mia interfaccia con il metodo con parametro di connessione. doInTransaction ottiene la connessione, chiama task e commit (o rollback in caso di eccezione) e chiude quella connessione.
user470365,

a giudicare dal tuo esempio, significherebbe che l'oggetto DataSource è un singleton?
ipohfly,

@ipohfly In realtà avrei dovuto nominare quell'oggetto dataSourcepiuttosto che DataSource(aggiusterò la mia risposta su quel punto). Il tipo esatto di quell'oggetto sarebbe javax.sql.DataSource. Nel vecchio codice avevo un singleton che gestiva tutte le fonti di dati disponibili all'interno delle mie applicazioni. I miei DAO non dovevano saperlo, poiché l' DataSourceistanza viene fornita tramite iniezione di dipendenza.
KevinLH,

Se usi questo schema, usa meglio le risorse di
prova

Quando ho risposto, non stavo ancora usando Java 7. Ma hai ragione che questo dovrebbe essere il modo preferito in questi giorni. Aggiornerò la mia risposta per includere il tuo suggerimento.
KevinLH,

0

c'è un compromesso nel fare le cose in questo modo piuttosto che usare un singleton che puoi ottenere se necessario. Ho fatto le cose in entrambi i modi in passato.

In generale, è necessario considerare le conseguenze della gestione della connessione al database e ciò può essere o meno ortogonale all'utilizzo delle query del database. Ad esempio, se si dispone di una connessione db per una determinata istanza dell'applicazione e viene chiusa quando non in uso, sarebbe ortogonale. Metti il ​​management in una classe singleton e non passarlo in giro. Ciò ti consente di gestire la connessione db di cui hai bisogno. Ad esempio, se si desidera chiudere una connessione su ogni commit (e riaprirlo alla chiamata successiva), è più semplice eseguire un singleton perché l'API per questo può essere centralizzata.

D'altra parte, supponiamo che sia necessario gestire un pool di connessioni in cui una determinata chiamata potrebbe dover utilizzare qualsiasi connessione arbitraria. Ciò può accadere quando si eseguono transazioni distribuite su più server, ad esempio. In questo caso, di solito è molto meglio passare l'oggetto connessione db piuttosto che lavorare con i singoli. Penso che questo sia di solito il caso più raro, ma non c'è niente di sbagliato nel farlo quando è necessario.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.