mirror of
https://github.com/ebean-orm/ebean.git
synced 2024-04-21 10:51:47 +00:00
#650 - Refactor: Optimise L2 cache entry for serialisation
This commit is contained in:
@@ -131,4 +131,10 @@ public class CachedBeanData implements Externalizable {
|
||||
return data.get(propertyName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all the property data.
|
||||
*/
|
||||
public Map<String,Object> getData() {
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -634,11 +634,13 @@ final class BeanDescriptorCacheHelp<T> {
|
||||
for (int i = 0; i < dirtyProperties.length; i++) {
|
||||
if (dirtyProperties[i]) {
|
||||
BeanProperty property = desc.propertiesIndex[i];
|
||||
Object val = property.getCacheDataValue(bean);
|
||||
changes.put(property.getName(), val);
|
||||
if (property.isNaturalKey()) {
|
||||
updateNaturalKey = true;
|
||||
changeSet.addNaturalKeyPut(desc, id, val);
|
||||
if (property.isCacheDataInclude()) {
|
||||
Object val = property.getCacheDataValue(bean);
|
||||
changes.put(property.getName(), val);
|
||||
if (property.isNaturalKey()) {
|
||||
updateNaturalKey = true;
|
||||
changeSet.addNaturalKeyPut(desc, id, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -733,11 +733,42 @@ public class BeanProperty implements ElPropertyValue, Property {
|
||||
return prefix + name + " on [" + descriptor + "] arg[" + value + "] type[" + beanType + "] threw error";
|
||||
}
|
||||
|
||||
public Object getCacheDataValue(EntityBean bean) {
|
||||
return getValue(bean);
|
||||
/**
|
||||
* Return true if this property should be included in the cache bean data.
|
||||
*/
|
||||
public boolean isCacheDataInclude() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the value for this property which we hold in the L2 cache entry.
|
||||
* <p>
|
||||
* This uses format() where possible to store the value as a string and this
|
||||
* is done to make any resulting Java object serialisation content smaller as
|
||||
* strings get special treatment.
|
||||
* </p>
|
||||
*/
|
||||
public Object getCacheDataValue(EntityBean bean) {
|
||||
Object value = getValue(bean);
|
||||
if (value == null || scalarType.isBinaryType()) {
|
||||
return value;
|
||||
} else {
|
||||
// convert to string as an optimisation for java object serialisation
|
||||
return scalarType.format(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the value for this property from L2 cache entry and set it to the bean.
|
||||
* <p>
|
||||
* This uses parse() as per the comment in getCacheDataValue().
|
||||
* </p>
|
||||
*/
|
||||
public void setCacheDataValue(EntityBean bean, Object cacheData) {
|
||||
if (cacheData instanceof String) {
|
||||
// parse back from string to support optimisation of java object serialisation
|
||||
cacheData = scalarType.parse((String)cacheData);
|
||||
}
|
||||
setValue(bean, cacheData);
|
||||
}
|
||||
|
||||
|
||||
@@ -326,6 +326,15 @@ public class BeanPropertyAssocMany<T> extends BeanPropertyAssoc<T> {
|
||||
return server.findIds(q, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exclude many properties from bean cache data.
|
||||
*/
|
||||
@Override
|
||||
public boolean isCacheDataInclude() {
|
||||
// this would change for DB Array type support
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the loaded current bean to its associated parent.
|
||||
*/
|
||||
|
||||
@@ -385,6 +385,7 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
return getPropertyType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCacheDataValue(EntityBean bean) {
|
||||
Object ap = getValue(bean);
|
||||
if (ap == null) {
|
||||
@@ -392,22 +393,23 @@ public class BeanPropertyAssocOne<T> extends BeanPropertyAssoc<T> {
|
||||
}
|
||||
if (embedded) {
|
||||
return targetDescriptor.cacheEmbeddedBeanExtract((EntityBean) ap);
|
||||
|
||||
} else {
|
||||
return targetDescriptor.getId((EntityBean) ap);
|
||||
return targetDescriptor.getIdProperty().getCacheDataValue((EntityBean) ap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCacheDataValue(EntityBean bean, Object cacheData) {
|
||||
if (cacheData != null) {
|
||||
if (cacheData == null) {
|
||||
setValue(bean, null);
|
||||
} else {
|
||||
if (embedded) {
|
||||
EntityBean embeddedBean = targetDescriptor.cacheEmbeddedBeanLoad((CachedBeanData) cacheData);
|
||||
setValue(bean, embeddedBean);
|
||||
|
||||
setValue(bean, targetDescriptor.cacheEmbeddedBeanLoad((CachedBeanData) cacheData));
|
||||
} else {
|
||||
T ref = targetDescriptor.createReference(Boolean.FALSE, cacheData);
|
||||
setValue(bean, ref);
|
||||
if (cacheData instanceof String) {
|
||||
cacheData = targetDescriptor.getIdProperty().scalarType.parse((String)cacheData);
|
||||
}
|
||||
setValue(bean, targetDescriptor.createReference(Boolean.FALSE, cacheData));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,12 @@ import java.sql.SQLException;
|
||||
*/
|
||||
public interface ScalarType<T> extends StringParser, StringFormatter, ScalarDataReader<T> {
|
||||
|
||||
/**
|
||||
* Return true if this is a binary type and can not support parse() and format() from/to string.
|
||||
* This allows Ebean to optimise marshalling types to string.
|
||||
*/
|
||||
boolean isBinaryType();
|
||||
|
||||
/**
|
||||
* Return true if this is a mutable scalar type (like hstore).
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,12 @@ public abstract class ScalarTypeBase<T> implements ScalarType<T> {
|
||||
throw new RuntimeException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
// override for binary/byte based types
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of mutable false.
|
||||
*/
|
||||
|
||||
@@ -23,6 +23,11 @@ public class ScalarTypeByte extends ScalarTypeBase<Byte> {
|
||||
super(Byte.class, true, Types.TINYINT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public void bind(DataBind b, Byte value) throws SQLException {
|
||||
if (value == null) {
|
||||
b.setNull(Types.TINYINT);
|
||||
|
||||
@@ -20,6 +20,11 @@ public abstract class ScalarTypeBytesBase extends ScalarTypeBase<byte[]> {
|
||||
super(byte[].class, jdbcNative, jdbcType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public byte[] convertToBytes(Object value) {
|
||||
return (byte[]) value;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,11 @@ public class ScalarTypeBytesEncrypted implements ScalarType<byte[]> {
|
||||
throw new RuntimeException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return false;
|
||||
|
||||
@@ -28,6 +28,11 @@ public class ScalarTypeEncryptedWrapper<T> implements ScalarType<T> {
|
||||
throw new RuntimeException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return wrapped.isBinaryType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return wrapped.isMutable();
|
||||
|
||||
@@ -29,6 +29,11 @@ public class ScalarTypeEnumWithMapping extends ScalarTypeEnumStandard.EnumBase i
|
||||
throw new RuntimeException("not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the IN values for DB constraint construction.
|
||||
*/
|
||||
|
||||
@@ -54,6 +54,11 @@ public class ScalarTypeFile extends ScalarTypeBase<File> {
|
||||
this.bufferSize = bufferSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return true;
|
||||
}
|
||||
|
||||
private InputStream getInputStream(File value) throws IOException {
|
||||
FileInputStream fi = new FileInputStream(value);
|
||||
return new BufferedInputStream(fi, bufferSize);
|
||||
|
||||
@@ -47,6 +47,11 @@ public class ScalarTypeWrapper<B, S> implements ScalarType<B> {
|
||||
return scalarType.asVersion(unwrapValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBinaryType() {
|
||||
return scalarType.isBinaryType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMutable() {
|
||||
return scalarType.isMutable();
|
||||
|
||||
+2
-2
@@ -32,9 +32,9 @@ public class CachedBeanDataFromBeanTest extends BaseTestCase {
|
||||
|
||||
CachedBeanData cacheData = CachedBeanDataFromBean.extract(desc, (EntityBean) customer);
|
||||
|
||||
assertEquals(cacheData.getData("id"), Integer.valueOf(42));
|
||||
assertEquals(cacheData.getData("id"), "42");
|
||||
assertEquals(cacheData.getData("name"), "Rob");
|
||||
assertEquals(cacheData.getData("billingAddress"), Short.valueOf("12"));
|
||||
assertEquals(cacheData.getData("billingAddress"), "12");
|
||||
}
|
||||
|
||||
|
||||
|
||||
+137
@@ -0,0 +1,137 @@
|
||||
package com.avaje.ebeaninternal.server.cache;
|
||||
|
||||
import com.avaje.ebean.BaseTestCase;
|
||||
import com.avaje.ebean.Ebean;
|
||||
import com.avaje.ebean.bean.EntityBean;
|
||||
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
|
||||
import com.avaje.tests.model.basic.Customer;
|
||||
import com.avaje.tests.model.basic.ResetBasicData;
|
||||
import com.avaje.tests.model.basic.TBytesOnly;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
public class CachedBeanDataSerializeTest extends BaseTestCase {
|
||||
|
||||
@Test
|
||||
public void write() throws IOException, ClassNotFoundException {
|
||||
|
||||
Map<String, Object> map = new LinkedHashMap<String,Object>();
|
||||
map.put("name", "rob");
|
||||
map.put("some", "thing");
|
||||
map.put("whenCreated", ""+System.currentTimeMillis());
|
||||
|
||||
long version = System.currentTimeMillis();
|
||||
CachedBeanData write = new CachedBeanData(null, "C", map, version);
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ObjectOutputStream oos = new ObjectOutputStream(os);
|
||||
|
||||
write.writeExternal(oos);
|
||||
oos.flush();
|
||||
oos.close();
|
||||
|
||||
byte[] bytes = os.toByteArray();
|
||||
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(is);
|
||||
|
||||
CachedBeanData read = new CachedBeanData();
|
||||
read.readExternal(ois);
|
||||
|
||||
assertEquals(read.getVersion(), write.getVersion());
|
||||
assertEquals(read.getWhenCreated(), write.getWhenCreated());
|
||||
assertEquals(read.getDiscValue(), write.getDiscValue());
|
||||
assertEquals(read.getData(), write.getData());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void fullBean() throws IOException, ClassNotFoundException {
|
||||
|
||||
ResetBasicData.reset();
|
||||
|
||||
List<Customer> customers = Ebean.find(Customer.class)
|
||||
.orderBy().asc("id")
|
||||
.setMaxRows(1).findList();
|
||||
|
||||
Customer customer = customers.get(0);
|
||||
|
||||
BeanDescriptor<Customer> desc = getBeanDescriptor(Customer.class);
|
||||
CachedBeanData extract = CachedBeanDataFromBean.extract(desc, (EntityBean)customer);
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
writeToStream(extract, os);
|
||||
|
||||
byte[] bytes = os.toByteArray();
|
||||
|
||||
CachedBeanData read = readFromStream(bytes);
|
||||
|
||||
assertEquals(read.getData(), extract.getData());
|
||||
|
||||
Customer loadCustomer = new Customer();
|
||||
CachedBeanDataToBean.load(desc, (EntityBean)loadCustomer, read);
|
||||
|
||||
assertEquals(loadCustomer.getVersion(), customer.getVersion());
|
||||
assertEquals(loadCustomer.getId(), customer.getId());
|
||||
assertEquals(loadCustomer.getName(), customer.getName());
|
||||
assertEquals(loadCustomer.getStatus(), customer.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void beanWithByteArray() throws IOException, ClassNotFoundException {
|
||||
|
||||
String stringContent = "ThisIsSome";
|
||||
|
||||
TBytesOnly bean = new TBytesOnly();
|
||||
bean.setId(42);
|
||||
bean.setContent(stringContent.getBytes("UTF8"));
|
||||
|
||||
BeanDescriptor<TBytesOnly> desc = getBeanDescriptor(TBytesOnly.class);
|
||||
CachedBeanData extract = CachedBeanDataFromBean.extract(desc, (EntityBean)bean);
|
||||
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
writeToStream(extract, os);
|
||||
byte[] bytes = os.toByteArray();
|
||||
|
||||
CachedBeanData read = readFromStream(bytes);
|
||||
byte[] extraContent = (byte[])extract.getData("content");
|
||||
|
||||
assertEquals(stringContent, new String(extraContent));
|
||||
assertTrue(Arrays.equals(bean.getContent(), extraContent));
|
||||
|
||||
TBytesOnly loadBean= new TBytesOnly();
|
||||
CachedBeanDataToBean.load(desc, (EntityBean)loadBean, read);
|
||||
|
||||
assertEquals(loadBean.getId(), bean.getId());
|
||||
assertTrue(Arrays.equals(loadBean.getContent(), bean.getContent()));
|
||||
}
|
||||
|
||||
private CachedBeanData readFromStream(byte[] bytes) throws IOException, ClassNotFoundException {
|
||||
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(bytes);
|
||||
ObjectInputStream ois = new ObjectInputStream(is);
|
||||
return (CachedBeanData)ois.readObject();
|
||||
}
|
||||
|
||||
private void writeToStream(CachedBeanData extract, ByteArrayOutputStream os) throws IOException {
|
||||
|
||||
ObjectOutputStream oos = new ObjectOutputStream(os);
|
||||
oos.writeObject(extract);
|
||||
oos.flush();
|
||||
oos.close();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user