Datenbankverbindung - sollten sie als Parameter übergeben werden?


11

Wir haben ein System, bei dem die Datenbankverbindung einmal mit einer gemeinsamen Methode hergestellt und in der jeweiligen zu verwendenden Klasse übergeben wird. Es gibt Zweifel, dass das Übergeben der Datenbankverbindung als Parameter an verschiedene Klassen zu Problemen führen würde. Daher überprüfe ich hier, ob dies tatsächlich möglich ist, und gibt es dafür bessere Muster?

Ich weiß, dass es einige ORM-Tools gibt, um die Persistenz zu gewährleisten, aber darauf können wir noch nicht eingehen.

Jedes Feedback ist willkommen, danke.


Auf welche Art von Problemen beziehen Sie sich? Wer hat diese Zweifel? (Nicht Sie, nehme ich an.)
Greg Hewgill

1
Probleme wie das Vergessen des Entwicklers, die Verbindung zu schließen, im Allgemeinen versuche ich nur zu sehen, ob es eine gute Praxis ist, die Datenbankverbindung als Parameter an verschiedene Methoden weiterzugeben. Ja, die Zweifel kommen von einem anderen Entwickler.
Ipohfly

Antworten:


8

Ja, es ist sicher, eine Verbindung weiterzugeben. Sie behandeln die Verbindung in einem äußeren Steuerblock. Daran ist nichts Unsicheres.

Was unsicher ist, ist das Schreiben von Code, der nicht garantiert, dass die Verbindung rechtzeitig ordnungsgemäß entsorgt wird. Das Vergessen, eine Ressource zu bereinigen, hat nichts mit dem Weitergeben zu tun. Sie können genauso gut Code schreiben, der eine hängende Verbindung hinterlässt, ohne sie irgendwo weiterzugeben.

In C ++ sind Sie durch RAII geschützt, wenn Sie auf dem Stapel zuweisen oder intelligente Zeiger verwenden. Machen Sie in C # eine harte Regel, dass alle verfügbaren Objekte (wie Verbindungen) in einem "using" -Block deklariert werden. In Java mit Try-finally-Logik aufräumen. Führen Sie Codeüberprüfungen für alle Datenschichtcodes durch, um dies sicherzustellen.

Der häufigste Anwendungsfall ist, wenn Sie mehrere Operationen haben, die in vielen Permutationen kombiniert werden können. Und jede dieser Permutationen muss eine Atomtransaktion sein (alle erfolgreich oder Rollback). Dann müssen Sie die Transaktion (und damit die entsprechende Verbindung) an alle Methoden weitergeben.

Angenommen, wir haben viele foobar () -Aktionen, die auf verschiedene Arten als Atomtransaktionen kombiniert werden können.

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

Übrigens möchten Sie Verbindungen so spät wie möglich öffnen und sie so schnell wie möglich entsorgen. Ihre Teamkollegen könnten Recht haben, wenn Sie Verbindungen als Objektmitglieder behandeln, sie als unnötigen Status einführen und Verbindungen viel länger als nötig offen lassen. Das Übergeben einer Verbindung oder Transaktion als Parameter ist jedoch nicht von Natur aus falsch.

Übrigens. Abhängig von der Unterstützung Ihrer Sprache für erstklassige Funktionen können Sie eine Liste von foobar () - Aktionen ausführen. Eine Funktion könnte also alle Permutationen der Aktionen verarbeiten. Eliminieren der Duplizierung des äußeren Steuerblocks für jede Permutation.


Markieren Sie dies als Antwort, da es mir mehr Vorstellung davon gibt, wie die Situation ist
ipohfly

6

Es hört sich so an, als wären Sie hinter der Abhängigkeitsinjektion her . Das heißt, die gepoolte Verbindung wird einmal erstellt und dort eingefügt, wo sie benötigt wird. Das Übergeben der Verbindung über einen Methodenparameter ist sicherlich eine Möglichkeit, Abhängigkeiten zu injizieren, aber ein IoC-Container wie Guice, PicoContainer oder Spring ist eine andere (sicherere) Möglichkeit, dies zu tun.

Mit DI können Sie die Logik rund um das Erstellen, Öffnen, Verwenden und Schließen der Verbindung ordentlich zusammenfassen - weg von Ihrer Kerngeschäftslogik.

