#2061 - Fix for L2 bean cache loaded using lazy loading on mutated partially loaded bean

The fix is when the property is dirty (mutated) put the original value into the cache entry.
This commit is contained in:
rob bygrave
2020-09-24 22:14:48 +12:00
parent c5eb6fa034
commit de734665cd
5 changed files with 93 additions and 13 deletions
@@ -11,11 +11,9 @@ import java.util.Map;
public class CachedBeanDataFromBean {
public static CachedBeanData extract(BeanDescriptor<?> desc, EntityBean bean) {
EntityBeanIntercept ebi = bean._ebean_getIntercept();
Map<String, Object> data = new LinkedHashMap<>();
BeanProperty idProperty = desc.getIdProperty();
@@ -25,11 +23,13 @@ public class CachedBeanDataFromBean {
data.put(idProperty.getName(), idProperty.getCacheDataValue(bean));
}
}
BeanProperty[] props = desc.propertiesNonMany();
// extract all the non-many properties
for (BeanProperty prop : props) {
if (ebi.isLoadedProperty(prop.getPropertyIndex())) {
final boolean dirty = ebi.isDirty();
for (BeanProperty prop : desc.propertiesNonMany()) {
if (dirty && ebi.isDirtyProperty(prop.getPropertyIndex())) {
data.put(prop.getName(), prop.getCacheDataValueOrig(ebi));
} else if (ebi.isLoadedProperty(prop.getPropertyIndex())) {
data.put(prop.getName(), prop.getCacheDataValue(bean));
}
}
@@ -72,5 +72,4 @@ public class CachedBeanDataFromBean {
return sharableBean;
}
}
@@ -3,6 +3,7 @@ package io.ebeaninternal.server.deploy;
import com.fasterxml.jackson.core.JsonToken;
import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebean.config.EncryptKey;
import io.ebean.config.dbplatform.DbEncryptFunction;
@@ -10,7 +11,6 @@ import io.ebean.config.dbplatform.DbPlatformType;
import io.ebean.plugin.Property;
import io.ebean.text.StringParser;
import io.ebean.util.SplitName;
import io.ebean.util.StringHelper;
import io.ebeaninternal.api.SpiExpressionRequest;
import io.ebeaninternal.api.SpiQuery;
import io.ebeaninternal.api.json.SpiJsonReader;
@@ -794,7 +794,17 @@ public class BeanProperty implements ElPropertyValue, Property, STreeProperty {
* </p>
*/
public Object getCacheDataValue(EntityBean bean) {
Object value = getValue(bean);
return cacheDataConvert(getValue(bean));
}
/**
* Return the bean cache value for this property using original values.
*/
public Object getCacheDataValueOrig(EntityBeanIntercept ebi) {
return cacheDataConvert(ebi.getOrigValue(propertyIndex));
}
private Object cacheDataConvert(Object value) {
if (value == null || scalarType.isBinaryType()) {
return value;
} else {
@@ -5,6 +5,7 @@ import io.ebean.SqlUpdate;
import io.ebean.Transaction;
import io.ebean.ValuePair;
import io.ebean.bean.EntityBean;
import io.ebean.bean.EntityBeanIntercept;
import io.ebean.bean.PersistenceContext;
import io.ebean.util.SplitName;
import io.ebeaninternal.api.SpiEbeanServer;
@@ -426,9 +427,19 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> implements STr
return getPropertyType();
}
/**
* Return the bean cache value for this property using original values.
*/
public Object getCacheDataValueOrig(EntityBeanIntercept ebi) {
return cacheDataConvert(ebi.getOrigValue(propertyIndex));
}
@Override
public Object getCacheDataValue(EntityBean bean) {
Object ap = getValue(bean);
return cacheDataConvert(getValue(bean));
}
private Object cacheDataConvert(Object ap) {
if (ap == null) {
return null;
}
@@ -43,7 +43,7 @@ public class CacheBeanDataTest extends BaseTestCase {
billingAddress.setLine1("92 Someplace Else");
c.setBillingAddress(billingAddress);
((EntityBean) c)._ebean_getIntercept().setNewBeanForUpdate();
((EntityBean) c)._ebean_getIntercept().setLoaded();
CachedBeanData cacheData = CachedBeanDataFromBean.extract(desc, (EntityBean) c);
@@ -5,21 +5,24 @@ import io.ebean.bean.EntityBean;
import io.ebeaninternal.api.SpiEbeanServer;
import io.ebeaninternal.server.deploy.BeanDescriptor;
import io.ebeaninternal.server.transaction.DefaultPersistenceContext;
import org.junit.Test;
import org.tests.model.basic.Address;
import org.tests.model.basic.Car;
import org.tests.model.basic.Contact;
import org.tests.model.basic.Customer;
import org.junit.Test;
import java.sql.Date;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
public class CachedBeanDataFromBeanTest extends BaseTestCase {
SpiEbeanServer server = spiEbeanServer();
private final SpiEbeanServer server = spiEbeanServer();
@Test
public void extract() throws Exception {
public void extract() {
BeanDescriptor<Customer> desc = server.getBeanDescriptor(Customer.class);
@@ -64,4 +67,61 @@ public class CachedBeanDataFromBeanTest extends BaseTestCase {
assertEquals(newCar.getDriver(), car.getDriver());
assertEquals(newCar.getNotes(), car.getNotes());
}
@SuppressWarnings("unchecked")
@Test
public void dirtyScalar_expect_originalValueUsed() {
Contact contact = new Contact();
contact.setId(42);
contact.setLastName("Bygrave");
contact.setFirstName("Foo");
contact.setEmail("rob@email.com");
EntityBean entityBean = (EntityBean)contact;
entityBean._ebean_getIntercept().setLoaded();
// mutate, dirty
contact.setLastName("Banana");
final BeanDescriptor<Contact> desc = getBeanDescriptor(Contact.class);
CachedBeanData cacheData = CachedBeanDataFromBean.extract(desc, entityBean);
final Map<String, Object> data = cacheData.getData();
assertThat(data.get("id")).isEqualTo("42");
assertThat(data.get("lastName")).isEqualTo("Bygrave"); // ORIGINAL VALUE
assertThat(data.get("firstName")).isEqualTo(contact.getFirstName());
assertThat(data.get("email")).isEqualTo(contact.getEmail());
}
@Test
public void dirtyManyToOne_expect_originalValueUsed() {
Customer customer = new Customer();
customer.setId(99);
Contact contact = new Contact();
contact.setFirstName("Foo");
contact.setLastName("Bygrave");
contact.setEmail("rob@email.com");
contact.setCustomer(customer);
EntityBean entityBean = (EntityBean)contact;
entityBean._ebean_getIntercept().setLoaded();
// mutate, dirty
Customer customer2 = new Customer();
customer2.setId(108);
contact.setCustomer(customer2);
contact.setLastName("Banana");
final BeanDescriptor<Contact> desc = getBeanDescriptor(Contact.class);
CachedBeanData cacheData = CachedBeanDataFromBean.extract(desc, entityBean);
final Map<String, Object> data = cacheData.getData();
assertThat(data.get("lastName")).isEqualTo("Bygrave"); // Original value
assertThat(data.get("customer")).isEqualTo("99"); // Original value
assertThat(data.get("firstName")).isEqualTo(contact.getFirstName());
assertThat(data.get("email")).isEqualTo(contact.getEmail());
}
}