mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-11-12 22:41:35 +00:00
Proof of concept to support also bean change detection / changelog on mutable properties
This commit is contained in:
1 parent
49b86d07d3
commit
5e595ec5f8
7 files changed
+180
-16
No files matched your search
@@ -2,6 +2,7 @@ package io.ebeaninternal.server.deploy;
|
||||
|
||||
import io.ebean.bean.EntityBean;
|
||||
import io.ebean.bean.EntityBeanIntercept;
|
||||
import io.ebean.bean.MutableJson;
|
||||
import io.ebean.core.type.DataReader;
|
||||
import io.ebean.text.TextException;
|
||||
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
|
||||
@@ -25,11 +26,11 @@ public class BeanPropertyJsonMapper extends BeanProperty {
|
||||
boolean isDirtyValue(Object value, EntityBeanIntercept ebi) {
|
||||
// dirty detection based on md5 hash of json content
|
||||
final String json = scalarType.jsonMapper(value);
|
||||
final String newHash = Md5.hash(json);
|
||||
final String oldHash = ebi.mutableHash(propertyIndex);
|
||||
if (!Objects.equals(newHash, oldHash)) {
|
||||
final MutableJson oldHash = ebi.mutableHash(propertyIndex);
|
||||
if (oldHash == null || !oldHash.isEqualToJson(json)) {
|
||||
ebi.mutableContent(propertyIndex, json); // so we only convert to json once
|
||||
ebi.mutableHash(propertyIndex, newHash); // for dirty detection next time
|
||||
//ebi.mutableHash(propertyIndex, scalarType.jsonMutable(json)); // for dirty detection next time
|
||||
//must be done AFTER persistControllers are called.
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -43,8 +44,8 @@ public class BeanPropertyJsonMapper extends BeanProperty {
|
||||
setValue(bean, value);
|
||||
String json = reader.popJson();
|
||||
if (json != null) {
|
||||
final String hash = Md5.hash(json);
|
||||
bean._ebean_getIntercept().mutableHash(propertyIndex, hash);
|
||||
final String hash = scalarType.format(value);
|
||||
bean._ebean_getIntercept().mutableHash(propertyIndex, scalarType.jsonMutable(hash));
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
+1
-2
@@ -32,8 +32,7 @@ class BindablePropertyJsonInsert extends BindableProperty {
|
||||
} else {
|
||||
// on insert store MD5 hash and push json
|
||||
final String json = prop.format(value);
|
||||
final String hash = Md5.hash(json);
|
||||
bean._ebean_getIntercept().mutableHash(propertyIndex, hash);
|
||||
bean._ebean_getIntercept().mutableHash(propertyIndex, prop.getScalarType().jsonMutable(json));
|
||||
request.pushJson(json);
|
||||
request.bind(value, prop);
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@ import com.fasterxml.jackson.databind.JavaType;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.ObjectWriter;
|
||||
import com.fasterxml.jackson.databind.introspect.AnnotatedField;
|
||||
|
||||
import io.ebean.bean.MutableJson;
|
||||
import io.ebean.core.type.DataBinder;
|
||||
import io.ebean.core.type.DataReader;
|
||||
import io.ebean.core.type.DocPropertyType;
|
||||
@@ -15,6 +17,7 @@ import io.ebean.text.TextException;
|
||||
import io.ebeaninternal.json.ModifyAwareList;
|
||||
import io.ebeaninternal.json.ModifyAwareMap;
|
||||
import io.ebeaninternal.json.ModifyAwareSet;
|
||||
import io.ebeaninternal.server.util.Md5;
|
||||
|
||||
import javax.persistence.PersistenceException;
|
||||
import java.io.DataInput;
|
||||
@@ -24,6 +27,7 @@ import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@@ -66,6 +70,66 @@ class ScalarTypeJsonObjectMapper {
|
||||
public String jsonMapper(Object value) {
|
||||
return formatValue(value);
|
||||
}
|
||||
|
||||
private class Md5MutableJson implements MutableJson {
|
||||
|
||||
private String md5;
|
||||
Md5MutableJson(String json) {
|
||||
md5 = Md5.hash(json);
|
||||
}
|
||||
@Override
|
||||
public boolean isEqualToObject(Object obj) {
|
||||
return true; // we cannot determine differences...
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqualToJson(String json) {
|
||||
return Md5.hash(json).equals(md5);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get() {
|
||||
return null; // cannot create object from json
|
||||
}
|
||||
@Override
|
||||
public void update(Object obj) {
|
||||
md5 = Md5.hash(format(obj));
|
||||
}
|
||||
}
|
||||
|
||||
private class PlainMutableJson implements MutableJson {
|
||||
|
||||
private String originalJson;
|
||||
PlainMutableJson(String json) {
|
||||
originalJson = json;
|
||||
}
|
||||
@Override
|
||||
public boolean isEqualToObject(Object obj) {
|
||||
return isEqualToJson(format(obj));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEqualToJson(String json) {
|
||||
return Objects.equals(originalJson, json);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object get() {
|
||||
return parse(originalJson);
|
||||
}
|
||||
@Override
|
||||
public void update(Object obj) {
|
||||
originalJson = format(obj);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public MutableJson jsonMutable(String originalJson) {
|
||||
if (false) {
|
||||
return new Md5MutableJson(originalJson);
|
||||
} else {
|
||||
return new PlainMutableJson(originalJson);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object read(DataReader reader) throws SQLException {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
package org.tests.json;
|
||||
|
||||
import io.ebean.BaseTestCase;
|
||||
import io.ebean.BeanState;
|
||||
import io.ebean.DB;
|
||||
import io.ebean.ValuePair;
|
||||
import io.ebean.event.BeanPersistAdapter;
|
||||
import io.ebean.event.BeanPersistRequest;
|
||||
import io.ebeantest.LoggedSql;
|
||||
import org.junit.Test;
|
||||
import org.tests.model.json.EBasicJsonJackson3;
|
||||
@@ -11,11 +15,33 @@ import org.tests.model.json.PlainBeanDirtyAware;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestDbJson_Jackson3 extends BaseTestCase {
|
||||
|
||||
public static class EBasicJsonListPersistController extends BeanPersistAdapter {
|
||||
|
||||
private static Map<String, ValuePair> updatedValues;
|
||||
|
||||
@Override
|
||||
public boolean isRegisterFor(Class<?> cls) {
|
||||
return EBasicJsonList.class.isAssignableFrom(cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preInsert(BeanPersistRequest<?> request) {
|
||||
updatedValues = request.getUpdatedValues();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean preUpdate(BeanPersistRequest<?> request) {
|
||||
updatedValues = request.getUpdatedValues();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void updateIncludesJsonColumn_when_explicit_isMarkedDirty() {
|
||||
|
||||
@@ -69,12 +95,54 @@ public class TestDbJson_Jackson3 extends BaseTestCase {
|
||||
found.setName("p1-mod");
|
||||
found.setBeanList(null);
|
||||
|
||||
BeanState state = DB.getBeanState(found);
|
||||
assertThat(state.getChangedProps()).containsExactlyInAnyOrder("name", "beanList");
|
||||
|
||||
ValuePair pair = state.getDirtyValues().get("name");
|
||||
assertThat(pair.getNewValue()).isEqualTo("p1-mod");
|
||||
assertThat(pair.getOldValue()).isEqualTo("p1");
|
||||
|
||||
pair = state.getDirtyValues().get("beanList");
|
||||
assertThat(pair.getNewValue()).isEqualTo(null);
|
||||
assertThat((List<PlainBean>)pair.getOldValue()).hasSize(1)
|
||||
.extracting(PlainBean::getName).containsExactly("a");
|
||||
|
||||
|
||||
LoggedSql.start();
|
||||
DB.save(found);
|
||||
|
||||
final List<String> sql = LoggedSql.stop();
|
||||
List<String> sql = LoggedSql.stop();
|
||||
assertThat(sql).hasSize(1);
|
||||
// plain_bean=?, no longer included with MD5 dirty detection
|
||||
assertThat(sql.get(0)).contains("update ebasic_json_list set name=?, bean_list=?, version=? where id=?");
|
||||
|
||||
assertThat(EBasicJsonListPersistController.updatedValues.entrySet())
|
||||
.extracting(Map.Entry::toString)
|
||||
.containsExactlyInAnyOrder("beanList=null,[name:a]","name=p1-mod,p1","version=2,1");
|
||||
|
||||
|
||||
found.getPlainBean().setName("b");
|
||||
|
||||
// CHECKME: How do we get these checks to work?
|
||||
// assertThat(DB.getBeanState(found).isDirty()).isTrue();
|
||||
|
||||
state = DB.getBeanState(found);
|
||||
assertThat(state.getChangedProps()).containsExactlyInAnyOrder("plainBean");
|
||||
pair = state.getDirtyValues().get("plainBean");
|
||||
assertThat(pair.getNewValue()).hasToString("name:b");
|
||||
assertThat(pair.getOldValue()).hasToString("name:a");
|
||||
|
||||
|
||||
LoggedSql.start();
|
||||
DB.save(found);
|
||||
|
||||
sql = LoggedSql.stop();
|
||||
assertThat(sql).hasSize(1);
|
||||
// plain_bean=?, no longer included with MD5 dirty detection
|
||||
assertThat(sql.get(0)).contains("update ebasic_json_list set plain_bean=?, version=? where id=?");
|
||||
|
||||
assertThat(EBasicJsonListPersistController.updatedValues.entrySet())
|
||||
.extracting(Map.Entry::toString)
|
||||
.containsExactlyInAnyOrder("plainBean=name:b,name:a", "version=3,2");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user