Вступление
Введя поддержку явной блокировки Hibernate, а также Cascade Types , пришло время проанализировать поведение CascadeType.LOCK .
Запрос блокировки Hibernate запускает внутренний LockEvent . Связанный DefaultLockEventListener может каскадно запросить блокировку для дочерних объектов блокировки.
Поскольку CascadeType.ALL также включает CascadeType.LOCK , стоит понимать, когда запрос блокировки распространяется от родительского объекта к дочернему объекту.
Время тестирования
Начнем со следующей модели сущности:
Post является родительской сущностью как связи один-к-одному PostDetail, так и отношения один-ко-многим Comment , и эти ассоциации помечаются CascadeType.ALL
| 01 02 03 04 05 06 07 08 09 10 11 12 | @OneToMany(    cascade = CascadeType.ALL,     mappedBy = "post",     orphanRemoval = true)privateList<Comment> comments = newArrayList<>();@OneToOne(    cascade = CascadeType.ALL,     mappedBy = "post",     optional = false,     fetch = FetchType.LAZY)privatePostDetails details; | 
Во всех будущих тестовых случаях будет использоваться следующий график модели сущностей:
| 01 02 03 04 05 06 07 08 09 10 | doInTransaction(session -> {    Post post = newPost();    post.setName("Hibernate Master Class");    post.addDetails(newPostDetails());    post.addComment(newComment("Good post!"));    post.addComment(newComment("Nice post!"));    session.persist(post);}); | 
Блокировка управляемых объектов
Управляемый объект загружается в текущий текущий контекст сохраняемости, и все изменения состояния объекта переводятся в операторы DML .
Когда управляемый родительский объект заблокирован:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 | doInTransaction(session -> {    Post post = (Post) session.createQuery(        "select p "+        "from Post p "+        "join fetch p.details "+        "where "+        "   p.id = :id")    .setParameter("id", 1L)    .uniqueResult();    session.buildLockRequest(        newLockOptions(LockMode.PESSIMISTIC_WRITE))    .lock(post);}); | 
Только родительская сущность блокируется, поэтому предотвращается каскад:
| 1 | select id from Post where id = 1forupdate | 
Hibernate определяет область действия LockOption , которая (согласно JavaDocs) должна позволять распространять запрос блокировки на дочерние объекты:
«Область действия» — это термин, определенный JPA. Это в основном каскадирование блокировки ассоциаций.
| 1 2 3 4 | session.buildLockRequest(    newLockOptions(LockMode.PESSIMISTIC_WRITE)).setScope(true).lock(post); | 
Установка флага области ничего не меняет, блокируется только управляемый объект:
| 1 | select id from Post where id = 1forupdate | 
Блокировка отдельных объектов
Помимо блокировки сущностей, запрос на блокировку может также повторно связать отдельные сущности. Чтобы доказать это, мы собираемся проверить граф сущности Post до и после запроса блокировки сущности:
| 01 02 03 04 05 06 07 08 09 10 11 | voidcontainsPost(Session session,     Post post, booleanexpected) {    assertEquals(expected,         session.contains(post));    assertEquals(expected,         session.contains(post.getDetails()));    for(Comment comment : post.getComments()) {        assertEquals(expected,             session.contains(comment));    }} | 
Следующий тест демонстрирует, как CascadeType.LOCK работает для отдельных объектов:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | //Load the Post entity, which will become detachedPost post = doInTransaction(session ->    (Post) session.createQuery(        "select p "+        "from Post p "+        "join fetch p.details "+        "join fetch p.comments "+        "where "+        "   p.id = :id").setParameter("id", 1L).uniqueResult());//Change the detached entity statepost.setName("Hibernate Training");doInTransaction(session -> {    //The Post entity graph is detached    containsPost(session, post, false);    //The Lock request associates     //the entity graph and locks the requested entity    session.buildLockRequest(        newLockOptions(LockMode.PESSIMISTIC_WRITE))    .lock(post);        //Hibernate doesn't know if the entity is dirty    assertEquals("Hibernate Training",         post.getName());    //The Post entity graph is attached    containsPost(session, post, true);});doInTransaction(session -> {    //The detached Post entity changes have been lost    Post _post = (Post) session.get(Post.class, 1L);    assertEquals("Hibernate Master Class",         _post.getName());}); | 
Запрос на блокировку повторно связывает граф сущностей, но текущий запущенный сеанс Hibernate не знает, что сущность стала грязной, находясь в отключенном состоянии. Сущность просто присоединяется без принудительного ОБНОВЛЕНИЯ или выбора текущего состояния базы данных для дальнейшего сравнения.
Как только объект станет управляемым, любое дальнейшее изменение будет обнаружено механизмом грязной проверки, и очистка также распространит изменения, предшествующие присоединению. Если во время управления объектом изменений не произойдет, объект не будет запланирован для сброса.
Если мы хотим убедиться, что состояние отсоединенной сущности всегда синхронизировано с базой данных, нам нужно использовать слияние или обновление .
Отдельные сущности распространяют параметры блокировки, когда для области действия установлено значение true :
| 1 2 3 4 | session.buildLockRequest(    newLockOptions(LockMode.PESSIMISTIC_WRITE)).setScope(true).lock(post); | 
Событие блокировки сущности Post распространяется на все дочерние сущности (так как мы используем CascadeType.ALL ):
| 1 2 3 4 | select id from Comment where id = 1forupdateselect id from Comment where id = 2forupdateselect id from PostDetails where id = 1forupdateselect id from Post where id = 1forupdate | 
Вывод
Каскадирование замков далеко не прямолинейно и не интуитивно понятно. Явная блокировка требует усердия (чем больше блокировок мы приобретаем, тем выше вероятность мертвой блокировки), и вам все равно лучше сохранить полный контроль над распространением блокировки дочерних объектов. Аналогично лучшим практикам параллельного программирования, ручная блокировка предпочтительнее, чем автоматическое распространение блокировки.
- Код доступен на GitHub .
| Ссылка: | Hibernate CascadeType.LOCK получил от нашего партнера JCG Влада Михалча в блоге Влада Михалча . | 
