From 7db9f30577700ff03d5c28bf3a727f5bff82a622 Mon Sep 17 00:00:00 2001 From: jackygurui Date: Fri, 3 Jun 2016 02:00:50 +0100 Subject: [PATCH] Initial commit for LiveObject Proof of concept, work in progress. --- pom.xml | 6 +- src/main/java/org/redisson/Redisson.java | 5 + .../RedissonAttachedLiveObjectService.java | 75 ++++++++++ .../java/org/redisson/RedissonClient.java | 3 + .../java/org/redisson/RedissonReference.java | 81 +++++++++++ .../RAttachedLiveObjectService.java | 25 ++++ .../RDetachedLiveObjectService.java | 90 ++++++++++++ .../liveobject/RLiveObjectService.java | 31 +++++ .../liveobject/annotation/REntity.java | 35 +++++ .../redisson/liveobject/annotation/RId.java | 17 +++ .../liveobject/core/AccessorInterceptor.java | 128 ++++++++++++++++++ .../liveobject/misc/Introspectior.java | 52 +++++++ .../resolver/FieldValueAsIdGenerator.java | 18 +++ .../liveobject/resolver/Resolver.java | 15 ++ ...RedissonAttachedLiveObjectServiceTest.java | 74 ++++++++++ 15 files changed, 654 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/redisson/RedissonAttachedLiveObjectService.java create mode 100644 src/main/java/org/redisson/RedissonReference.java create mode 100644 src/main/java/org/redisson/liveobject/RAttachedLiveObjectService.java create mode 100644 src/main/java/org/redisson/liveobject/RDetachedLiveObjectService.java create mode 100644 src/main/java/org/redisson/liveobject/RLiveObjectService.java create mode 100644 src/main/java/org/redisson/liveobject/annotation/REntity.java create mode 100644 src/main/java/org/redisson/liveobject/annotation/RId.java create mode 100644 src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java create mode 100644 src/main/java/org/redisson/liveobject/misc/Introspectior.java create mode 100644 src/main/java/org/redisson/liveobject/resolver/FieldValueAsIdGenerator.java create mode 100644 src/main/java/org/redisson/liveobject/resolver/Resolver.java create mode 100644 src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java diff --git a/pom.xml b/pom.xml index 1e09ffc38..2a7e1d307 100644 --- a/pom.xml +++ b/pom.xml @@ -228,7 +228,11 @@ zero-allocation-hashing 0.5 - + + net.bytebuddy + byte-buddy + 1.3.19 + org.springframework spring-context diff --git a/src/main/java/org/redisson/Redisson.java b/src/main/java/org/redisson/Redisson.java index 4d6f929ab..f735b0c65 100755 --- a/src/main/java/org/redisson/Redisson.java +++ b/src/main/java/org/redisson/Redisson.java @@ -531,6 +531,11 @@ public class Redisson implements RedissonClient { return new RedissonBatch(evictionScheduler, connectionManager); } + @Override + public RedissonAttachedLiveObjectService getAttachedLiveObjectService() { + return new RedissonAttachedLiveObjectService(this, commandExecutor); + } + @Override public void shutdown() { connectionManager.shutdown(); diff --git a/src/main/java/org/redisson/RedissonAttachedLiveObjectService.java b/src/main/java/org/redisson/RedissonAttachedLiveObjectService.java new file mode 100644 index 000000000..6351981ea --- /dev/null +++ b/src/main/java/org/redisson/RedissonAttachedLiveObjectService.java @@ -0,0 +1,75 @@ +package org.redisson; + +import io.netty.util.internal.PlatformDependent; +import java.util.Map; +import net.bytebuddy.ByteBuddy; +import net.bytebuddy.dynamic.loading.ClassLoadingStrategy; +import net.bytebuddy.implementation.MethodDelegation; +import net.bytebuddy.matcher.ElementMatchers; +import org.redisson.command.CommandExecutor; +import org.redisson.liveobject.RAttachedLiveObjectService; +import org.redisson.liveobject.annotation.RId; +import org.redisson.liveobject.core.AccessorInterceptor; +import org.redisson.liveobject.misc.Introspectior; + +public class RedissonAttachedLiveObjectService implements RAttachedLiveObjectService { + + private static final Map classCache + = PlatformDependent.newConcurrentHashMap(); + private static final Map proxyCache + = PlatformDependent.newConcurrentHashMap(); + private final RedissonClient redisson; + private final CommandExecutor commandExecutor; + + public RedissonAttachedLiveObjectService(RedissonClient redisson, CommandExecutor commandExecutor) { + this.redisson = redisson; + this.commandExecutor = commandExecutor; + } + + //TODO: Support ID Generator + + @Override + public T get(Class entityClass, K id, long ttl) { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public T get(Class entityClass, K id) { + try { + //TODO: support class with no arg constructor + return getProxyClass(entityClass).getConstructor(id.getClass()).newInstance(id); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + private Class getProxyClass(Class entityClass) throws Exception { + if (!classCache.containsKey(entityClass)) { + registerClass(entityClass); + } + return classCache.get(entityClass); + } + + private void registerClass(Class entityClass) throws Exception { + //TODO: check annotation on the entityClass + String idFieldName = Introspectior.getFieldsWithAnnotation(entityClass, RId.class) + .getOnly() + .getName(); + classCache.putIfAbsent(entityClass, new ByteBuddy() + .subclass(entityClass) + .method(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class)) + .and(ElementMatchers.isGetter() + .or(ElementMatchers.isSetter())) + .and(ElementMatchers.isPublic())) + .intercept(MethodDelegation.to(new AccessorInterceptor(redisson, entityClass, idFieldName, commandExecutor))) + .make().load(getClass().getClassLoader(), + ClassLoadingStrategy.Default.WRAPPER) + .getLoaded()); + proxyCache.putIfAbsent(classCache.get(entityClass), entityClass); + } + + public static Class getActualClass(Class proxyClass) { + return proxyCache.get(proxyClass); + } + +} diff --git a/src/main/java/org/redisson/RedissonClient.java b/src/main/java/org/redisson/RedissonClient.java index f2aa29c16..3f7bf4184 100755 --- a/src/main/java/org/redisson/RedissonClient.java +++ b/src/main/java/org/redisson/RedissonClient.java @@ -21,6 +21,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import org.redisson.client.codec.Codec; +import org.redisson.command.CommandExecutor; import org.redisson.core.ClusterNode; import org.redisson.core.Node; import org.redisson.core.NodesGroup; @@ -638,6 +639,8 @@ public interface RedissonClient { */ RKeys getKeys(); + RedissonAttachedLiveObjectService getAttachedLiveObjectService(); + /** * Shuts down Redisson instance NOT Redis server * diff --git a/src/main/java/org/redisson/RedissonReference.java b/src/main/java/org/redisson/RedissonReference.java new file mode 100644 index 000000000..e6c1447a3 --- /dev/null +++ b/src/main/java/org/redisson/RedissonReference.java @@ -0,0 +1,81 @@ +package org.redisson; + +import org.redisson.client.codec.Codec; +import org.redisson.core.RObject; + +/** + * + * @author ruigu + */ +public class RedissonReference { + private String type; + private Object keyName; + private String codec; + + public RedissonReference() { + } + + public RedissonReference(Class type, Object keyName) { + this.type = type.getCanonicalName(); + this.keyName = keyName; + this.codec = null; + } + + public RedissonReference(Class type, Object keyName, Codec codec) { + this.type = type.getCanonicalName(); + this.keyName = keyName; + this.codec = codec.getClass().getCanonicalName(); + } + + public boolean isDefaultCodec() { + return codec == null; + } + + /** + * @return the type + */ + public Class getType() { + try { + return (Class) Class.forName(type); + } catch (ClassNotFoundException ex) { + return null; + } + } + + /** + * @param type the type to set + */ + public void setType(Class type) { + this.type = type.getCanonicalName(); + } + + /** + * @return the keyName + */ + public Object getKeyName() { + return keyName; + } + + /** + * @param keyName the keyName to set + */ + public void setKeyName(Object keyName) { + this.keyName = keyName; + } + + /** + * @return the codec + */ + public Codec getCodec() throws Exception { + return (Codec) Class.forName(codec).newInstance(); + } + + /** + * @param codec the codec to set + */ + public void setCodec(Codec codec) { + this.codec = codec.getClass().getCanonicalName(); + } + + +} diff --git a/src/main/java/org/redisson/liveobject/RAttachedLiveObjectService.java b/src/main/java/org/redisson/liveobject/RAttachedLiveObjectService.java new file mode 100644 index 000000000..66fab5b76 --- /dev/null +++ b/src/main/java/org/redisson/liveobject/RAttachedLiveObjectService.java @@ -0,0 +1,25 @@ +package org.redisson.liveobject; + +/** + * + * @author ruigu + * + * @param Entity type + * @param Key type + */ +public interface RAttachedLiveObjectService extends RLiveObjectService { + + /** + * Finds the entity from Redis with the id. + * + * @param entityClass Entity class + * @param id identifier + * @param ttl sets the time to live on the object. Any calls to the accessor + * of this object will renew this. If it is not been accessed + * before the ttl reaches. This object is then expires and + * removed from redis. Think of it is been garbage collected. + * @return In ATTACHED Mode, this always returns a proxy class. Even it does + * not exist in redis. + */ + public T get(Class entityClass, K id, long ttl); +} diff --git a/src/main/java/org/redisson/liveobject/RDetachedLiveObjectService.java b/src/main/java/org/redisson/liveobject/RDetachedLiveObjectService.java new file mode 100644 index 000000000..1f0accddd --- /dev/null +++ b/src/main/java/org/redisson/liveobject/RDetachedLiveObjectService.java @@ -0,0 +1,90 @@ +package org.redisson.liveobject; + +import io.netty.util.concurrent.Future; + +/** + * + * @author ruigu + * + * @param Entity type + * @param Key type + */ +public interface RDetachedLiveObjectService extends RLiveObjectService { + + + /** + * Finds the entity from Redis with the id. + * + * @param entityClass Entity class + * @param id identifier + * @return In ATTACHED Mode, this always returns a proxy class. Even it does + * not exist in redis. + * In DETACHED Mode, this returns an instance of the entity class. + * IF it doesn't exist in redis, null is returned. + */ + @Override + public T get(Class entityClass, K id); + + /** + * Finds the entity from Redis with the id. + * + * @param entityClass Entity class + * @param id identifier + * @return In ATTACHED Mode, this always returns a proxy class. Even it does + * not exist in redis. + * In DETACHED Mode, this returns an instance of the entity class. + * IF it doesn't exist in redis, a runtime exception is thrown. + * @throws org.redisson.liveobject.REntityNotFoundException A Runtime + * exception is thrown when the entity does not exist in Redis. + */ + public Future getAsync(Class entityClass, K id); + + /** + * Persist the instance into redis + * + * @param instance the instance to be persisted + * @return K The id of the object. + */ + public K persist(T instance); + + /** + * Persist the instance into redis + * + * @param instance the instance to be persisted + * @return K The id of the object. + */ + public Future persistAsync(T instance); + + /** + * Persist the instance into redis with specified time to live. + * + * @param instance the instance to be persisted + * @param ttl the time to live of the instance + * @return K The id of the object. + */ + public K persist(T instance, long ttl); + + /** + * Persist the instance into redis with specified time to live. + * + * @param instance + * @param ttl the time to live of the instance + * @return K The id of the object. + */ + public Future persistAsync(T instance, long ttl); + + /** + * Remove the instance from redis by specifying the id + * + * @param id + */ + public void remove(K id); + + /** + * Remove the instance from redis by specifying the id + * + * @param id + * @return Future. + */ + public Future removeAsync(K id); +} diff --git a/src/main/java/org/redisson/liveobject/RLiveObjectService.java b/src/main/java/org/redisson/liveobject/RLiveObjectService.java new file mode 100644 index 000000000..08062ebdb --- /dev/null +++ b/src/main/java/org/redisson/liveobject/RLiveObjectService.java @@ -0,0 +1,31 @@ +package org.redisson.liveobject; + +/** + * The pre-registration of each entity class is not necessary. + * + * In ATTACHED Mode, entity's getters and setters propagate operations to Redis + * automatically. + * + * In DETACHED Mode, entity's field values are kept local and only pushed to + * Redis when update is called. + * + * @author ruigu + * + * @param Entity type + * @param Key type + */ +public interface RLiveObjectService { + + /** + * Finds the entity from Redis with the id. + * + * @param entityClass Entity class + * @param id identifier + * @return In ATTACHED Mode, this always returns a proxy class. Even it does + * not exist in redis. In DETACHED Mode, this returns an instance of the + * entity class. IF it doesn't exist in redis, a runtime exception is + * thrown. + */ + public T get(Class entityClass, K id); + +} diff --git a/src/main/java/org/redisson/liveobject/annotation/REntity.java b/src/main/java/org/redisson/liveobject/annotation/REntity.java new file mode 100644 index 000000000..3624d9aa9 --- /dev/null +++ b/src/main/java/org/redisson/liveobject/annotation/REntity.java @@ -0,0 +1,35 @@ +package org.redisson.liveobject.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author ruigu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE}) +public @interface REntity { + + + Class namingScheme() default DefaultNamingScheme.class; + + public interface NamingScheme { + + public String getName(Class cls, String idFieldName, Object id); + + } + + public class DefaultNamingScheme implements NamingScheme { + + public static final DefaultNamingScheme INSTANCE = new DefaultNamingScheme(); + + @Override + public String getName(Class cls, String idFieldName, Object id) { + return "redisson_live_object:{class=" + cls.getCanonicalName() + ", " + idFieldName + "=" + id.toString() + "}"; + } + + } +} diff --git a/src/main/java/org/redisson/liveobject/annotation/RId.java b/src/main/java/org/redisson/liveobject/annotation/RId.java new file mode 100644 index 000000000..0dfbf6aef --- /dev/null +++ b/src/main/java/org/redisson/liveobject/annotation/RId.java @@ -0,0 +1,17 @@ +package org.redisson.liveobject.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.redisson.liveobject.resolver.FieldValueAsIdGenerator; + +/** + * + * @author ruigu + */ +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD}) +public @interface RId { + Class generator() default FieldValueAsIdGenerator.class; +} diff --git a/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java b/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java new file mode 100644 index 000000000..2c77164d4 --- /dev/null +++ b/src/main/java/org/redisson/liveobject/core/AccessorInterceptor.java @@ -0,0 +1,128 @@ +package org.redisson.liveobject.core; + +import java.lang.reflect.Method; +import java.util.concurrent.Callable; +import net.bytebuddy.implementation.bind.annotation.AllArguments; +import net.bytebuddy.implementation.bind.annotation.Origin; +import net.bytebuddy.implementation.bind.annotation.RuntimeType; +import net.bytebuddy.implementation.bind.annotation.SuperCall; +import net.bytebuddy.implementation.bind.annotation.This; +import org.redisson.RedissonAttachedLiveObjectService; +import org.redisson.RedissonClient; +import org.redisson.RedissonReference; +import org.redisson.client.RedisException; +import org.redisson.client.codec.Codec; +import org.redisson.command.CommandExecutor; +import org.redisson.core.RMap; +import org.redisson.core.RObject; +import org.redisson.liveobject.annotation.REntity; +import org.redisson.liveobject.annotation.RId; +import org.redisson.liveobject.misc.Introspectior; + +/** + * + * @author ruigu + */ +public class AccessorInterceptor { + + private final RedissonClient redisson; + private final Class originalClass; + private final String idFieldName; + private final REntity.NamingScheme namingScheme; + private final CommandExecutor commandExecutor; + private RMap liveMap; + + public AccessorInterceptor(RedissonClient redisson, Class entityClass, String idFieldName, CommandExecutor commandExecutor) throws Exception { + this.redisson = redisson; + this.originalClass = entityClass; + this.idFieldName = idFieldName; + this.commandExecutor = commandExecutor; + this.namingScheme = ((REntity) entityClass.getAnnotation(REntity.class)).namingScheme().newInstance(); + } + + @RuntimeType + public Object intercept(@Origin Method method, @SuperCall Callable superMethod, @AllArguments Object[] args, @This T me) throws Exception { + if (isGetter(method, idFieldName)) { + return superMethod.call(); + } + initLiveMapIfRequired(getId(me)); + if (isSetter(method, idFieldName)) { + superMethod.call(); + try { + liveMap.rename(getMapKey((K) args[0])); + } catch (RedisException e) { + if (e.getMessage() == null || !e.getMessage().startsWith("ERR no such key")) { + throw e; + } + } + liveMap = null; + return null; + } + String fieldName = getFieldName(method); + if (isGetter(method, fieldName)) { + Object result = liveMap.get(fieldName); + if (method.getReturnType().isAnnotationPresent(REntity.class)) { + return redisson.getAttachedLiveObjectService().get((Class) method.getReturnType(), result); + } else if (result instanceof RedissonReference) { + RedissonReference r = ((RedissonReference) result); + return r.getType().getConstructor(Codec.class, CommandExecutor.class, String.class).newInstance(r.getCodec(), commandExecutor, r.getKeyName()); + } + return result; + } + if (isSetter(method, fieldName)) { + if (method.getParameterTypes()[0].isAnnotationPresent(REntity.class)) { + return liveMap.fastPut(fieldName, getREntityId(args[0])); + } else if (args[0] instanceof RObject) { + RObject ar = (RObject) args[0]; + return liveMap.fastPut(fieldName, new RedissonReference((Class) args[0].getClass(), ar.getName())); + } + return liveMap.fastPut(fieldName, args[0]); + } + return superMethod.call(); + } + + private void initLiveMapIfRequired(K id) { + if (liveMap == null) { + liveMap = redisson.getMap(getMapKey(id)); + } + } + + private String getFieldName(Method method) { + return method.getName().substring(3, 4).toLowerCase() + method.getName().substring(4); + } + + private boolean isGetter(Method method, String fieldName) { + return method.getName().startsWith("get") + && method.getName().endsWith(getFieldNameSuffix(fieldName)); + } + + private boolean isSetter(Method method, String fieldName) { + return method.getName().startsWith("set") + && method.getName().endsWith(getFieldNameSuffix(fieldName)); + } + + private String getMapKey(K id) { + return namingScheme.getName(originalClass, idFieldName, id); + } + + private K getId(T me) throws Exception { + return (K) originalClass.getDeclaredMethod("get" + getFieldNameSuffix(idFieldName)).invoke(me); + } + + private static String getFieldNameSuffix(String fieldName) { + return fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1); + } + + private static Object getFieldValue(Object o, String fieldName) throws Exception { + return RedissonAttachedLiveObjectService.getActualClass(o.getClass()).getDeclaredMethod("get" + getFieldNameSuffix(fieldName)).invoke(o); + } + + private static Object getREntityId(Object o) throws Exception { + String idName = Introspectior + .getFieldsWithAnnotation(RedissonAttachedLiveObjectService.getActualClass(o.getClass()), RId.class) + .getOnly() + .getName(); + return getFieldValue(o, idName); + } + +} diff --git a/src/main/java/org/redisson/liveobject/misc/Introspectior.java b/src/main/java/org/redisson/liveobject/misc/Introspectior.java new file mode 100644 index 000000000..9356a699f --- /dev/null +++ b/src/main/java/org/redisson/liveobject/misc/Introspectior.java @@ -0,0 +1,52 @@ +package org.redisson.liveobject.misc; + +import io.netty.util.internal.PlatformDependent; +import java.lang.annotation.Annotation; +import java.util.concurrent.ConcurrentMap; +import net.bytebuddy.description.field.FieldDescription; +import net.bytebuddy.description.field.FieldList; +import net.bytebuddy.description.method.MethodDescription; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatchers; + +/** + * + * @author ruigu + */ +public class Introspectior { + + private static final ConcurrentMap tCache = PlatformDependent.newConcurrentHashMap(); + + public static TypeDescription.ForLoadedType getTypeDescription(Class c) { + if (!tCache.containsKey(c)) { + return new TypeDescription.ForLoadedType(c); + } + return tCache.get(c); + } + + public static MethodDescription getMethodDescription(Class c, String method) { + if (method == null || method.isEmpty()) { + return null; + } + return getTypeDescription(c) + .getDeclaredMethods() + .filter(ElementMatchers.hasMethodName(method)) + .getOnly(); + } + + public static FieldDescription getFieldDescription(Class c, String field) { + if (field == null || field.isEmpty()) { + return null; + } + return getTypeDescription(c) + .getDeclaredFields() + .filter(ElementMatchers.named(field)) + .getOnly(); + } + + public static FieldList getFieldsWithAnnotation(Class c, Class a) { + return getTypeDescription(c) + .getDeclaredFields() + .filter(ElementMatchers.isAnnotatedWith(a)); + } +} diff --git a/src/main/java/org/redisson/liveobject/resolver/FieldValueAsIdGenerator.java b/src/main/java/org/redisson/liveobject/resolver/FieldValueAsIdGenerator.java new file mode 100644 index 000000000..cda164c13 --- /dev/null +++ b/src/main/java/org/redisson/liveobject/resolver/FieldValueAsIdGenerator.java @@ -0,0 +1,18 @@ +package org.redisson.liveobject.resolver; + +import org.redisson.liveobject.annotation.RId; + +/** + * + * @author ruigu + */ +public class FieldValueAsIdGenerator implements Resolver{ + + public static final FieldValueAsIdGenerator INSTANCE = new FieldValueAsIdGenerator(); + + @Override + public String resolve(Object value, RId index) { + return value.toString(); + } + +} diff --git a/src/main/java/org/redisson/liveobject/resolver/Resolver.java b/src/main/java/org/redisson/liveobject/resolver/Resolver.java new file mode 100644 index 000000000..69b5941e8 --- /dev/null +++ b/src/main/java/org/redisson/liveobject/resolver/Resolver.java @@ -0,0 +1,15 @@ +package org.redisson.liveobject.resolver; + +import java.lang.annotation.Annotation; + +/** + * + * @author ruigu + * @param Field instance + * @param Annotation to resolve + */ +public interface Resolver { + + public V resolve(T value, A index); + +} diff --git a/src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java b/src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java new file mode 100644 index 000000000..f18385a7b --- /dev/null +++ b/src/test/java/org/redisson/RedissonAttachedLiveObjectServiceTest.java @@ -0,0 +1,74 @@ +package org.redisson; + +import java.io.Serializable; +import static org.junit.Assert.*; +import org.junit.Test; +import org.redisson.core.RMap; +import org.redisson.liveobject.annotation.REntity; +import org.redisson.liveobject.annotation.RId; + +/** + * + * @author ruigu + */ +public class RedissonAttachedLiveObjectServiceTest extends BaseTest { + + @REntity + public static class TestREntity implements Comparable, Serializable { + + @RId + private String name; + private String value; + + public TestREntity(String name) { + this.name = name; + } + + public TestREntity(String name, String value) { + super(); + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + public void setName(String name) { + this.name = name; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public int compareTo(TestREntity o) { + int res = name.compareTo(o.name); + if (res == 0) { + return value.compareTo(o.value); + } + return res; + } + } + + @Test + public void test() { + RedissonAttachedLiveObjectService s = redisson.getAttachedLiveObjectService(); + TestREntity t = s.get(TestREntity.class, "1"); + assertEquals("1", t.getName()); + assertTrue(!redisson.getMap(REntity.DefaultNamingScheme.INSTANCE.getName(TestREntity.class, "name", "1")).isExists()); + t.setName("3333"); + assertEquals("3333", t.getName()); + assertTrue(!redisson.getMap(REntity.DefaultNamingScheme.INSTANCE.getName(TestREntity.class, "name", "3333")).isExists()); + t.setValue("111"); + assertEquals("111", t.getValue()); + assertTrue(redisson.getMap(REntity.DefaultNamingScheme.INSTANCE.getName(TestREntity.class, "name", "3333")).isExists()); + assertTrue(!redisson.getMap(REntity.DefaultNamingScheme.INSTANCE.getName(TestREntity.class, "name", "1")).isExists()); + assertEquals("111", redisson.getMap(REntity.DefaultNamingScheme.INSTANCE.getName(TestREntity.class, "name", "3333")).get("value")); + } +}