Skip to content

Commit b37168a

Browse files
Francesco MarinoSanne
authored andcommitted
HHH-15134 Update a bytecode enanchhed Entity with a Version attribute causes OptimisticLockException
1 parent f2ac89a commit b37168a

File tree

2 files changed

+244
-2
lines changed

2 files changed

+244
-2
lines changed

hibernate-core/src/main/java/org/hibernate/bytecode/enhance/spi/interceptor/EnhancementAsProxyLazinessInterceptor.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,9 @@ public EnhancementAsProxyLazinessInterceptor(
7474

7575
this.inLineDirtyChecking = SelfDirtinessTracker.class.isAssignableFrom( entityPersister.getMappedClass() );
7676
// if self-dirty tracking is enabled but DynamicUpdate is not enabled then we need to initialise the entity
77-
// because the pre-computed update statement contains even not dirty properties and so we need all the values
78-
initializeBeforeWrite = !( inLineDirtyChecking && entityPersister.getEntityMetamodel().isDynamicUpdate() );
77+
// because the pre-computed update statement contains even not dirty properties and so we need all the values
78+
// we have to initialise it even if it's versioned to fetch the current version
79+
initializeBeforeWrite = !( inLineDirtyChecking && entityPersister.getEntityMetamodel().isDynamicUpdate() ) || entityPersister.isVersioned();
7980
status = Status.UNINITIALIZED;
8081
}
8182

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
package org.hibernate.orm.test.bytecode.enhancement.version;
2+
3+
import org.hibernate.Hibernate;
4+
import org.hibernate.testing.TestForIssue;
5+
import org.hibernate.testing.bytecode.enhancement.BytecodeEnhancerRunner;
6+
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
7+
import org.junit.Before;
8+
import org.junit.Test;
9+
import org.junit.runner.RunWith;
10+
11+
import java.io.Serializable;
12+
import java.util.HashSet;
13+
import java.util.Set;
14+
15+
import jakarta.persistence.CascadeType;
16+
import jakarta.persistence.Entity;
17+
import jakarta.persistence.FetchType;
18+
import jakarta.persistence.ForeignKey;
19+
import jakarta.persistence.GeneratedValue;
20+
import jakarta.persistence.Id;
21+
import jakarta.persistence.JoinColumn;
22+
import jakarta.persistence.ManyToOne;
23+
import jakarta.persistence.MappedSuperclass;
24+
import jakarta.persistence.OneToMany;
25+
import jakarta.persistence.Version;
26+
27+
import static org.hibernate.Hibernate.isInitialized;
28+
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
29+
import static org.junit.Assert.assertFalse;
30+
import static org.junit.Assert.assertTrue;
31+
32+
@TestForIssue(jiraKey = "HHH-15134")
33+
@RunWith(BytecodeEnhancerRunner.class)
34+
public class VersionedEntityTest extends BaseCoreFunctionalTestCase {
35+
private final Long parentID = 1L;
36+
37+
@Override
38+
public Class<?>[] getAnnotatedClasses() {
39+
return new Class<?>[]{ FooEntity.class, BarEntity.class, BazEntity.class };
40+
}
41+
42+
@Before
43+
public void prepare() {
44+
doInJPA(this::sessionFactory, em -> {
45+
final FooEntity entity = FooEntity.of( parentID, "foo" );
46+
em.persist( entity );
47+
});
48+
}
49+
50+
@Test
51+
public void testUpdateVersionedEntity() {
52+
doInJPA(this::sessionFactory, em -> {
53+
final FooEntity entity = em.getReference( FooEntity.class, parentID );
54+
55+
assertFalse( isInitialized( entity ) );
56+
assertTrue( Hibernate.isPropertyInitialized( entity, "id" ) );
57+
assertFalse( Hibernate.isPropertyInitialized( entity, "name" ) );
58+
assertFalse( Hibernate.isPropertyInitialized( entity, "version" ) );
59+
assertFalse( Hibernate.isPropertyInitialized( entity, "bars" ) );
60+
assertFalse( Hibernate.isPropertyInitialized( entity, "bazzes" ) );
61+
62+
entity.setName( "bar" );
63+
});
64+
}
65+
66+
@MappedSuperclass
67+
public static abstract class AbstractEntity<T extends Serializable> {
68+
69+
public abstract T getId();
70+
71+
public abstract void setId(T id);
72+
73+
@Override
74+
public int hashCode() {
75+
return getClass().hashCode();
76+
}
77+
78+
@Override
79+
public boolean equals(Object obj) {
80+
if (obj == this) return true;
81+
if (obj == null || obj.getClass() != getClass()) return false;
82+
83+
final AbstractEntity<?> other = (AbstractEntity<?>) obj;
84+
return getId() != null && getId().equals(other.getId());
85+
}
86+
}
87+
88+
@Entity(name = "FooEntity")
89+
public static class FooEntity extends AbstractEntity<Long> {
90+
91+
@Id
92+
private long id;
93+
@Version
94+
private int version;
95+
96+
private String name;
97+
98+
@OneToMany(mappedBy = "foo", cascade = CascadeType.ALL, targetEntity = BarEntity.class, orphanRemoval = true)
99+
public Set<BarEntity> bars = new HashSet<>();
100+
101+
@OneToMany(mappedBy = "foo", cascade = CascadeType.ALL, targetEntity = BazEntity.class, orphanRemoval = true)
102+
public Set<BazEntity> bazzes = new HashSet<>();
103+
104+
public static FooEntity of(long id, String name) {
105+
final FooEntity f = new FooEntity();
106+
f.id = id;
107+
f.name = name;
108+
return f;
109+
}
110+
111+
@Override
112+
public Long getId() {
113+
return id;
114+
}
115+
116+
@Override
117+
public void setId(Long id) {
118+
this.id = id;
119+
}
120+
121+
public int getVersion() {
122+
return version;
123+
}
124+
125+
public void setVersion(int version) {
126+
this.version = version;
127+
}
128+
129+
public String getName() {
130+
return name;
131+
}
132+
133+
public void setName(String name) {
134+
this.name = name;
135+
}
136+
137+
public Set<BarEntity> getBars() {
138+
return bars;
139+
}
140+
141+
public void addBar(BarEntity bar) {
142+
bars.add(bar);
143+
bar.setFoo(this);
144+
}
145+
146+
public void removeBar(BarEntity bar) {
147+
bars.remove(bar);
148+
bar.setFoo(null);
149+
}
150+
151+
public Set<BazEntity> getBazzes() {
152+
return bazzes;
153+
}
154+
155+
public void addBaz(BazEntity baz) {
156+
bazzes.add(baz);
157+
baz.setFoo(this);
158+
}
159+
160+
public void removeBaz(BazEntity baz) {
161+
bazzes.remove(baz);
162+
baz.setFoo(null);
163+
}
164+
165+
@Override
166+
public String toString() {
167+
return String.format("FooEntity: id=%d, version=%d, name=%s", id, version, name);
168+
}
169+
}
170+
171+
@Entity(name = "BazEntity")
172+
public static class BazEntity extends AbstractEntity<Long> {
173+
174+
@Id
175+
@GeneratedValue
176+
private long id;
177+
178+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
179+
@JoinColumn(foreignKey = @ForeignKey(name = "fk_baz_foo"), nullable = false)
180+
private FooEntity foo;
181+
182+
@Override
183+
public Long getId() {
184+
return id;
185+
}
186+
187+
@Override
188+
public void setId(Long id) {
189+
this.id = id;
190+
}
191+
192+
public FooEntity getFoo() {
193+
return foo;
194+
}
195+
196+
public void setFoo(FooEntity foo) {
197+
this.foo = foo;
198+
}
199+
200+
@Override
201+
public String toString() {
202+
return String.format("BazEntity: id=%d", id);
203+
}
204+
}
205+
206+
@Entity(name = "BarEntity")
207+
public static class BarEntity extends AbstractEntity<Long> {
208+
209+
@Id
210+
@GeneratedValue
211+
private long id;
212+
213+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
214+
@JoinColumn(foreignKey = @ForeignKey(name = "fk_bar_foo"), nullable = false)
215+
private FooEntity foo;
216+
217+
@Override
218+
public Long getId() {
219+
return id;
220+
}
221+
222+
@Override
223+
public void setId(Long id) {
224+
this.id = id;
225+
}
226+
227+
public FooEntity getFoo() {
228+
return foo;
229+
}
230+
231+
public void setFoo(FooEntity foo) {
232+
this.foo = foo;
233+
}
234+
235+
@Override
236+
public String toString() {
237+
return String.format("BarEntity: id=%d", id);
238+
}
239+
}
240+
}
241+

0 commit comments

Comments
 (0)