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
|
@Entity public 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 ); } } @Entity public 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_.index insert 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_. type delete from Image where id =? insert into Image ( id , index, name, product_id) values (default, ?, ?, ?) |
- Исходный код доступен здесь .