# Hibernate 4 的并发控制

> 原文： [https://javabeginnerstutorial.com/hibernate/concurrency-control-with-hibernate-4/](https://javabeginnerstutorial.com/hibernate/concurrency-control-with-hibernate-4/)

在大多数情况下，仅让数据库执行并发控制工作是可以的，但是有时您会遇到需要接管的应用。

在本文中，我将简要介绍**乐观**和**悲观**并发控制。

## 乐观并发控制

高并发应用中唯一一致的方法是带有版本控制的乐观并发控制。 此方法使用版本号或时间戳来检测冲突并防止更新丢失。

### 应用版本检查

使用这种方法，应用必须手动维护实体的版本。 这意味着开发人员有责任在操作它们之前从数据库加载实际的实体状态。 当对象被 Hibernate 刷新时，版本会自动增加-因此开发人员不需要增加此属性。

如果您的应用具有低并发性并跳过版本控制，则可以使用此方法。 在这种情况下，总是最后一次提交获胜，并且根据该状态更新对象。 这就是为什么**总是**需要在操作数据库之前先从数据库中加载实体的实际状态。

### 自动版本控制

要使用自动版本控制，只需在您希望在乐观锁定的版本控制下拥有的实体中添加一个字段或方法，然后使用 [[受电子邮件保护]](/cdn-cgi/l/email-protection) 注解对其进行注解。 如文档所述，此注解可用于以下类型：`int`，`Integer`，`short`，`Short`，`long`，`Long`，`java.sql.Timestamp`。

如果在加载实体和将其刷新回数据库之间发生一些更新，则会从 Hibernate 收到一条错误消息：

线程“主”中的异常`org.hibernate.StaleObjectStateException`：行已由另一个事务更新或删除（或未保存值的映射不正确）：`[hibernate_example.joined.Book#1]`

## 悲观并发控制

正如我上次已经提到的那样，Hibernate 不会锁定内存中的对象，它将始终使用基础数据库的锁定机制。

但是， `LockMode`类定义了一些可由 Hibernate 获得的机制：

*   `WRITE`：在 Hibernate 更新或插入行时自动获取
*   `UPGRADE`：可以在明确的用户请求下使用`SELECT…FOR UPDATE`在支持此语法的数据库上获取
*   `UPGRADE_NOWAIT`：可在 Oracle 下使用`SELECT…FOR UPDATE NOWAIT`根据明确的用户请求获取
*   `READ`：Hibernate 读取数据时自动获取
*   `NONE`：表示没有锁，在事务结束时所有对象都切换到此锁模式
*   `PESSIMISTIC_FORCE_INCREMENT`：加载实体时强制增加版本。

上面提到的“显式用户请求”可以表示为以下调用之一：

*   为`LockOptions`参数指定`LockMode`的`load()`
*   `buildLockRequest()`
*   `setLockMode()`

## 设置锁定模式的示例

上面我已经提到了一些锁定模式以及如何设置它们，现在是时候看看一些示例代码来了解它们的作用了。

```java
getBooks(session).stream().forEach(b -> session.load(Book.class, b.getId(), 
new LockOptions(LockMode.PESSIMISTIC_FORCE_INCREMENT)));
 getBooks(session).stream().forEach(System.out::println);
```

上面的代码块有两件事：从数据库加载实体，设置一种用于强制版本增加的锁定模式，然后将书籍打印到控制台。 结果将如下所示：

```java
Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29\. 0:00 [1]
 Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29\. 0:00 [1]
 Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29\. 0:00 [1]
 Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29\. 16:14 [1]
```

每行末尾的方括号包含实体的版本号。

如果我们想将锁定移动到`getBooks()`方法中，可以执行以下操作：

```java
private static List<Book> getBooks(Session session) {
    final Query query = session.createQuery("from Book b");
    query.setLockMode("b", LockMode.PESSIMISTIC_FORCE_INCREMENT);
    return query.list();
 }
```

有趣的是`setLockMode`方法的第一个字符串参数：它是实体使用此锁定的别名，您必须在查询的`FROM`块中使用此别名。

使用此仓库方法加载书籍后，将书籍打印到控制台的结果可能是这样的：

```java
Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29\. 0:00 [1]
 Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29\. 0:00 [1]
 Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29\. 0:00 [1]
 Java 8 in Action by Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft (ISBN: 9781617291999), published 2015.07.29\. 16:15 [1]
```

### 一个有趣的事实

版本号仅在当前会话中更新，直到您更新实体并将其保存到数据库为止。

如果不这样做，您会在应用中看到版本增量，但是它们不会保存到数据库中，即使您使用某些`*_FORCE_INCREMENT`策略。

```java
| 1 |   Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft | 9781617291999 | 2015-07-29 | Java 8 in Action | 0 |
 | 2 | Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft | 9781617291999 |   2015-07-29 | Java 8 in Action | 0 |
 | 3 | Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft | 9781617291999 |   2015-07-29 | Java 8 in Action | 0 |
 | 4 | Raoul-Gabriel Urma, Mario Fusco, Alan Mycroft | 9781617291999 |   2015-07-29 | Java 8 in Action | 0 |
```

如果我们不更新旧条目，则上面的块显示了数据库中的实体。 这些列从左到右如下：ID，作者，ISBN，出版，标题和版本。

要进行一些版本更改，请取消注释示例中的代码块，然后运行应用。 该块将书籍的日期更新为当前日期，并将实体保存回数据库中。 为此，您需要进行事务。

要开始事务，只需调用`session.beginTransaction();`，如果完成调用`session.getTransaction().commite()`， 完成并写入数据库或`session.getTransaction().rollback()`以还原此事务中所做的所有更改。 或者，您可以存储由`beginTransaction()`方法返回的*事务*对象，并在此*事务*实例上调用`commit()`或`rollback()`。

## 一些事务方式

如果我提到了事务，请允许我给您一些模式（甚至是反模式），以了解通常如何处理事务。

### 每个操作的会话（反模式）

这是一种反模式，因为如果使用此事务管理方法，则将为每个数据库调用打开和关闭会话。 即使您使用数据库的*自动提交*功能（在每次调用数据库后隐式调用*提交*），此操作也会完成。

### 每个请求的会话

这是用于事务的最常见模式。 名称中的**请求**与其中有来自客户端/用户（例如 Web 应用）的许多请求的系统有关。

常见的工作流程是，当此类请求到达系统时，将打开 Hibernate *会话*，并保持打开状态，直到处理该请求的信息（更新存储的信息或检索要显示的内容）为止。

如果使用此模式，则在大多数情况下可以减少数据丢失，因为信息会连续保存在数据库中。

### 每个应用的会话

开发人员不同意这是模式还是反模式，因为有时应用很小，您确实可以打开一个事务并在最后调用`commit`。 但是，对于大型应用而言，这是一种太糟糕的方法，在大型应用中，最终的应用故障会导致数据丢失，或者如果存在并发用户并且数据相关，则这些用户之间将无法处于同步状态。

## 总结

锁定可能会有些麻烦，但是 Hibernate 会通过各种锁定机制为我们提供帮助和帮助。 如果您想微调内置解决方案，也可以使用上述方法进行。

我们稍微深入研究事务，只是为了了解如何通常使用设计模式进行处理，以了解如何将数据存储在数据库中并保留这些信息。 您可以[从此处下载代码](https://github.com/JBTAdmin/Hibernate)。

