Hibernate меняет мышление разработчика с мышления SQL на переходы состояний мышления объекта. Согласно Hibernate Docs сущность может находиться в одном из следующих состояний:
- new / transient: объект не связан с постоянным контекстом, будь то вновь созданный объект, о котором база данных ничего не знает.
- постоянный: объект связан с постоянным контекстом (находится в кэше 1-го уровня), и существует строка базы данных, представляющая этот объект.
- detached: объект ранее был связан с контекстом постоянства, но контекст постоянства был закрыт, или объект был удален вручную.
- удалено: объект был помечен как удаленный, и контекст сохранения удалит его из базы данных во время сброса.
Перемещение объекта из одного состояния в другое выполняется путем вызова методов EntityManager, таких как:
- сохраняются ()
- слияния ()
- удалять()
Каскадирование позволяет передавать данное событие от родителя к потомку, а также упрощает управление отношениями между сущностями.
Во время сброса Hibernate преобразует изменения, записанные текущим контекстом постоянства, в запросы SQL.
Теперь подумайте, что происходит в следующем коде (сокращено для краткости):
|
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
|
@Entitypublic class Product { @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "product", orphanRemoval = true) @OrderBy("index") private Set images = new LinkedHashSet(); public Set getImages() { return images; } public void addImage(Image image) { images.add(image); image.setProduct(this); } public void removeImage(Image image) { images.remove(image); image.setProduct(null); }}@Entitypublic class Image { @Column(unique = true) private int index; @ManyToOne private Product product; public int getIndex() { return index; } public void setIndex(int index) { this.index = index; } public Product getProduct() { return product; } public void setProduct(Product product) { this.product = product; }}final Long productId = transactionTemplate.execute(new TransactionCallback() { @Override public Long doInTransaction(TransactionStatus transactionStatus) { Product product = new Product(); Image frontImage = new Image(); frontImage.setIndex(0); Image sideImage = new Image(); sideImage.setIndex(1); product.addImage(frontImage); product.addImage(sideImage); entityManager.persist(product); return product.getId(); }});try { transactionTemplate.execute(new TransactionCallback() { @Override public Void doInTransaction(TransactionStatus transactionStatus) { Product product = entityManager.find(Product.class, productId); assertEquals(2, product.getImages().size()); Iterator imageIterator = product.getImages().iterator(); Image frontImage = imageIterator.next(); assertEquals(0, frontImage.getIndex()); Image sideImage = imageIterator.next(); assertEquals(1, sideImage.getIndex()); Image backImage = new Image(); sideImage.setName("back image"); sideImage.setIndex(1); product.removeImage(sideImage); product.addImage(backImage); entityManager.flush(); return null; }}); fail("Expected ConstraintViolationException");} catch (PersistenceException expected) { assertEquals(ConstraintViolationException.class, expected.getCause().getClass());} |
Из-за уникального ограничения Image.index мы получаем исключение ConstraintviolationException во время сброса.
Вы можете задаться вопросом, почему это происходит, поскольку мы вызываем remove для sideImage до добавления backImage с тем же индексом, и ответом является порядок операций сброса.
Согласно Hibernate JavaDocs порядок операций SQL:
- вставки
- обновления
- удаления элементов коллекций
- вставки элементов коллекции
- удалений
Поскольку наша коллекция изображений «mappedBy», Image будет управлять связью, поэтому вставка «backImage» происходит до удаления «sideImage».
|
1
2
3
4
|
select product0_.id as id1_5_0_, product0_.name as name2_5_0_ from Product product0_ where product0_.id=?select images0_.product_id as product_4_5_1_, images0_.id as id1_1_1_, images0_.id as id1_1_0_, images0_.index as index2_1_0_, images0_.name as name3_1_0_, images0_.product_id as product_4_1_0_ from Image images0_ where images0_.product_id=? order by images0_.indexinsert into Image (id, index, name, product_id) values (default, ?, ?, ?)ERROR: integrity constraint violation: unique constraint or index violation; UK_OQBG3YIU5I1E17SL0FEAWT8PE table: IMAGE |
Чтобы это исправить, вы должны вручную сбросить контекст сохраняемости после операции удаления:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
transactionTemplate.execute(new TransactionCallback<Void>() { @Override public Void doInTransaction(TransactionStatus transactionStatus) { Product product = entityManager.find(Product.class, productId); assertEquals(2, product.getImages().size()); Iterator<Image> imageIterator = product.getImages().iterator(); Image frontImage = imageIterator.next(); assertEquals(0, frontImage.getIndex()); Image sideImage = imageIterator.next(); assertEquals(1, sideImage.getIndex()); Image backImage = new Image(); backImage.setIndex(1); product.removeImage(sideImage); entityManager.flush(); product.addImage(backImage); entityManager.flush(); return null; }}); |
Это выведет желаемое поведение:
|
1
2
3
|
select versions0_.image_id as image_id3_1_1_, versions0_.id as id1_8_1_, versions0_.id as id1_8_0_, versions0_.image_id as image_id3_8_0_, versions0_.type as type2_8_0_ from Version versions0_ where versions0_.image_id=? order by versions0_.typedelete from Image where id=?insert into Image (id, index, name, product_id) values (default, ?, ?, ?) |
- Исходный код доступен здесь .