Skip to content

Commit c8aee62

Browse files
authored
Test and Fix for wrong values put into L2 cache when modifying a Json-Property before lazyLoading
1 parent ada8714 commit c8aee62

3 files changed

Lines changed: 150 additions & 4 deletions

File tree

ebean-core/src/main/java/io/ebeaninternal/server/cache/CachedBeanDataFromBean.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package io.ebeaninternal.server.cache;
22

3+
import io.ebean.ModifyAwareType;
34
import io.ebean.bean.EntityBean;
45
import io.ebean.bean.EntityBeanIntercept;
56
import io.ebeaninternal.server.deploy.BeanDescriptor;
@@ -26,16 +27,31 @@ public static CachedBeanData extract(BeanDescriptor<?> desc, EntityBean bean) {
2627
// extract all the non-many properties
2728
final boolean dirty = ebi.isDirty();
2829
for (BeanProperty prop : desc.propertiesNonMany()) {
29-
if (dirty && ebi.isDirtyProperty(prop.propertyIndex())) {
30+
if (!ebi.isLoadedProperty(prop.propertyIndex())) {
31+
continue;
32+
}
33+
boolean useOriginalValue = false;
34+
// For ModifyAwareType (especially JSON) there is a constellation where the entity might not yet be dirty, but the property
35+
// has still changed. This would result in modified data being put into cache
36+
if (ebi.value(prop.propertyIndex()) instanceof ModifyAwareType) {
37+
useOriginalValue = ((ModifyAwareType) ebi.value(prop.propertyIndex())).isMarkedDirty();
38+
} else if (dirty && ebi.isDirtyProperty(prop.propertyIndex())) {
39+
useOriginalValue = true;
40+
}
41+
if (useOriginalValue) {
3042
data.put(prop.name(), prop.getCacheDataValueOrig(ebi));
31-
} else if (ebi.isLoadedProperty(prop.propertyIndex())) {
43+
} else {
3244
data.put(prop.name(), prop.getCacheDataValue(bean));
3345
}
3446
}
3547

3648
for (BeanPropertyAssocMany<?> prop : desc.propertiesMany()) {
3749
if (prop.isElementCollection()) {
38-
data.put(prop.name(), prop.getCacheDataValue(bean));
50+
if (ebi.isChangedProperty(prop.propertyIndex())) {
51+
data.put(prop.name(), prop.getCacheDataValueOrig(ebi));
52+
} else {
53+
data.put(prop.name(), prop.getCacheDataValue(bean));
54+
}
3955
}
4056
}
4157

ebean-test/src/test/java/org/tests/cache/TestQueryCache.java

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
import io.ebean.cache.ServerCache;
1111
import io.ebean.test.LoggedSql;
1212
import io.ebean.xtest.BaseTestCase;
13-
import org.junit.jupiter.api.Disabled;
1413
import org.junit.jupiter.api.Test;
1514
import org.tests.model.basic.Customer;
15+
import org.tests.model.basic.EBasicJsonCache;
1616
import org.tests.model.basic.ResetBasicData;
1717
import org.tests.model.cache.EColAB;
1818

@@ -474,6 +474,56 @@ public void findIds() {
474474
assertThat(sql).hasSize(1);
475475
}
476476

477+
@Test
478+
public void testDirtyUpCacheJsonMutationDetectionSource() {
479+
EBasicJsonCache testDirty = new EBasicJsonCache();
480+
testDirty.setName("testDirty");
481+
testDirty.setDescription("desc");
482+
testDirty.save();
483+
DB.cacheManager().clearAll();
484+
485+
EBasicJsonCache fromDb = DB.find(EBasicJsonCache.class).select("name,tags").setId(testDirty.getId()).findOne();
486+
assertThat(fromDb).isNotNull();
487+
assertThat(fromDb.getName()).isEqualTo("testDirty");
488+
assertThat(DB.beanState(fromDb).loadedProps()).containsExactlyInAnyOrder("id", "name", "tags");
489+
fromDb.getTags().add("tagtag");
490+
491+
assertThat(DB.beanState(fromDb).loadedProps()).containsExactlyInAnyOrder("id", "name", "tags");
492+
493+
// trigger lazy load
494+
assertThat(fromDb.getDescription()).isEqualTo("desc");
495+
496+
EBasicJsonCache fromDb2 = DB.find(EBasicJsonCache.class).setId(testDirty.getId()).findOne();
497+
assertThat(fromDb2).isNotNull();
498+
assertThat(fromDb2).isNotSameAs(fromDb);
499+
assertThat(fromDb2.getTags()).isNullOrEmpty();
500+
}
501+
502+
@Test
503+
public void testDirtyUpCacheJsonMutationDetectionDefault() {
504+
EBasicJsonCache testDirty = new EBasicJsonCache();
505+
testDirty.setName("testDirty");
506+
testDirty.setDescription("desc");
507+
testDirty.save();
508+
DB.cacheManager().clearAll();
509+
510+
EBasicJsonCache fromDb = DB.find(EBasicJsonCache.class).select("name,superTags").setId(testDirty.getId()).findOne();
511+
assertThat(fromDb).isNotNull();
512+
assertThat(fromDb.getName()).isEqualTo("testDirty");
513+
assertThat(DB.beanState(fromDb).loadedProps()).containsExactlyInAnyOrder("id", "name", "superTags");
514+
fromDb.getSuperTags().add("tagtag");
515+
516+
assertThat(DB.beanState(fromDb).loadedProps()).containsExactlyInAnyOrder("id", "name", "superTags");
517+
518+
// trigger lazy load
519+
assertThat(fromDb.getDescription()).isEqualTo("desc");
520+
521+
EBasicJsonCache fromDb2 = DB.find(EBasicJsonCache.class).setId(testDirty.getId()).findOne();
522+
assertThat(fromDb2).isNotNull();
523+
assertThat(fromDb2).isNotSameAs(fromDb);
524+
assertThat(fromDb2.getSuperTags()).isNullOrEmpty();
525+
}
526+
477527
@Test
478528
public void findCountDifferentQueriesBit() {
479529
DB.getDefault().pluginApi().cacheManager().clearAll();
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package org.tests.model.basic;
2+
3+
import io.ebean.Model;
4+
import io.ebean.annotation.Cache;
5+
import io.ebean.annotation.DbJson;
6+
import io.ebean.annotation.Identity;
7+
import io.ebean.annotation.MutationDetection;
8+
import jakarta.persistence.Entity;
9+
import jakarta.persistence.Id;
10+
import jakarta.persistence.Table;
11+
12+
import java.util.ArrayList;
13+
import java.util.List;
14+
15+
import static io.ebean.annotation.IdentityGenerated.BY_DEFAULT;
16+
17+
/**
18+
* Test entity for making sure json mutation detection works with cache correctly.
19+
*
20+
* @author Jonas Pöhler, Foconis Analytics GmbH
21+
*/
22+
@Cache(enableQueryCache = true)
23+
@Entity
24+
@Table(name = "e_basic_json_cache")
25+
public class EBasicJsonCache extends Model {
26+
27+
@Id
28+
@Identity(generated = BY_DEFAULT)
29+
Integer id;
30+
31+
String name;
32+
33+
String description;
34+
35+
@DbJson(mutationDetection = MutationDetection.SOURCE)
36+
List<String> tags = new ArrayList<>();
37+
38+
@DbJson(mutationDetection = MutationDetection.DEFAULT)
39+
List<String> superTags = new ArrayList<>();
40+
41+
public Integer getId() {
42+
return id;
43+
}
44+
45+
public void setId(Integer id) {
46+
this.id = id;
47+
}
48+
49+
public String getName() {
50+
return name;
51+
}
52+
53+
public void setName(String name) {
54+
this.name = name;
55+
}
56+
57+
public String getDescription() {
58+
return description;
59+
}
60+
61+
public void setDescription(String description) {
62+
this.description = description;
63+
}
64+
65+
public List<String> getTags() {
66+
return tags;
67+
}
68+
69+
public void setTags(List<String> tags) {
70+
this.tags = tags;
71+
}
72+
73+
public List<String> getSuperTags() {
74+
return superTags;
75+
}
76+
77+
public void setSuperTags(List<String> superTags) {
78+
this.superTags = superTags;
79+
}
80+
}

0 commit comments

Comments
 (0)