Połączenie z bazą danych - czy powinny być przekazywane jako parametr?

11

Mamy system, w którym połączenie z bazą danych jest uzyskiwane raz za pomocą wspólnej metody i przekazywane przez odpowiednią klasę, która ma być używana. Istnieją wątpliwości, że przekazanie połączenia z bazą danych jako parametru do różnych klas spowodowałoby problem, więc sprawdzam tutaj, czy jest to faktycznie wykonalne, i czy są jakieś lepsze wzorce, aby to zrobić?

Wiem, że istnieją narzędzia ORM do uporczywości, ale nie możemy się w to zagłębić, ale ..

Wszelkie opinie są mile widziane, dziękuję.

ipohfly
źródło
Jakie problemy masz na myśli? Kto ma te wątpliwości? (Nie, jak sądzę.)
Greg Hewgill
1
Problemy takie jak powodowanie przez programistę zapominania o zamknięciu połączenia, na ogół próbuję tylko sprawdzić, czy dobrą praktyką jest przekazywanie połączenia bazy danych do różnych metod jako parametru. Wątpliwości pochodzą od innego programisty.
ipohfly

Odpowiedzi:

8

Tak, można bezpiecznie ominąć połączenie. Obsługujesz połączenie w zewnętrznym bloku sterującym. Nie ma w tym nic niebezpiecznego.

Niebezpieczne jest pisanie kodu, który nie gwarantuje, że połączenie zostanie odpowiednio usunięte w odpowiednim czasie. Zapominanie o oczyszczeniu zasobu nie ma związku z przekazywaniem go. Równie łatwo możesz napisać kod, który pozostawia zawieszone połączenie, nie przekazując go nigdzie.

W C ++ jesteś chroniony przez RAII, jeśli alokujesz na stosie lub używasz inteligentnych wskaźników. W języku C # ustal twardą zasadę, że wszystkie obiekty jednorazowego użytku (takie jak połączenia) powinny być zadeklarowane w bloku „używającym”. W Javie posprzątaj z logiką try-wreszcie. Dokonaj recenzji kodu dla wszystkich kodów warstwy danych, aby to zapewnić.

Najczęstszym przypadkiem użycia jest kilka operacji, które można łączyć w wiele kombinacji. I każda z tych permutacji musi być transakcją atomową (wszystkie zakończone powodzeniem lub wycofaniem). następnie musisz przekazać transakcję (a zatem odpowiednie połączenie) do wszystkich metod.

Załóżmy, że mamy wiele akcji foobar (), które można łączyć na różne sposoby jako transakcje atomowe.

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

BTW, będziesz chciał otwierać połączenia tak późno, jak to możliwe, usuń je jak najszybciej. Twoi koledzy z drużyny mogą mieć rację, jeśli traktujesz połączenia jako elementy obiektu, wprowadzając je jako niepotrzebny stan i pozostawiając połączenia otwarte dłużej niż to konieczne. Ale przekazanie połączenia lub transakcji jako parametru nie jest z natury złe.

BTW. W zależności od wsparcia języka dla funkcji pierwszej klasy, możesz podjąć listę działań foobar (). Tak więc jedna funkcja może obsłużyć wszystkie kombinacje akcji. Eliminowanie duplikacji zewnętrznego bloku kontrolnego dla każdej permutacji.

mike30
źródło
oznaczając to jako odpowiedź, ponieważ daje mi to więcej informacji na temat
obecnej
6

Brzmi jakbyś chciał wstrzyknięciu zależności . Oznacza to, że połączone połączenie jest tworzone raz i wprowadzane tam, gdzie jest potrzebne. Oczywiście przekazywanie połączenia za pomocą parametru metody jest jednym ze sposobów wstrzykiwania zależności, ale kontener IoC, taki jak Guice, PicoContainer lub Spring, jest innym (bezpieczniejszym) sposobem, aby to zrobić.

Korzystanie z DI oznacza, że ​​możesz starannie zamknąć logikę wokół tworzenia, otwierania, używania i zamykania połączenia - z dala od podstawowej logiki biznesowej.