Spring JDBC et al. Sind weitere Beispiele für die Durchführung dieser Art von Verhalten für Sie


Emm schaut nicht wirklich auf Abhängigkeitsinjektion. Ich versuche nur herauszufinden, ob dies im Allgemeinen eine gute Vorgehensweise ist, und wenn nicht, wie lässt sich die Datenbankverbindung besser verwalten (DI ist jedoch eine Möglichkeit, dies zu tun).
Ipohfly

-1. Eine Verbindung passt nicht zu einem Mehrbenutzersystem. Es scheint aufgrund des geringen Benutzervolumens und der schnellen Ausführung zu funktionieren. Beim Pooling ist es besser, ein Verbindungsobjekt pro Aktion auch in einem einzelnen Benutzersystem zu instanziieren.
Mike30

2

Das Weitergeben von Datenbank-Dingen anstelle von Daten-Dingen kann zu Problemen führen. Insofern, wann immer es praktikabel ist, übergeben Sie keine Datenbanksache, es sei denn, Sie können eine ordnungsgemäße Datenbankhygiene gewährleisten.

Das Problem beim Weitergeben von Datenbank-Dingen ist, dass es schlampig sein kann. Ich habe mehr als einen Fehler im Code mit jemandem gesehen, der eine Datenbankverbindung herumgereicht hat, dass jemand dann eine Ergebnismenge aufnimmt und in einem lokalen Objekt (der Ergebnismenge, die noch mit der Datenbank verbunden ist) versteckt und dann einen Cursor in der Datenbank für eine bedeutende Zeit. In einer anderen Instanz hat jemand eine Ergebnismenge an eine andere Person übergeben (die dann gespeichert wurde), und die Methode, die die Ergebnismenge übergeben hat, hat sie (und die Anweisung) geschlossen, was zu Fehlern führte, wenn andere Methoden versuchten, mit der Ergebnismenge zu arbeiten, die nicht mehr vorhanden war.

All dies ist darauf zurückzuführen, dass die Datenbank, die Verbindung, die Anweisung, die Ergebnismenge und ihre Lebenszyklen nicht berücksichtigt werden.

Um dies zu vermeiden, gibt es vorhandene Muster und Strukturen, die besser mit Datenbanken spielen und keine Datenbank haben, die aus den Klassen, in denen sie beschränkt sind, herauskommen muss. Daten gehen ein, Daten gehen aus, die Datenbank bleibt erhalten.


1
+1 dB-Verbindungen sollten die kürzestmögliche Zeitspanne haben. Öffne es, benutze es, schließe es so schnell wie möglich. Heutzutage gibt es viele Implementierungen von Verbindungspools, so dass die Verwendung einer Verbindung für mehrere Operationen eine falsche Wirtschaftlichkeit darstellt. Und eine Einladung für Fehler oder Leistungsprobleme (Sperren für Tabellen, Verwendung von Verbindungsressourcen)
jqa

Wie heißen einige dieser vorhandenen Muster und Strukturen?
Daniel Kaplan

@tieTYT Das wichtigste Objekt, das im Vordergrund steht, ist das Datenzugriffsobjekt , mit dem die Datenbank vor dem Rest der Anwendung ausgeblendet wird . Siehe auch Datenzugriffsschicht und

Wenn ich an diese Muster denke, habe ich das Gefühl, dass sie sich auf einer höheren Abstraktionsebene befinden als das, wonach er fragt. Angenommen, Sie haben eine Möglichkeit, Rootein Dao zu erhalten. Aber dann merkt man, dass man auch einen Weg haben will, Nodeohne das ganze RootObjekt damit herauszuziehen . Wie lässt sich das RootDao den NodeDao-Code aufrufen (dh wiederverwenden), aber sicherstellen, dass das NodeDao die Verbindung nur schließt, wenn das NodeDao direkt aufgerufen wird, und die Verbindung offen hält, wenn das RootDao aufgerufen wird?
Daniel Kaplan

1
Ich wollte nur hinzufügen, dass, wenn Sie sich nicht im Auto-Commit-Modus befinden, das Weitergeben einer Verbindung zu einer Situation führen kann, in der ein Objekt die Datenbank aktualisiert, ein anderes (möglicherweise nicht verwandtes) Objekt die Verbindung erhält, einen Fehler aufweist und beendet wird Zurücksetzen der Änderungen des ersten Objekts. Diese Art von Fehlern kann sehr schwer zu debuggen sein.
TMN

