Skip to content

Commit 91995cf

Browse files
ballmgbadner
authored andcommitted
HHH-7573 Added check for handle LazyPropertyInitializer.UNFETCHED_PROPERTY
Added test to check that the change handles the various possible scenarios. Test requires build time instrumentation. (cherry picked from commit e7475f1)
1 parent 16c0a1f commit 91995cf

File tree

4 files changed

+150
-2
lines changed

4 files changed

+150
-2
lines changed

hibernate-entitymanager/hibernate-entitymanager.gradle

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,17 @@ task testJar(type: Jar, dependsOn: testClasses) {
112112
artifacts {
113113
tests testJar
114114
}
115+
116+
task instrument(dependsOn: compileTestJava) << {
117+
ant.taskdef(name: 'hibInstrument',
118+
classname: 'org.hibernate.tool.instrument.javassist.InstrumentTask',
119+
classpath: configurations.testCompile.asPath)
120+
ant.hibInstrument(verbose: 'true'){
121+
fileset(dir:"$buildDir/classes/test/org/hibernate/jpa/test/callbacks/"){
122+
include(name: "EntityWithLazyProperty.class")
123+
}
124+
}
125+
println("hibernate instrumentation")
126+
}
127+
128+
test.dependsOn instrument

hibernate-entitymanager/src/main/java/org/hibernate/jpa/event/internal/core/JpaFlushEntityEventListener.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package org.hibernate.jpa.event.internal.core;
88

99
import org.hibernate.SessionFactory;
10+
import org.hibernate.bytecode.instrumentation.spi.LazyPropertyInitializer;
1011
import org.hibernate.engine.spi.EntityEntry;
1112
import org.hibernate.engine.spi.SessionImplementor;
1213
import org.hibernate.engine.spi.Status;
@@ -61,7 +62,8 @@ private boolean copyState(Object entity, Type[] types, Object[] state, SessionFa
6162
int size = newState.length;
6263
boolean isDirty = false;
6364
for ( int index = 0; index < size ; index++ ) {
64-
if ( !types[index].isEqual( state[index], newState[index] ) ) {
65+
if (state[index] == LazyPropertyInitializer.UNFETCHED_PROPERTY && newState[index] != LazyPropertyInitializer.UNFETCHED_PROPERTY
66+
|| state[index] != newState[index] && !types[index].isEqual(state[index], newState[index])) {
6567
isDirty = true;
6668
state[index] = newState[index];
6769
}

hibernate-entitymanager/src/test/java/org/hibernate/jpa/test/callbacks/CallbacksTest.java

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,76 @@ public Class[] getAnnotatedClasses() {
251251
RemoteControl.class,
252252
Rythm.class,
253253
Plant.class,
254-
Kitten.class
254+
Kitten.class,
255+
EntityWithLazyProperty.class
255256
};
256257
}
258+
259+
/**
260+
* Test for HHH-7573.
261+
* Load some test data into an entity which has a lazy property and a @PreUpdate callback, then reload and update a
262+
* non lazy field which will trigger the PreUpdate lifecycle callback.
263+
* @throws Exception
264+
*/
265+
@Test
266+
public void testJpaFlushEntityEventListener() throws Exception {
267+
EntityWithLazyProperty entity;
268+
EntityManager em = getOrCreateEntityManager();
269+
270+
byte[] testArray = new byte[]{0x2A};
271+
272+
//persist the test entity.
273+
em.getTransaction().begin();
274+
entity = new EntityWithLazyProperty();
275+
entity.setSomeField("TEST");
276+
entity.setLazyData(testArray);
277+
em.persist(entity);
278+
em.getTransaction().commit();
279+
checkLazyField(entity, em, testArray);
280+
281+
/**
282+
* Set a non lazy field, therefore the lazyData field will be LazyPropertyInitializer.UNFETCHED_PROPERTY
283+
* for both state and newState so the field should not change. This should no longer cause a ClassCastException.
284+
*/
285+
em.getTransaction().begin();
286+
entity = em.find(EntityWithLazyProperty.class, entity.getId());
287+
entity.setSomeField("TEST1");
288+
em.getTransaction().commit();
289+
checkLazyField(entity, em, testArray);
290+
291+
/**
292+
* Set the updateLazyFieldInPreUpdate flag so that the lazy field is updated from within the
293+
* PreUpdate annotated callback method. So state == LazyPropertyInitializer.UNFETCHED_PROPERTY and
294+
* newState == EntityWithLazyProperty.PRE_UPDATE_VALUE. This should no longer cause a ClassCastException.
295+
*/
296+
em.getTransaction().begin();
297+
entity = em.find(EntityWithLazyProperty.class, entity.getId());
298+
entity.setUpdateLazyFieldInPreUpdate(true);
299+
entity.setSomeField("TEST2");
300+
em.getTransaction().commit();
301+
checkLazyField(entity, em, EntityWithLazyProperty.PRE_UPDATE_VALUE);
302+
303+
/**
304+
* Set the updateLazyFieldInPreUpdate flag so that the lazy field is updated from within the
305+
* PreUpdate annotated callback method and also set the lazyData field directly to testArray1. When we reload we
306+
* should get EntityWithLazyProperty.PRE_UPDATE_VALUE.
307+
*/
308+
em.getTransaction().begin();
309+
entity = em.find(EntityWithLazyProperty.class, entity.getId());
310+
entity.setUpdateLazyFieldInPreUpdate(true);
311+
entity.setLazyData(testArray);
312+
entity.setSomeField("TEST3");
313+
em.getTransaction().commit();
314+
checkLazyField(entity, em, EntityWithLazyProperty.PRE_UPDATE_VALUE);
315+
316+
em.close();
317+
}
318+
319+
private void checkLazyField(EntityWithLazyProperty entity, EntityManager em, byte[] expected) {
320+
// reload the entity and check the lazy value matches what we expect.
321+
em.getTransaction().begin();
322+
entity = em.find(EntityWithLazyProperty.class, entity.getId());
323+
assertEquals(expected, entity.getLazyData());
324+
em.getTransaction().commit();
325+
}
257326
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.hibernate.jpa.test.callbacks;
2+
3+
import javax.persistence.*;
4+
5+
/**
6+
* Test entity with a lazy property which requires build time instrumentation.
7+
*/
8+
@Entity
9+
public class EntityWithLazyProperty {
10+
11+
public static final byte[] PRE_UPDATE_VALUE = new byte[]{0x2A, 0x2A, 0x2A, 0x2A};
12+
13+
@Id
14+
@GeneratedValue
15+
private Long id;
16+
17+
@Basic(fetch = FetchType.LAZY)
18+
private byte[] lazyData;
19+
20+
private String someField;
21+
22+
private boolean updateLazyFieldInPreUpdate;
23+
24+
public Long getId() {
25+
return id;
26+
}
27+
28+
public void setId(final Long id) {
29+
this.id = id;
30+
}
31+
32+
public byte[] getLazyData() {
33+
return lazyData;
34+
}
35+
36+
public void setLazyData(final byte[] lazyData) {
37+
this.lazyData = lazyData;
38+
}
39+
40+
public String getSomeField() {
41+
return someField;
42+
}
43+
44+
public void setSomeField(String someField) {
45+
this.someField = someField;
46+
}
47+
48+
public boolean isUpdateLazyFieldInPreUpdate() {
49+
return updateLazyFieldInPreUpdate;
50+
}
51+
52+
public void setUpdateLazyFieldInPreUpdate(boolean updateLazyFieldInPreUpdate) {
53+
this.updateLazyFieldInPreUpdate = updateLazyFieldInPreUpdate;
54+
}
55+
56+
@PreUpdate
57+
public void onPreUpdate() {
58+
//Allow the update of the lazy field from within the pre update to check that this does not break things.
59+
if(isUpdateLazyFieldInPreUpdate()) {
60+
this.setLazyData(PRE_UPDATE_VALUE);
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)