Skip to content

Commit 75e7803

Browse files
committed
HHH-7180 Test and fix collection key comparison in ProxyVisitor
1 parent 0452d76 commit 75e7803

File tree

2 files changed

+133
-1
lines changed

2 files changed

+133
-1
lines changed

hibernate-core/src/main/java/org/hibernate/event/internal/ProxyVisitor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ protected static boolean isOwnerUnchanged(
3939
CollectionPersister persister, Object id, PersistentCollection<?> snapshot) {
4040
return isCollectionSnapshotValid( snapshot )
4141
&& persister.getRole().equals( snapshot.getRole() )
42-
&& id.equals( snapshot.getKey() );
42+
&& persister.getKeyType().isEqual( id, snapshot.getKey() );
4343
}
4444

4545
private static boolean isCollectionSnapshotValid(PersistentCollection<?> snapshot) {
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* SPDX-License-Identifier: LGPL-2.1-or-later
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.orm.test.id.array;
6+
7+
import jakarta.persistence.CascadeType;
8+
import jakarta.persistence.Entity;
9+
import jakarta.persistence.FetchType;
10+
import jakarta.persistence.Id;
11+
import jakarta.persistence.ManyToOne;
12+
import jakarta.persistence.OneToMany;
13+
import org.hibernate.community.dialect.InformixDialect;
14+
import org.hibernate.dialect.MySQLDialect;
15+
import org.hibernate.dialect.OracleDialect;
16+
import org.hibernate.stat.spi.StatisticsImplementor;
17+
import org.hibernate.testing.orm.junit.DomainModel;
18+
import org.hibernate.testing.orm.junit.JiraKey;
19+
import org.hibernate.testing.orm.junit.SessionFactory;
20+
import org.hibernate.testing.orm.junit.SessionFactoryScope;
21+
import org.hibernate.testing.orm.junit.SkipForDialect;
22+
import org.junit.jupiter.api.AfterEach;
23+
import org.junit.jupiter.api.BeforeEach;
24+
import org.junit.jupiter.api.Test;
25+
26+
import java.util.HashSet;
27+
import java.util.Set;
28+
29+
import static org.junit.jupiter.api.Assertions.assertEquals;
30+
31+
32+
@SkipForDialect(dialectClass = MySQLDialect.class, majorVersion = 5, reason = "BLOB/TEXT column 'id' used in key specification without a key length")
33+
@SkipForDialect(dialectClass = OracleDialect.class, matchSubTypes = true, reason = "ORA-02329: column of datatype LOB cannot be unique or a primary key")
34+
@SkipForDialect(dialectClass = InformixDialect.class, reason = "Informix does not support unique / primary constraints on binary columns")
35+
@DomainModel(
36+
annotatedClasses = {
37+
PrimitiveByteArrayIdCollectionTest.Parent.class,
38+
PrimitiveByteArrayIdCollectionTest.Child.class
39+
}
40+
)
41+
@SessionFactory
42+
public class PrimitiveByteArrayIdCollectionTest {
43+
44+
@BeforeEach
45+
public void prepare(SessionFactoryScope scope) {
46+
scope.inTransaction(
47+
session -> {
48+
Parent entity = new Parent();
49+
entity.id = new byte[] {
50+
(byte) ( 1 ),
51+
(byte) ( 2 ),
52+
(byte) ( 3 ),
53+
(byte) ( 4 )
54+
};
55+
entity.name = "Simple name";
56+
57+
for ( int j = 1; j <= 2; j++ ) {
58+
Child child = new Child();
59+
child.id = j;
60+
entity.name = "Child name " + j;
61+
child.parent = entity;
62+
entity.children.add(child);
63+
}
64+
session.persist( entity );
65+
}
66+
);
67+
}
68+
69+
@AfterEach
70+
public void cleanup(SessionFactoryScope scope) {
71+
scope.inTransaction(
72+
session -> {
73+
session.createQuery( "delete from Child" ).executeUpdate();
74+
session.createQuery( "delete from Parent" ).executeUpdate();
75+
}
76+
);
77+
}
78+
79+
@Test
80+
@JiraKey(value = "HHH-7180")
81+
public void testReattach(SessionFactoryScope scope) {
82+
// Since reattachment was removed in ORM 7,
83+
// but the code path to trigger the bug is still reachable through removing a detached entity,
84+
// construct a scenario that shows a problem
85+
86+
final Parent parent = scope.fromTransaction(
87+
session -> session.createQuery( "from Parent p", Parent.class ).getSingleResult()
88+
);
89+
// Copy the byte-array id which will make it a different instance, yet equal to the collection key
90+
parent.id = parent.id.clone();
91+
92+
final StatisticsImplementor statistics = scope.getSessionFactory().getStatistics();
93+
statistics.setStatisticsEnabled( true );
94+
statistics.clear();
95+
96+
scope.inTransaction(
97+
session -> {
98+
session.remove( parent );
99+
session.flush();
100+
101+
// The collection will be removed twice if the collection key can't be matched to the entity id
102+
assertEquals( 1L, statistics.getCollectionRemoveCount() );
103+
}
104+
);
105+
106+
}
107+
108+
@Entity(name = "Parent")
109+
public static class Parent {
110+
@Id
111+
public byte[] id;
112+
public String name;
113+
@OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
114+
public Set<Child> children = new HashSet<>();
115+
}
116+
117+
@Entity(name = "Child")
118+
public static class Child {
119+
@Id
120+
public Integer id;
121+
public String name;
122+
@ManyToOne(fetch = FetchType.LAZY)
123+
public Parent parent;
124+
125+
public Child() {
126+
}
127+
128+
public Child(Integer id) {
129+
this.id = id;
130+
}
131+
}
132+
}

0 commit comments

Comments
 (0)