Spring JDBC i in. Są innymi przykładami wykonywania tego rodzaju zachowania za Ciebie

Martijn Verburg
źródło
Emm tak naprawdę nie patrzy na zastrzyk zależności. Próbuję tylko dowiedzieć się, czy ogólnie jest to dobrą praktyką, a jeśli nie, jaki jest lepszy sposób zarządzania połączeniem z bazą danych (DI to jednak jeden ze sposobów).
ipohfly
-1. Jedno połączenie nie pasuje do systemu dla wielu użytkowników. Może się wydawać, że działa z powodu małej liczby użytkowników i szybkiego wykonania. W przypadku puli lepiej jest utworzyć instancję obiektu połączenia na akcję, nawet w systemie jednego użytkownika.
mike30
2

Przekazywanie elementów bazy danych zamiast danych może prowadzić do problemów. Do tego stopnia, o ile jest to praktyczne, nie przechodź przez bazę danych, chyba że można zagwarantować odpowiednią higienę bazy danych.

Problem z przekazywaniem rzeczy do bazy danych polega na tym, że może być niechlujny. Widziałem więcej niż jeden błąd w kodzie, gdy ktoś przechodził wokół połączenia z bazą danych, który następnie pobiera zestaw wyników i ukrywa w obiekcie lokalnym (zestaw wyników, nadal podłączony do bazy danych), a następnie wiąże kursor w baza danych przez długi czas. W innym przypadku ktoś przekazał zestaw wyników komuś innemu (który został następnie ukryty), a następnie metoda, która przekazała zestaw wyników, zamknęła go (i instrukcję), co prowadziło do błędów, gdy inne metody próbowały pracować z zestawem wyników, który już nie był.

Wszystko to wynika z nieprzestrzegania bazy danych, połączenia, instrukcji, zestawu wyników i ich cykli życia.

Aby tego uniknąć, istnieją istniejące wzorce i struktury, które lepiej się bawią z bazami danych i nie wymagają, aby baza danych musiała wychodzić z klas, w których są zamknięte. Dane wchodzą, dane wychodzą, baza pozostaje.


źródło
1
Połączenia +1 db powinny mieć możliwie najkrótszy czas. Otwórz, użyj, zamknij jak najszybciej. Obecnie istnieje wiele implementacji puli połączeń, więc użycie połączenia dla wielu operacji to fałszywa ekonomia. I zaproszenie na błędy lub problemy z wydajnością (trzymanie blokad na tabelach, korzystanie z zasobów połączenia)
jqa
Jakie są nazwy niektórych z tych istniejących wzorów i struktur?
Daniel Kaplan
@tieTYT Najważniejsze z nich to obiekt dostępu do danych, który służy do ukrywania bazy danych przed resztą aplikacji. Zobacz także Mapowanie
Kiedy myślę o tych wzorach, czuję, że mają wyższy poziom abstrakcji niż to, o co prosi. Powiedzmy, że masz sposób na uzyskanie Rootz Dao. Ale wtedy zdajesz sobie sprawę, że chcesz również uzyskać sposób, Nodenie wyciągając przy tym całego Rootobiektu. W jaki sposób sprawiasz, że Rootdao wywołuje Nodekod dao (tj .: ponowne użycie), ale upewnij się, że Nodedao zamyka połączenie tylko wtedy, gdy Nodedao jest wywoływane bezpośrednio, i utrzymuje połączenie otwarte, gdy Rootwywoływane jest dao?
Daniel Kaplan
1
Chciałem tylko dodać, że jeśli nie jesteś w trybie automatycznego zatwierdzania, przekazywanie połączenia może doprowadzić do sytuacji, w której jeden obiekt aktualizuje bazę danych, a następnie inny (być może niezwiązany) obiekt otrzymuje połączenie, ma błąd i kończy się wycofywanie zmian pierwszego obiektu. Tego rodzaju błędy mogą być bardzo trudne do debugowania.
TMN
2

