Защо са необходими транзакции

Много често, когато работите с база данни, възниква ситуация, когато трябва да извършите много различни действия, но те имат смисъл само заедно.

Например, ние пишем банков софтуер, който трябва да прави три неща:

  • Tagлене на пари от сметката на клиента
  • Добавете пари към сметката на получателя
  • Запишете данните за публикуване в „дневник на публикуване“

Ако възникне грешка по време на изпълнението на някое от тези действия, тогава другите две също трябва да бъдат отменени. Невъзможно е да отпишете пари от клиента и да не ги добавите към получателя? Е, or да добавите към получателя, но не и да отпишете от клиента?

И така, такова логическо групиране на различни действия в едно се нарича транзакция . С други думи, транзакцията е група от действия, които трябва да се извършват само всички заедно . Ако някое действие е неуспешно or е изпълнено с грешка, тогава всички други действия трябва да бъдат отменени.

Една транзакция обикновено има три състояния:

  • начално състояние - състоянието на системата преди изпълнение на група от действия
  • успех състояние - състояние след завършване на групата действия
  • провалена държава - нещо се обърка

В този случай обикновено има три команди:

  • start/start - изпълнява се преди началото на логическата група действия
  • commit - изпълнява се след групата за действие на транзакция
  • връщане назад - стартира процеса на връщане на системата от неуспешно състояние в първоначално състояние

Работи така.

Първо трябва да отворите транзакция - извикайте метода begin() or start() . Извикването на този метод показва състоянието на системата, към което ще се опитаме да се върнем, ако нещо се обърка.

След това се извършват всички действия, които се обединяват в логическа група - транзакция.

Тогава методът commit() се извиква . Неговото извикване маркира края на логическа група от действия и обикновено започва процеса на прилагане на тези действия на практика.

Спомнете си How написахме нещо във FileWriter: първо всичко, което написахме, се съхранява в паметта, а след това, когато се извика методът flush () , всички данни от буфера в паметта се записват на диска. Този flush() е ангажиментът на транзакцията.

Е, ако е възникнала грешка по време на операцията на транзакцията, тогава трябва да започнете процеса на връщане към началното състояние. Този процес се нарича rollback() и обикновено за него отговаря методът със същото име.

Грубо казано, има 2 начина за извършване на транзакция:

  • COMMIT - потвърждаваме всички напequalsи промени
  • ROLLBACK - връщане назад на всички напequalsи промени

Транзакции в JDBC

Почти всяка СУБД може да работи с транзакции. Така че JDBC също има поддръжка за този случай. Всичко се изпълнява много просто.

Първо, всяко извикване на метода execute() на обекта Statement се изпълнява в отделна транзакция. За да направите това, Connection има параметър AutoCommit . Ако е зададено на true , тогава commit() ще се извиква след всяко извикване на метода execute() .

Второ, ако искате да изпълните няколко команди в една транзакция, можете да го направите по следния начин:

  • деактивирайте AutoCommit
  • извикване на нашите команди
  • извикайте изрично метода 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  employee SET salary = salary+1000");

connection.commit();

Ако възникне грешка на сървъра, докато се изпълнява методът commit() , тогава SQL сървърът ще отмени и трите действия.

Но има ситуации, когато грешката все още възниква от страна на клиента и никога не сме стигнали до извикването на метода 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();

Ако възникне грешка по време на изпълнението на един executeUpdate() , тогава методът commit() няма да бъде извикан. За да отмените всички предприети действия, трябва да извикате метода rollback() . Обикновено изглежда така:

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();
}

Точки за запазване

С появата на JDBC 3.0 стана възможно да се работи по-ефективно с връщане назад на транзакции. Сега можете да зададете точки за запазване - запишете точки и когато извикате операцията за връщане назад () , върнете се до конкретна точка за запазване.

За да запазите, трябва да създадете точка за запис, това става с командата:

Savepoint save = connection.setSavepoint();

Връщането към точка за запис се извършва с командата:

connection.rollback(save);

Нека опитаме да добавим точка за запис преди нашата проблемна команда:

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();
}

Организирахме вложени транзакции, като добавихме точка за запазване, преди да извикаме проблемния метод, и се върнахме към запазеното състояние, като извикаме метода за връщане (запазване) .

Да, много е подобно на запазване/зареждане в игрите.