Dlaczego potrzebne są transakcje

Bardzo często podczas pracy z bazą danych dochodzi do sytuacji, kiedy trzeba wykonać wiele różnych czynności, ale tylko razem mają one sens.

Na przykład piszemy oprogramowanie bankowe, które powinno robić trzy rzeczy:

  • Wypłać pieniądze z konta klienta
  • Dodaj pieniądze do konta odbiorcy
  • Zapisz dane o wysłaniu w „dzienniku wysyłania”

Jeśli podczas wykonywania którejkolwiek z tych akcji wystąpi błąd, pozostałe dwie również muszą zostać anulowane. Nie da się odpisać pieniędzy od klienta i nie dodać do odbiorcy? Cóż, czy dodać do odbiorcy, ale nie odpisać od klienta?

Tak więc takie logiczne zgrupowanie różnych działań w jedną nazywa się transakcją . Innymi słowy, transakcja to grupa działań, które muszą być wykonane tylko razem . Jeśli jakakolwiek akcja nie powiodła się lub została wykonana z błędem, wszystkie inne akcje muszą zostać anulowane.

Transakcja ma zwykle trzy stany:

  • stan początkowy - stan systemu przed wykonaniem grupy akcji
  • stan sukcesu - stan po zakończeniu grupy akcji
  • stan nieudany - coś poszło nie tak

W takim przypadku zwykle są trzy polecenia:

  • begin/start - wykonywany przed rozpoczęciem logicznej grupy akcji
  • zatwierdzenie - wykonywane po grupie akcji transakcji
  • rollback - rozpoczyna proces przywracania systemu ze stanu awarii do stanu początkowego

To działa tak.

Najpierw musisz otworzyć transakcję - wywołaj metodę begin() lub start() . Wywołanie tej metody wskazuje stan systemu, do którego będziemy próbowali wrócić, jeśli coś pójdzie nie tak.

Następnie wykonywane są wszystkie akcje, które są łączone w logiczną grupę - transakcję.

Następnie wywoływana jest metoda commit() . Jego wezwanie oznacza koniec logicznej grupy działań, a także zwykle rozpoczyna proces wprowadzania tych działań w życie.

Przypomnij sobie, jak napisaliśmy coś w FileWriter: najpierw wszystko, co napisaliśmy, jest przechowywane w pamięci, a następnie, gdy wywoływana jest metoda flush () , wszystkie dane z bufora w pamięci są zapisywane na dysku. To flush() jest zatwierdzeniem transakcji.

Otóż ​​jeśli podczas operacji transakcji wystąpił błąd, to należy zainicjować proces powrotu do stanu początkowego. Proces ten nazywa się rollback() i zwykle odpowiada za niego metoda o tej samej nazwie.

Z grubsza rzecz biorąc, istnieją 2 sposoby zakończenia transakcji:

  • COMMIT - potwierdzamy wszystkie wprowadzone zmiany
  • ROLLBACK - cofnij wszystkie wprowadzone zmiany

Transakcje w JDBC

Prawie każdy DBMS może pracować z transakcjami. Więc JDBC ma również wsparcie dla tego przypadku. Wszystko jest realizowane bardzo prosto.

Po pierwsze, każde wywołanie metody execute() obiektu Statement jest wykonywane w oddzielnej transakcji. Aby to zrobić, Connection ma parametr AutoCommit . Jeśli jest ustawiona na true , to commit() będzie wywoływana po każdym wywołaniu metody execute() .

Po drugie, jeśli chcesz wykonać kilka poleceń w jednej transakcji, możesz to zrobić w ten sposób:

  • wyłącz automatyczne zatwierdzanie
  • dzwoniąc do naszych zespołów
  • wywołaj jawnie metodę commit().

Wygląda to bardzo prosto:

connection.setAutoCommit(false);

Statement statement = connection.createStatement();
int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount3 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");

connection.commit();

Jeśli podczas działania metody commit() na serwerze wystąpi błąd , serwer SQL anuluje wszystkie trzy akcje.

Ale zdarzają się sytuacje, w których błąd nadal występuje po stronie klienta i nigdy nie dotarliśmy do wywołania metody commit() :

connection.setAutoCommit(false);

Statement statement = connection.createStatement();
int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
int rowsCount3 = statement.executeUpdate("UPDATE multiple typos will result in an exception");

connection.commit();

Jeśli podczas wykonywania jednej funkcji executeUpdate() wystąpi błąd , metoda commit() nie zostanie wywołana. Aby cofnąć wszystkie podjęte działania, należy wywołać metodę rollback() . Zwykle wygląda to tak:

try{
  	connection.setAutoCommit(false);

  	Statement statement = connection.createStatement();
  	int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount3 = statement.executeUpdate("UPDATE multiple typos will result in an exception");

	  connection.commit();
 }
 catch (Exception e) {
   connection.rollback();
}

Punkty zapisu

Wraz z pojawieniem się JDBC 3.0 stała się możliwa bardziej wydajna praca z wycofywaniem transakcji. Teraz możesz ustawić punkty zapisu - punkty zapisu, a kiedy wywołasz operację rollback () , cofnij się do określonego punktu zapisu.

Aby zapisać, musisz utworzyć punkt zapisu, odbywa się to za pomocą polecenia:

Savepoint save = connection.setSavepoint();

Powrót do punktu zapisu odbywa się za pomocą polecenia:

connection.rollback(save);

Spróbujmy dodać punkt zapisu przed naszym problematycznym poleceniem:

try{
  	connection.setAutoCommit(false);

  	Statement statement = connection.createStatement();
  	int rowsCount1 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");
  	int rowsCount2 = statement.executeUpdate("UPDATE  employee SET salary = salary+1000");

  	Savepoint save = connection.setSavepoint();
 	 try{
      	int rowsCount3 = statement.executeUpdate("UPDATE multiple typos will result in an exception");
 	 }
 	 catch (Exception e) {
    	   connection.rollback(save);
 	 }

	  connection.commit();
 }
 catch (Exception e) {
   connection.rollback();
}

Zorganizowaliśmy zagnieżdżone transakcje, dodając punkt zapisu przed wywołaniem problematycznej metody i powracając do zapisanego stanu, wywołując metodę rollback(save) .

Tak, jest to bardzo podobne do zapisywania/ładowania w grach.