Przekazywanie Connectioninstancji zwykle nie stanowi problemu, chociaż w większości sytuacji tylko implementacje DAO powinny mieć z nimi coś wspólnego. Teraz, gdy problem polega na tym, że połączenia nie są zamykane po użyciu, w rzeczywistości jest to łatwe do naprawienia: Connectionobiekt musi zostać zamknięty na tym samym poziomie, na którym jest otwarty, tj. Tą samą metodą. Osobiście używam następującego wzorca kodu:

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

W ten sposób zapewniam, że wszystkie połączenia są zawsze zamknięte, nawet jeśli wyjątek zostanie zgłoszony w bloku. Właściwie pracuję tak długo, jak używam dokładnie tego samego wzoru Statementi ResultSetinstancji, a do tej pory wszystko przebiegało płynnie.

Edytuj 2018-03-29: Jak wskazał użytkownik1156544 w poniższych komentarzach, od wersji Java 7 należy preferować konstrukcję try-with-resources. Korzystając z niego, wzór kodu podany w mojej początkowej odpowiedzi można uprościć w następujący sposób:

try (final Connection cnx = dataSource.getConnection()) {
    // Operations using the instance
}
KevinLH
źródło
1
Używam czegoś podobnego. Mam funkcję doInTransaction (zadanie DbTask), gdzie DbTask to mój interfejs z metodą z parametrem połączenia. doInTransaction uzyskuje połączenie, wywołuje zadanie i zatwierdza (lub wycofuje, jeśli był wyjątek) i zamyka to połączenie.
user470365
sądząc z twojego przykładu, oznaczałoby to, że obiekt DataSource jest singletonem?
ipohfly,
@ipohfly Właściwie powinienem był nazwać ten obiekt, dataSourcea nie DataSource(naprawię moją odpowiedź dotyczącą tego punktu). Dokładny typ tego obiektu to javax.sql.DataSource. W starym kodzie miałem singletona do zarządzania wszystkimi dostępnymi źródłami danych w moich aplikacjach. Moje DAO nie musiały o tym wiedzieć, ponieważ DataSourceinstancja jest zapewniona przez wstrzyknięcie zależności.
KevinLH
Jeśli korzystasz z tego schematu, lepiej korzystaj z try-with-resources
1156544
Kiedy odpowiedziałem, nie korzystałem jeszcze z Java 7. Ale masz rację, że w dzisiejszych czasach powinien to być preferowany sposób. Zaktualizuję moją odpowiedź, aby uwzględnić Twoją sugestię.
KevinLH
0

istnieje kompromis w robieniu rzeczy w ten sposób, zamiast korzystania z singletonu, który można uzyskać w razie potrzeby. W przeszłości robiłem to na dwa sposoby.

Ogólnie rzecz biorąc, musisz pomyśleć o konsekwencjach zarządzania połączeniami z bazą danych, a może to być ortogonalne w stosunku do użycia zapytań do bazy danych. Na przykład, jeśli masz jedno połączenie db dla danej instancji aplikacji i zostanie ono zamknięte, gdy nie będzie używane, byłoby to ortogonalne. Umieść kierownictwo w klasie singleton i nie pomijaj go. Umożliwia to zarządzanie połączeniem db według potrzeb. Na przykład, jeśli chcesz zamknąć połączenie przy każdym zatwierdzeniu (i ponownie otworzyć przy następnym wywołaniu), łatwiej jest to zrobić w singletonie, ponieważ API do tego celu można scentralizować.

Z drugiej strony załóżmy, że musisz zarządzać pulą połączeń, w której dane połączenie może wymagać dowolnego dowolnego połączenia. Może się to zdarzyć na przykład podczas wykonywania transakcji rozproszonych na wielu serwerach. W takim przypadku zwykle lepiej jest przekazywać obiekt połączenia db niż praca z singletonami. Myślę, że jest to zwykle rzadszy przypadek, ale nie ma nic złego w robieniu tego, kiedy trzeba.

Chris Travers
źródło