mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#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:
@@ -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);
|
||||
|
||||
|
||||
+63
-3
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user