2

Das Weitergeben von ConnectionInstanzen ist normalerweise kein Problem, obwohl in den meisten Situationen nur die DAO-Implementierungen etwas damit zu tun haben sollten. Da Ihr Problem darin besteht, dass Verbindungen nach der Verwendung nicht geschlossen werden, ist es tatsächlich einfach zu beheben: Das ConnectionObjekt muss auf derselben Ebene geschlossen werden, auf der es geöffnet ist, dh auf dieselbe Weise. Ich persönlich verwende das folgende Codemuster:

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

Auf diese Weise stelle ich sicher, dass alle Verbindungen immer geschlossen sind, auch wenn eine Ausnahme innerhalb des Blocks ausgelöst wird. Ich gehe tatsächlich so lange, wie ich genau das gleiche Muster für Statementund ResultSetInstanzen verwende, und alles war bisher reibungslos.

Bearbeiten 2018-03-29: Wie von user1156544 in den Kommentaren unten angegeben, sollte ab Java 7 die Verwendung des Try-with-Resources-Konstrukts bevorzugt werden. Mit ihm kann das Codemuster, das ich in meiner ersten Antwort angegeben habe, folgendermaßen vereinfacht werden:

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

1
Ich benutze etwas ähnliches. Ich habe die Funktion doInTransaction (DbTask-Task), wobei DbTask meine Schnittstelle mit der Methode mit dem Verbindungsparameter ist. doInTransaction erhält eine Verbindung, ruft task und commit auf (oder rollback, wenn es eine Ausnahme gab) und schließt diese Verbindung.
user470365

Nach Ihrem Beispiel zu urteilen, würde dies bedeuten, dass das DataSource-Objekt ein Singleton ist?
Ipohfly

@ipohfly Eigentlich hätte ich dieses Objekt dataSourceeher benennen sollen als DataSource(ich werde meine Antwort in Bezug auf diesen Punkt korrigieren ). Der genaue Typ dieses Objekts wäre javax.sql.DataSource. In altem Code hatte ich früher einen Singleton, der alle verfügbaren Datenquellen in meinen Anwendungen verwaltete. Meine DAOs mussten dies nicht durch wissen, da die DataSourceInstanz durch Abhängigkeitsinjektion bereitgestellt wird.
KevinLH

Wenn Sie dieses Schema verwenden, verwenden Sie Try-with-Resources besser
user1156544

Als ich antwortete, benutzte ich noch nicht Java 7. Aber Sie haben Recht, dass dies heutzutage der bevorzugte Weg sein sollte. Ich werde meine Antwort aktualisieren, um Ihren Vorschlag aufzunehmen.
KevinLH

0

Es ist ein Kompromiss, Dinge auf diese Weise zu tun, anstatt einen Singleton zu verwenden, den Sie nach Bedarf erhalten können. Ich habe in der Vergangenheit Dinge in beide Richtungen getan.

Im Allgemeinen müssen Sie über die Konsequenzen der Datenbankverbindungsverwaltung nachdenken. Dies kann orthogonal zur Verwendung von Datenbankabfragen sein oder auch nicht. Wenn Sie beispielsweise eine Datenbankverbindung für eine bestimmte Anwendungsinstanz haben und diese geschlossen wird, wenn sie nicht verwendet wird, ist dies orthogonal. Stellen Sie das Management in eine Singleton-Klasse und geben Sie es nicht weiter. Auf diese Weise können Sie die Datenbankverbindung nach Bedarf verwalten. Wenn Sie beispielsweise bei jedem Commit eine Verbindung schließen (und beim nächsten Aufruf erneut öffnen) möchten, ist dies bei einem Singleton einfacher, da die API dafür zentralisiert werden kann.

Angenommen, Sie müssen einen Verbindungspool verwalten, bei dem ein bestimmter Anruf möglicherweise eine beliebige Verbindung verwenden muss. Dies kann beispielsweise bei verteilten Transaktionen auf mehreren Servern der Fall sein. In diesem Fall ist es normalerweise weitaus besser, das Datenbankverbindungsobjekt zu übergeben, als mit Singletons zu arbeiten. Ich denke, dies ist normalerweise der seltenere Fall, aber es ist nichts Falsches daran, es zu tun, wenn Sie es brauchen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.