I am hesitant to suggest this is a bug - because it seems like such bread-and-butter functionality that is broken, which surely is unlikely.
(I am using Hibernate 5.4.0.Final - and also tested with 5.4.2.Final)
I have a simple uni-directional Parent-Child object relationship where a parent has one-to-many children.
I can save the object graph no problem.
If I then start with a detached Parent and :
- Re-order the list (say Collections.shuffle(children) ) and merge the Parent, no SQL updates are executed & the changes to the List order are not persisted.
- Remove an item from the middle of the list (say children.remove(1) ) and merge the Parent, an SQL delete is executed as expected, however no updates are executed to update the OrderColumn
- If I remove an item from the middle of the list AND add an item at the end of the list, hilarity ensues. A delete is executed as expected, an insert is executed as expected, but now two child rows in the database will have the same OrderColumn value
Is this a bug? Or have I forgotten how hibernate works? (Admittedly, its been a few years since I have used this aspect of hibernate)
Here are my classes:
Parent
@Entity
public class Parent {
@Id
@Column
private UUID id;
@Column
private String name;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JoinColumn(name = "parent_id", nullable = false, updatable = false)
@OrderColumn(name = "child_index", nullable = false)
private List<Child> children = new ArrayList<>();
...
public Parent removeChild() {
children.remove(1);
return this;
}
public Parent addChild() {
children.add(new Child("new"));
return this;
}
public Parent reorderChildren() {
Collections.shuffle(children);
return this;
}
}
Child
@Entity
public class Child {
@Id
@Column
private UUID id;
@Column
private String name;
...
}
Here is an extract from the ORMUnitTestCase test-case-template
Save a Parent
private final Parent parent = new Parent("barry", new ArrayList<>(Arrays.asList(new Child("one"), new Child("two"), new Child("three"), new Child("four"))));
@Before
public void setUp() throws SQLException {
doInNewSessionAndTx(session -> {
System.out.println("******** SAVE session.save()");
Parent detached = detached(parent);
session.save(detached);
});
printActualTable("Child table after save...");
}
FYI detached() is just doing a manual clone of the parent object - simulating, say, a JSON serialisation/deserialisation round-trip
Re-ordering children
@Test
public void reOrderingChildrenIsNotPersisted() throws SQLException {
Parent expectedUpdate = detached(parent)
.reorderChildren();
doInNewSessionAndTx(session -> {
System.out.println("******** MERGE session.merge()");
session.merge(expectedUpdate);
});
Parent loaded = loadById(parent.getId());
printActualTable("Child table after merge :-o");
assertEquals(expectedUpdate, loaded);
}
Remove child and add new one
@Test
public void removingChildrenDoesNotUpdateProperly2() throws SQLException {
Parent expectedUpdate = detached(parent)
.removeChild()
.addChild();
doInNewSessionAndTx(session -> {
System.out.println("******** MERGE session.merge()");
session.merge(expectedUpdate);
});
Parent loaded = loadById(parent.getId());
printActualTable("Child table after merge :-o");
assertEquals(expectedUpdate, loaded);
}
Here is the std-out of first test:
******** SAVE session.save()
Hibernate: select child_.id, child_.name as name2_0_ from Child child_ where child_.id=?
Hibernate: select child_.id, child_.name as name2_0_ from Child child_ where child_.id=?
Hibernate: select child_.id, child_.name as name2_0_ from Child child_ where child_.id=?
Hibernate: select child_.id, child_.name as name2_0_ from Child child_ where child_.id=?
Hibernate: insert into Parent (name, id) values (?, ?)
Hibernate: insert into Child (name, parent_id, child_index, id) values (?, ?, ?, ?)
Hibernate: insert into Child (name, parent_id, child_index, id) values (?, ?, ?, ?)
Hibernate: insert into Child (name, parent_id, child_index, id) values (?, ?, ?, ?)
Hibernate: insert into Child (name, parent_id, child_index, id) values (?, ?, ?, ?)
=============================================================================
Child table after save...
=============================================================================
ID PARENT_ID IDX NAME
69e8d7e2a3404ae593b43892b9ce864c bf27d244622e40379b558cca3e712a9e 0 one
67a24f9312684a5a9c019925c0537b0d bf27d244622e40379b558cca3e712a9e 1 two
89653117d9094bd1941be7e4bbacd764 bf27d244622e40379b558cca3e712a9e 2 three
7601535cb1fe4151bbee578dc047ffdc bf27d244622e40379b558cca3e712a9e 3 four
=============================================================================
******** MERGE session.merge()
Hibernate: select parent0_.id as id1_1_1_, parent0_.name as name2_1_1_, children1_.parent_id as parent_i3_0_3_, children1_.id as id1_0_3_, children1_.child_index as child_in4_3_, children1_.id as id1_0_0_, children1_.name as name2_0_0_ from Parent parent0_ left outer join Child children1_ on parent0_.id=children1_.parent_id where parent0_.id=?
******** GET BY ID
Hibernate: select parent0_.id as id1_1_0_, parent0_.name as name2_1_0_, children1_.parent_id as parent_i3_0_1_, children1_.id as id1_0_1_, children1_.child_index as child_in4_1_, children1_.id as id1_0_2_, children1_.name as name2_0_2_ from Parent parent0_ left outer join Child children1_ on parent0_.id=children1_.parent_id where parent0_.id=?
=============================================================================
Child table after merge
=============================================================================
ID PARENT_ID IDX NAME
69e8d7e2a3404ae593b43892b9ce864c bf27d244622e40379b558cca3e712a9e 0 one
67a24f9312684a5a9c019925c0537b0d bf27d244622e40379b558cca3e712a9e 1 two
89653117d9094bd1941be7e4bbacd764 bf27d244622e40379b558cca3e712a9e 2 three
7601535cb1fe4151bbee578dc047ffdc bf27d244622e40379b558cca3e712a9e 3 four
=============================================================================
Here is the std-out of the 2nd test
******** SAVE session.save()
Hibernate: select child_.id, child_.name as name2_0_ from Child child_ where child_.id=?
Hibernate: select child_.id, child_.name as name2_0_ from Child child_ where child_.id=?
Hibernate: select child_.id, child_.name as name2_0_ from Child child_ where child_.id=?
Hibernate: select child_.id, child_.name as name2_0_ from Child child_ where child_.id=?
Hibernate: insert into Parent (name, id) values (?, ?)
Hibernate: insert into Child (name, parent_id, child_index, id) values (?, ?, ?, ?)
Hibernate: insert into Child (name, parent_id, child_index, id) values (?, ?, ?, ?)
Hibernate: insert into Child (name, parent_id, child_index, id) values (?, ?, ?, ?)
Hibernate: insert into Child (name, parent_id, child_index, id) values (?, ?, ?, ?)
=============================================================================
Child table after save...
=============================================================================
ID PARENT_ID IDX NAME
1dac1cecc17347f59dd527697b18a096 4c5c06ef232f4d7cb471eaa05c50cb5a 0 one
94f38a6a8b634f3c84703c5669ada229 4c5c06ef232f4d7cb471eaa05c50cb5a 1 two
fdfad4f8e7574b64a6f8352200a462e6 4c5c06ef232f4d7cb471eaa05c50cb5a 2 three
6103df0cfc4e484ba6936dcf9eb28e44 4c5c06ef232f4d7cb471eaa05c50cb5a 3 four
=============================================================================
******** MERGE session.merge()
Hibernate: select parent0_.id as id1_1_1_, parent0_.name as name2_1_1_, children1_.parent_id as parent_i3_0_3_, children1_.id as id1_0_3_, children1_.child_index as child_in4_3_, children1_.id as id1_0_0_, children1_.name as name2_0_0_ from Parent parent0_ left outer join Child children1_ on parent0_.id=children1_.parent_id where parent0_.id=?
Hibernate: select child0_.id as id1_0_0_, child0_.name as name2_0_0_ from Child child0_ where child0_.id=?
Hibernate: insert into Child (name, parent_id, child_index, id) values (?, ?, ?, ?)
Hibernate: delete from Child where id=?
******** GET BY ID
Hibernate: select parent0_.id as id1_1_0_, parent0_.name as name2_1_0_, children1_.parent_id as parent_i3_0_1_, children1_.id as id1_0_1_, children1_.child_index as child_in4_1_, children1_.id as id1_0_2_, children1_.name as name2_0_2_ from Parent parent0_ left outer join Child children1_ on parent0_.id=children1_.parent_id where parent0_.id=?
=============================================================================
Child table after merge :-o
=============================================================================
ID PARENT_ID IDX NAME
1dac1cecc17347f59dd527697b18a096 4c5c06ef232f4d7cb471eaa05c50cb5a 0 one
fdfad4f8e7574b64a6f8352200a462e6 4c5c06ef232f4d7cb471eaa05c50cb5a 2 three
6103df0cfc4e484ba6936dcf9eb28e44 4c5c06ef232f4d7cb471eaa05c50cb5a 3 four
4d754d0f13f848a28e994babdf77e8cb 4c5c06ef232f4d7cb471eaa05c50cb5a 3 new
=============================================================================