为什么需要交易

很多时候,在使用数据库时,会出现需要执行许多不同操作的情况,但它们只有放在一起才有意义。

例如,我们正在编写的银行软件应该做三件事:

  • 从客户账户中提取资金
  • 将钱添加到收款人的帐户
  • 将投寄资料记录于「投寄纪录」

如果在执行这些操作中的任何一个期间发生错误,则其他两个操作也必须取消。不可能从客户那里注销钱而不把它加到收款人身上?好吧,还是添加到收件人,但不从客户端注销?

因此,这种将不同操作合而为一的逻辑组合称为事务。换句话说,事务是一组必须一起执行的操作。如果任何操作失败或执行出错,则必须取消所有其他操作。

一个事务通常有三种状态:

  • 初始状态——执行一组动作之前的系统状态
  • 成功状态——动作组完成后的状态
  • 失败状态 - 出了点问题

在这种情况下,通常有三个命令:

  • begin/start - 在逻辑动作组开始之前执行
  • commit - 在事务操作组之后执行
  • 回滚- 启动将系统从失败状态返回到初始状态的过程

它是这样工作的。

首先您需要打开一个事务——调用begin()start()方法。调用此方法表示出现问题时我们将尝试返回的系统状态。

然后执行所有操作,这些操作组合成一个逻辑组 - 事务。

然后调用commit()方法。它的调用标志着一组逻辑动作的结束,通常也开始将这些动作付诸实践的过程。

回想一下我们在 FileWriter 中是如何写的:首先,我们写的所有内容都存储在内存中,然后当调用flush()方法时,将内存中缓冲区中的所有数据写入磁盘。这个flush()是事务提交。

那么,如果在事务运行过程中发生了错误,那么就需要发起返回起始状态的过程。这个过程称为rollback(),同名方法通常负责它。

粗略地说,有两种方式可以完成一笔交易:

  • COMMIT - 我们确认所做的所有更改
  • ROLLBACK - 回滚所有所做的更改

JDBC 中的事务

几乎每个 DBMS 都可以处理事务。所以JDBC也有这种情况的支持。一切都非常简单地实现。

首先,对Statement 对象的execute()方法的每次调用都在单独的事务中执行。为此,Connection 有一个AutoCommit参数。如果设置为true,则每次调用execute()方法后都会调用commit()

其次,如果你想在一个事务中执行多个命令,那么你可以这样做:

  • 禁用自动提交
  • 调用我们的命令
  • 显式调用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 的出现,可以更有效地处理事务回滚。现在可以设置save points-save points,调用rollback()操作时,回滚到特定的save point。

为了保存,您需要创建一个保存点,这是通过命令完成的:

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

我们通过在调用有问题的方法之前添加一个保存点来组织嵌套事务,并通过调用rollback(save)方法返回到保存的状态。

是的,它与游戏中的保存/加载非常相似。