Вступление
Введя поддержку явной блокировки 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)private List<Comment> comments = new ArrayList<>();@OneToOne( cascade = CascadeType.ALL, mappedBy = "post", optional = false, fetch = FetchType.LAZY)private PostDetails details; |
Во всех будущих тестовых случаях будет использоваться следующий график модели сущностей:
|
01
02
03
04
05
06
07
08
09
10
|
doInTransaction(session -> { Post post = new Post(); post.setName("Hibernate Master Class"); post.addDetails(new PostDetails()); post.addComment(new Comment("Good post!")); post.addComment(new Comment("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( new LockOptions(LockMode.PESSIMISTIC_WRITE)) .lock(post);}); |
Только родительская сущность блокируется, поэтому предотвращается каскад:
|
1
|
select id from Post where id = 1 for update |
Hibernate определяет область действия LockOption , которая (согласно JavaDocs) должна позволять распространять запрос блокировки на дочерние объекты:
«Область действия» — это термин, определенный JPA. Это в основном каскадирование блокировки ассоциаций.
|
1
2
3
4
|
session.buildLockRequest( new LockOptions(LockMode.PESSIMISTIC_WRITE)).setScope(true).lock(post); |
Установка флага области ничего не меняет, блокируется только управляемый объект:
|
1
|
select id from Post where id = 1 for update |
Блокировка отдельных объектов
Помимо блокировки сущностей, запрос на блокировку может также повторно связать отдельные сущности. Чтобы доказать это, мы собираемся проверить граф сущности Post до и после запроса блокировки сущности:
|
01
02
03
04
05
06
07
08
09
10
11
|
void containsPost(Session session, Post post, boolean expected) { 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( new LockOptions(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( new LockOptions(LockMode.PESSIMISTIC_WRITE)).setScope(true).lock(post); |
Событие блокировки сущности Post распространяется на все дочерние сущности (так как мы используем CascadeType.ALL ):
|
1
2
3
4
|
select id from Comment where id = 1 for updateselect id from Comment where id = 2 for updateselect id from PostDetails where id = 1 for updateselect id from Post where id = 1 for update |
Вывод
Каскадирование замков далеко не прямолинейно и не интуитивно понятно. Явная блокировка требует усердия (чем больше блокировок мы приобретаем, тем выше вероятность мертвой блокировки), и вам все равно лучше сохранить полный контроль над распространением блокировки дочерних объектов. Аналогично лучшим практикам параллельного программирования, ручная блокировка предпочтительнее, чем автоматическое распространение блокировки.
- Код доступен на GitHub .
| Ссылка: | Hibernate CascadeType.LOCK получил от нашего партнера JCG Влада Михалча в блоге Влада Михалча . |
