├── .gitignore ├── README.md ├── pom.xml └── src └── main └── java └── org └── rojo ├── annotations ├── Entity.java ├── Id.java ├── Index.java └── Value.java ├── exceptions ├── InvalidTypeException.java ├── RepositoryError.java └── RojoException.java ├── repository ├── DefaultGenerator.java ├── EntityRepresentation.java ├── IdGenerator.java ├── RedisFacade.java └── Rojo.java ├── test ├── BaseEntity.java ├── Test.java ├── TestEntity.java ├── TestGenerator.java ├── TestSoft.java └── Work.java └── util ├── Cache.java ├── CacheoutListerner.java ├── LruCache.java ├── PriorityQueue.java ├── SoftCache.java └── Stats.java /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | .classpath 3 | .project 4 | .settings/ 5 | .idea/ 6 | *.iml 7 | *.iws 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rojo 2 | 3 | 4 | 原是github上的一个开源项目[rojo](https://github.com/giulio/rojo),本作进行了重构 5 | 使得它更加高效,功能上也更完善。 6 | 7 | ## 问题 8 | 9 | 使用redis的java客户端jedis的时候需要操作一系列key,保存某个对象的时候,需要把一个 10 | 个属性组合为key,然后set到redis,较繁琐。 11 | 12 | ## 解决方案 13 | 14 | rojo是为了简化对象持久化到redis时的操作的,它提供了几个运行时解析的annotation来做 15 | 这件事情,把用户从设计对象属性的各种key的繁重劳作中解脱出来,而把精力主要放在对象 16 | 模型的设计上。 17 | 18 | ## 使用 19 | 20 | 直接上代码: 21 | ```java 22 | @Entity 23 | public class BaseEntity { 24 | 25 | @Id(Generator = "common::id") 26 | private long id; 27 | @Value(unique = true) 28 | private String name; 29 | 30 | public long getId() { 31 | return id; 32 | } 33 | 34 | public String getName() { 35 | return name; 36 | } 37 | 38 | public void setName(String name) { 39 | this.name = name; 40 | } 41 | 42 | } 43 | ``` 44 | 45 | 这个BaseEntity使用了几个annotation。Entity表示此对象可被持久化到redis;Id指定了此类 46 | 对象的唯一id,Id拥有Generator属性,用来定制id生成策略,默认为空字符串,表示需要用户自行 47 | 管理id。@Id(Generator = "common::id")表示使用common::id这个redis的自增长key来自动生成id 48 | Value这个annotation可标注于基本类型(byte、short、int、long、float、double、String) 49 | 的属性上,以及元素为基本类型的集合(Collection、List、Set)以及Map属性上。 50 | Value拥有几个重要是属性。当被标注的属性为基本类型时,可以指定它的unique为true(默认false) 51 | 以表达被标注属性的唯一性,比如BaseEntity的name属性就是唯一的,当持久化BaseEntity时,会 52 | 查看是否已经有一个与此对象相同(equal)的name属性的BaseEntity存在,如存在则持久化失败 53 | 下面再看另一个Entity。 54 | 55 | ```java 56 | @Entity(Cache=true) 57 | public class TestEntity extends BaseEntity { 58 | 59 | @Value 60 | private List list; 61 | @Value 62 | private Set set; 63 | @Value 64 | private Map map; 65 | @Value(sort = true, bigFirst = true, size = 100) 66 | private int age; 67 | 68 | public void setAge(int age) { 69 | this.age = age; 70 | } 71 | 72 | public int getAge() { 73 | return age; 74 | } 75 | 76 | public void setMap(Map map) { 77 | this.map = map; 78 | } 79 | 80 | public Map getMap() { 81 | return map; 82 | } 83 | 84 | public void setSet(Set set) { 85 | this.set = set; 86 | } 87 | 88 | public Set getSet() { 89 | return set; 90 | } 91 | 92 | public List getList() { 93 | return list; 94 | } 95 | 96 | public void setList(List strings) { 97 | this.list = strings; 98 | } 99 | 100 | } 101 | ``` 102 | 103 | 这个TestEntity是继承自BaseEntity,因此id和name与BaseEntity相同不在赘述。此外此类 104 | 拥有list、set、map和age四个属性,list、set、map分别是元素类型为基本类型的集合类,也 105 | 在上面曾提过不赘述。看一下age这个属性,标注的Value注解拥有三个属性:sort、bigFirst、 106 | size,用来表达持久化时按照此属性值排序到一个有序列表里面;sort为true表明需要排序, 107 | bigFirst指定排序是按照从大到小的顺序,size指明排序列表的总长度(此例中排序列表长100) 108 | 109 | 看一下如何持久化TestEntity到redis: 110 | ``` 111 | Jedis je = new Jedis("localhost", 6379); 112 | je.auth("4swardsman"); 113 | je.ping(); 114 | je.flushAll(); 115 | Repository re = new Repository(je); 116 | ``` 117 | 首先就是创建一个Repository(指定Jedis对象)。很简单。 118 | ```java 119 | TestEntity ss = new TestEntity(); 120 | ss.setName("test" + i); 121 | ss.setAge(i); 122 | List l = new ArrayList(); 123 | l.add("fd"); 124 | l.add("akjl;sfd"); 125 | ss.setList(l); 126 | Map map = new HashMap(); 127 | ss.setMap(map); 128 | map.put("a", 1); 129 | map.put("b", 2); 130 | re.writeAndFlush(ss); 131 | ``` 132 | 以上就是初始化一个TestEntity并设置好各属性,最后调用Repository的writeAndFlush 133 | 方法写入redis。如果写入成功,返回的long值就是ss对象的id(>0)并且ss的id属性会被正确 134 | 赋值;如果失败则返回值<=0 135 | 136 | ss = re.get(TestEntity.class, ss.getId()); 137 | 调用Repository的get方法(指定类型和id)则加载对应此id的TestEntity对象。 138 | 139 | 此外Repository还有其他几个重要方法:long write(Object entity) 写入对象但不立即flush 140 | 一般用于批量保存对象,多个对象写入后,再调用一次flush,会大大提高写入效率;boolean write(Class claz, long id, String p, Object v) 141 | 此方法用来更新对象的某个属性但不会立即flush; T get(Class claz, long id, String p)方法 142 | 可用于获取对象的单个属性;void delete(Class claz, long id) 方法用于删除对象; 143 | List rank(Class claz, String p, long start, long end)方法用于获取排名列表。 144 | 145 | 另外值得一提的还有缓存机制(Entity注释的Cache属性为true则开启缓存),当写入和获取一个对象后, 146 | 此对象会立即进入缓存,以便于下次获取时,有更好的效率。 147 | 坐标: 148 | 149 | ```xml 150 | 151 | org.beykery 152 | rojo 153 | 1.1.0 154 | 155 | ``` 156 | 157 | ## 结语 158 | 159 | enjoy it. 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | org.beykery 5 | rojo 6 | 1.1.0 7 | jar 8 | 9 | UTF-8 10 | 1.7 11 | 1.7 12 | 13 | rojo 14 | 15 | rojo is a orm lib for redis . this release is a enhancement fork from https://github.com/giulio/rojo . 16 | 17 | 18 | http://git.oschina.net/beykery/rojo 19 | 20 | 21 | 22 | The Apache Software License, Version 2.0 23 | http://www.apache.org/licenses/LICENSE-2.0.txt 24 | 25 | 26 | 27 | 28 | beykery 29 | beykery@sina.com 30 | 31 | 32 | 33 | scm:git:git@git.oschina.net:beykery/rojo.git 34 | scm:git:git@git.oschina.net:beykery/rojo.git 35 | git@git.oschina.net:beykery/rojo.git 36 | 37 | 38 | 39 | oss 40 | 41 | 42 | 43 | org.sonatype.plugins 44 | nexus-staging-maven-plugin 45 | 1.6.6 46 | true 47 | 48 | oss 49 | https://oss.sonatype.org/ 50 | true 51 | 52 | 53 | 54 | org.apache.maven.plugins 55 | maven-source-plugin 56 | 2.4 57 | 58 | 59 | attach-sources 60 | 61 | jar-no-fork 62 | 63 | 64 | 65 | 66 | 67 | org.apache.maven.plugins 68 | maven-javadoc-plugin 69 | 2.10.3 70 | 71 | 72 | attach-javadocs 73 | 74 | jar 75 | 76 | 77 | false 78 | 79 | 80 | 81 | 82 | 83 | org.apache.maven.plugins 84 | maven-gpg-plugin 85 | 1.6 86 | 87 | 88 | sign-artifacts 89 | verify 90 | 91 | sign 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | oss 101 | https://oss.sonatype.org/content/repositories/snapshots 102 | 103 | 104 | oss 105 | https://oss.sonatype.org/service/local/staging/deploy/maven2 106 | 107 | 108 | 109 | 110 | 111 | 112 | redis.clients 113 | jedis 114 | 2.9.0 115 | jar 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/annotations/Entity.java: -------------------------------------------------------------------------------- 1 | package org.rojo.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.TYPE) 10 | public @interface Entity 11 | { 12 | 13 | boolean cache() default false; 14 | 15 | boolean idCache() default true; 16 | 17 | String table() default ""; 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/annotations/Id.java: -------------------------------------------------------------------------------- 1 | package org.rojo.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface Id 11 | { 12 | 13 | /** 14 | * 15 | * @return 16 | */ 17 | String generator() default "defaultGenerator"; 18 | 19 | /** 20 | * 21 | * @return 22 | */ 23 | boolean auto() default false; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/annotations/Index.java: -------------------------------------------------------------------------------- 1 | /** 2 | * indxing the field 3 | */ 4 | package org.rojo.annotations; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Target(ElementType.FIELD) 13 | public @interface Index 14 | { 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/annotations/Value.java: -------------------------------------------------------------------------------- 1 | package org.rojo.annotations; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.FIELD) 10 | public @interface Value 11 | { 12 | 13 | String column() default ""; 14 | 15 | boolean unique() default false; 16 | 17 | boolean sort() default false; 18 | 19 | boolean bigFirst() default false; 20 | 21 | long size() default 0; 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/exceptions/InvalidTypeException.java: -------------------------------------------------------------------------------- 1 | package org.rojo.exceptions; 2 | 3 | @SuppressWarnings("serial") 4 | public class InvalidTypeException extends RojoException { 5 | 6 | public InvalidTypeException(String message) { 7 | super(message); 8 | } 9 | 10 | public InvalidTypeException(Throwable e) { 11 | super(e); 12 | } 13 | 14 | public InvalidTypeException() { 15 | super(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/exceptions/RepositoryError.java: -------------------------------------------------------------------------------- 1 | package org.rojo.exceptions; 2 | 3 | 4 | @SuppressWarnings("serial") 5 | public class RepositoryError extends RojoException { 6 | 7 | public RepositoryError(Exception e) { 8 | super(e); 9 | } 10 | 11 | public RepositoryError(String msg) { 12 | super(msg); 13 | } 14 | 15 | public RepositoryError(String msg, Exception e) { 16 | super(msg, e); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/exceptions/RojoException.java: -------------------------------------------------------------------------------- 1 | package org.rojo.exceptions; 2 | 3 | @SuppressWarnings("serial") 4 | public class RojoException extends RuntimeException 5 | { 6 | 7 | public RojoException() 8 | { 9 | super(); 10 | } 11 | 12 | public RojoException(Throwable e) 13 | { 14 | super(e); 15 | } 16 | 17 | public RojoException(String msg) 18 | { 19 | super(msg); 20 | } 21 | 22 | public RojoException(String msg, Exception e) 23 | { 24 | super(msg, e); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/repository/DefaultGenerator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * default generator 3 | */ 4 | package org.rojo.repository; 5 | 6 | import redis.clients.jedis.Jedis; 7 | 8 | /** 9 | * 10 | * @author beykery 11 | */ 12 | public class DefaultGenerator extends IdGenerator 13 | { 14 | 15 | public DefaultGenerator() 16 | { 17 | } 18 | 19 | @Override 20 | public String id(Class claz, String table, Jedis je) 21 | { 22 | return je.incr(table + ":::id").toString(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/repository/EntityRepresentation.java: -------------------------------------------------------------------------------- 1 | package org.rojo.repository; 2 | 3 | import org.rojo.annotations.Value; 4 | import org.rojo.exceptions.InvalidTypeException; 5 | import java.lang.reflect.Field; 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.Set; 12 | import org.rojo.annotations.Entity; 13 | import org.rojo.annotations.Id; 14 | import org.rojo.annotations.Index; 15 | import org.rojo.exceptions.RojoException; 16 | 17 | /** 18 | * Encapsulate an entity. Provide accessors for @Id and @Value. 19 | * 20 | * Entities representation are cached since instantiation requires access to 21 | * Java reflection methods (time consuming) 22 | */ 23 | public class EntityRepresentation 24 | { 25 | 26 | private static final Map, EntityRepresentation> knownEntities; 27 | private static final Map tableEntities; 28 | 29 | private boolean cacheable; 30 | private boolean idCache; 31 | private String table; 32 | private Field id; 33 | private final Field[] fields; 34 | private final String[] columns; 35 | private Field unique; 36 | private final Map fieldMap = new HashMap(); 37 | private final Map columnMap = new HashMap(); 38 | private final List indexes = new ArrayList(); 39 | private IdGenerator idGenerator; 40 | private boolean autoId; 41 | 42 | static 43 | { 44 | knownEntities = new HashMap, EntityRepresentation>(); 45 | tableEntities = new HashMap(); 46 | } 47 | 48 | public static EntityRepresentation forClass(Class entityClass) 49 | { 50 | if (knownEntities.containsKey(entityClass)) 51 | { 52 | return knownEntities.get(entityClass); 53 | } 54 | EntityRepresentation entityRepresentation = new EntityRepresentation(entityClass); 55 | knownEntities.put(entityClass, entityRepresentation); 56 | EntityRepresentation old = tableEntities.put(entityRepresentation.table, entityRepresentation); 57 | if (old != null) 58 | { 59 | throw new RojoException("duplicate table:" + entityRepresentation.table); 60 | } 61 | return entityRepresentation; 62 | } 63 | 64 | /** 65 | * 66 | * 67 | * @param entityClass 68 | */ 69 | private EntityRepresentation(Class entityClass) 70 | { 71 | verifyEntityAnnotation(entityClass); 72 | cacheable = entityClass.getAnnotation(Entity.class).cache(); 73 | idCache = entityClass.getAnnotation(Entity.class).idCache(); 74 | table = entityClass.getAnnotation(Entity.class).table(); 75 | if (table.isEmpty()) 76 | { 77 | table = entityClass.getSimpleName(); 78 | } 79 | allField(entityClass); 80 | if (id == null) 81 | { 82 | error(entityClass, "missing @Id field!"); 83 | } 84 | fields = new Field[fieldMap.size()]; 85 | columns = new String[fieldMap.size()]; 86 | fieldMap.values().toArray(fields); 87 | for (int i = 0; i < columns.length; i++) 88 | { 89 | if (fields[i].isAnnotationPresent(Value.class)) 90 | { 91 | columns[i] = fields[i].getAnnotation(Value.class).column(); 92 | } 93 | if (columns[i] == null || columns[i].isEmpty()) 94 | { 95 | columns[i] = fields[i].getName(); 96 | } 97 | columnMap.put(fields[i].getName(), columns[i]); 98 | } 99 | } 100 | 101 | /** 102 | * fields include super classes 103 | * 104 | * @param entityClass 105 | * @return 106 | */ 107 | private void allField(Class claz) 108 | { 109 | Field[] fs = claz.getDeclaredFields(); 110 | for (Field f : fs) 111 | { 112 | if (f.isAnnotationPresent(Id.class)) 113 | { 114 | if (id == null) 115 | { 116 | if (!(f.getType() == String.class)) 117 | { 118 | error(claz, "invalid @Id field type! accepted types are {String}"); 119 | } 120 | f.setAccessible(true); 121 | id = f; 122 | fieldMap.put(f.getName(), f); 123 | Id annotation = id.getAnnotation(Id.class); 124 | idGenerator = IdGenerator.getGenerator(annotation.generator()); 125 | autoId = annotation.auto(); 126 | if (idGenerator == null) 127 | { 128 | error(claz, "idGenerator is null!"); 129 | } 130 | } else 131 | { 132 | error(claz, "duplicate @Id field type !"); 133 | } 134 | } else if (f.isAnnotationPresent(Value.class)) 135 | { 136 | if (Collection.class.isAssignableFrom(f.getType())) 137 | { 138 | if (!(f.getType() == Set.class || f.getType() == List.class || f.getType() == Collection.class)) 139 | { 140 | error(claz, "only Collection, Set and List are supported"); 141 | } 142 | } 143 | Value value = f.getAnnotation(Value.class); 144 | f.setAccessible(true); 145 | fieldMap.put(f.getName(), f); 146 | if (f.isAnnotationPresent(Index.class)) 147 | { 148 | this.indexes.add(f); 149 | } 150 | if (value.unique()) 151 | { 152 | if (unique == null && f.getType() != byte[].class)//blob not unique 153 | { 154 | this.unique = f; 155 | } else 156 | { 157 | error(claz, "more than one unique field or blob unique."); 158 | } 159 | } 160 | } 161 | } 162 | Class sclaz = claz.getSuperclass(); 163 | if (sclaz != null) 164 | { 165 | allField(sclaz); 166 | } 167 | } 168 | 169 | private void verifyEntityAnnotation(Class entityClass) 170 | { 171 | if (entityClass.getAnnotation(Entity.class) == null) 172 | { 173 | error(entityClass, "missing @Entity annotation"); 174 | } 175 | } 176 | 177 | private void error(Class entityClass, String msg) 178 | { 179 | throw new InvalidTypeException(entityClass.getCanonicalName() + ": " + msg); 180 | } 181 | 182 | public Field getUnique() 183 | { 184 | return unique; 185 | } 186 | 187 | public IdGenerator getIdGenerator() 188 | { 189 | return idGenerator; 190 | } 191 | 192 | public boolean isAutoId() 193 | { 194 | return autoId; 195 | } 196 | 197 | public String getTable() 198 | { 199 | return table; 200 | } 201 | 202 | public String[] getColumns() 203 | { 204 | return columns; 205 | } 206 | 207 | public String getId(Object entity) 208 | { 209 | return readId(entity, id); 210 | } 211 | 212 | public void setId(Object entity, String idValue) 213 | { 214 | try 215 | { 216 | id.set(entity, idValue); 217 | } catch (Exception e) 218 | { 219 | throw new InvalidTypeException(e); 220 | } 221 | } 222 | 223 | public Field[] getFields() 224 | { 225 | return fields; 226 | } 227 | 228 | public Map getFieldMap() 229 | { 230 | return fieldMap; 231 | } 232 | 233 | public Field getField(String k) 234 | { 235 | return fieldMap.get(k); 236 | } 237 | 238 | String getColumn(String f) 239 | { 240 | return columnMap.get(f); 241 | } 242 | 243 | private String readId(Object entity, Field id) 244 | { 245 | String returnValue = null; 246 | try 247 | { 248 | Object o = id.get(entity); 249 | returnValue = o == null ? null : o.toString(); 250 | } catch (Exception e) 251 | { 252 | throw new RojoException("access error" + e.getMessage()); 253 | } 254 | return returnValue; 255 | } 256 | 257 | public boolean isCacheable() 258 | { 259 | return cacheable; 260 | } 261 | 262 | public void setCacheable(boolean cache) 263 | { 264 | this.cacheable = cache; 265 | } 266 | 267 | public void setIdCache(boolean idCache) 268 | { 269 | this.idCache = idCache; 270 | } 271 | 272 | public boolean isIdCache() 273 | { 274 | return idCache; 275 | } 276 | 277 | Object readProperty(Object entity, Field f) 278 | { 279 | try 280 | { 281 | Object o = f.get(entity); 282 | return o; 283 | } catch (Exception ex) 284 | { 285 | throw new RojoException("access error" + ex.getMessage()); 286 | } 287 | } 288 | 289 | /** 290 | * indexed fields 291 | * 292 | * @return 293 | */ 294 | public List getIndexes() 295 | { 296 | return indexes; 297 | } 298 | 299 | /** 300 | * sample entity 301 | * 302 | * @return 303 | */ 304 | boolean isSampleEntity() 305 | { 306 | return this.unique == null && this.indexes.isEmpty(); 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/repository/IdGenerator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * id generator 3 | */ 4 | package org.rojo.repository; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import redis.clients.jedis.Jedis; 9 | 10 | /** 11 | * 12 | * @author beykery 13 | */ 14 | public abstract class IdGenerator 15 | { 16 | 17 | private static final Map gs = new HashMap(); 18 | 19 | /** 20 | * configue 21 | * 22 | * @param name 23 | */ 24 | public void configue(String name) 25 | { 26 | gs.put(name, this); 27 | } 28 | 29 | /** 30 | * get generator 31 | * 32 | * @param name 33 | * @return 34 | */ 35 | static IdGenerator getGenerator(String name) 36 | { 37 | return gs.get(name); 38 | } 39 | 40 | /** 41 | * generate id 42 | * 43 | * @param claz 44 | * @param table 45 | * @param je 46 | * @return 47 | */ 48 | public abstract String id(Class claz, String table, Jedis je); 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/repository/RedisFacade.java: -------------------------------------------------------------------------------- 1 | package org.rojo.repository; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.lang.reflect.Field; 5 | import java.lang.reflect.Type; 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | import java.util.Date; 9 | import java.util.HashMap; 10 | import java.util.LinkedHashSet; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import org.rojo.annotations.Index; 15 | import org.rojo.annotations.Value; 16 | import org.rojo.exceptions.InvalidTypeException; 17 | import redis.clients.jedis.Jedis; 18 | import redis.clients.jedis.Pipeline; 19 | import redis.clients.jedis.Response; 20 | 21 | public class RedisFacade 22 | { 23 | 24 | private final Jedis je; 25 | private final Pipeline pipe; 26 | private static final String FOR_ALLFIELD_KEY = "001all_propertiesHashMap_key"; 27 | private static final String FOR_ALL_IDS_SET_KEY = "002all_ids_key"; 28 | private static final String FOR_SORTED_KEY = "003sorted_key"; 29 | private static final String FOR_UNIQUE_KEY = "004unique_key"; 30 | private static final String FOR_INDEX_KEY = "005indexing_key"; 31 | private static final String NULL = "#*%$NULL@&%$#*{)}}(";//null 32 | 33 | public RedisFacade(Jedis jrClient) 34 | { 35 | this.je = jrClient; 36 | pipe = je.pipelined(); 37 | } 38 | 39 | /** 40 | * read a proterty 41 | * 42 | * @param 43 | * @param table 44 | * @param id 45 | * @param column 46 | * @param field 47 | * @return 48 | * @throws java.io.UnsupportedEncodingException 49 | */ 50 | @SuppressWarnings("unchecked") 51 | public T readValue(String table, String id, String column, Field field) throws UnsupportedEncodingException 52 | { 53 | byte[] v = je.hget(keyForAllField(table, id).getBytes("UTF-8"), column.getBytes("UTF-8")); 54 | Class t = field.getType(); 55 | return decode(t, v); 56 | } 57 | 58 | /** 59 | * read collection 60 | * 61 | * @param 62 | * @param table 63 | * @param id 64 | * @param column 65 | * @param field 66 | * @param destination 67 | */ 68 | public void readValues(String table, String id, String column, Field field, Collection destination) 69 | { 70 | List values = je.lrange(keyForField(table, id, column), 0, -1); 71 | for (String value : values) 72 | { 73 | destination.add((T) decode((Class) ((java.lang.reflect.ParameterizedType) field.getGenericType()).getActualTypeArguments()[0], value)); 74 | } 75 | } 76 | 77 | void readValues(String table, String id, String column, Field f, Map dest) 78 | { 79 | Map values = je.hgetAll(keyForField(table, id, column)); 80 | for (Map.Entry en : values.entrySet()) 81 | { 82 | Type[] ts = ((java.lang.reflect.ParameterizedType) f.getGenericType()).getActualTypeArguments(); 83 | Class keyClaz = (Class) ts[0]; 84 | Class valueClaz = (Class) ts[1]; 85 | dest.put((K) decode(keyClaz, en.getKey()), (V) decode(valueClaz, en.getValue())); 86 | } 87 | } 88 | 89 | /** 90 | * all fields 91 | * 92 | * @param 93 | * @param entity 94 | * @param id 95 | * @param fields 96 | */ 97 | void processFields(T entity, EntityRepresentation representation, String id) throws Exception 98 | { 99 | Field[] fields = representation.getFields(); 100 | String[] columns = representation.getColumns(); 101 | String table = representation.getTable(); 102 | Response[] rs = new Response[fields.length]; 103 | for (int i = 0; i < rs.length; i++) 104 | { 105 | rs[i] = readFuture(table, id, columns[i], fields[i]); 106 | } 107 | sync(); 108 | for (int i = 0; i < rs.length; i++) 109 | { 110 | if (Collection.class.isAssignableFrom(fields[i].getType())) 111 | { 112 | Collection holder = initCollectionHolder(fields[i]); 113 | List r = (List) rs[i].get(); 114 | for (String value : r) 115 | { 116 | holder.add(decode((Class) ((java.lang.reflect.ParameterizedType) fields[i].getGenericType()).getActualTypeArguments()[0], value)); 117 | } 118 | fields[i].set(entity, holder); 119 | } else if (Map.class.isAssignableFrom(fields[i].getType())) 120 | { 121 | Map map = initMapHolder(fields[i]); 122 | Map values = (Map) rs[i].get(); 123 | Type[] ts = ((java.lang.reflect.ParameterizedType) fields[i].getGenericType()).getActualTypeArguments(); 124 | for (Map.Entry en : values.entrySet()) 125 | { 126 | Class keyClaz = (Class) ts[0]; 127 | Class valueClaz = (Class) ts[1]; 128 | Object k = decode(keyClaz, en.getKey()); 129 | if (k != null) 130 | { 131 | map.put(k, decode(valueClaz, en.getValue())); 132 | } 133 | } 134 | fields[i].set(entity, map); 135 | } else 136 | { 137 | Object temp = decode(fields[i].getType(), rs[i].get()); 138 | if (temp != null) 139 | { 140 | fields[i].set(entity, temp); 141 | } 142 | } 143 | } 144 | } 145 | 146 | private String keyForAll(String table) 147 | { 148 | return table + ":" + FOR_ALL_IDS_SET_KEY; 149 | } 150 | 151 | private String keyForField(String table, String id, String column) 152 | { 153 | return table + ":" + id + ":" + column; 154 | } 155 | 156 | private String keyForAllField(String table, String id) 157 | { 158 | return table + ":" + id + ":" + FOR_ALLFIELD_KEY; 159 | } 160 | 161 | private String keyForSorted(String table, String column) 162 | { 163 | return table + ":" + column + ":" + FOR_SORTED_KEY; 164 | } 165 | 166 | private String keyForUnique(String table, String column, String v) 167 | { 168 | return table + ":" + column + ":" + v + ":" + FOR_UNIQUE_KEY; 169 | } 170 | 171 | private String keyForIndex(String table, String column, String v) 172 | { 173 | return table + ":" + column + ":" + v + ":" + FOR_INDEX_KEY; 174 | } 175 | 176 | /** 177 | * write object 178 | * 179 | * @param entity 180 | * @param id 181 | * @param field 182 | * @return 183 | */ 184 | boolean write(String table, String id, String column, Object v, Field field, boolean withIndexing) 185 | { 186 | try 187 | { 188 | pipe.hset(keyForAllField(table, id), column, v.toString()); 189 | Value annotation = field.getAnnotation(Value.class); 190 | if (annotation.sort()) 191 | { 192 | final String key = keyForSorted(table, column); 193 | pipe.zadd(key, toDouble(v), String.valueOf(id)); 194 | if (annotation.size() > 0) 195 | { 196 | if (annotation.bigFirst()) 197 | { 198 | pipe.zremrangeByRank(key, 0, -annotation.size() - 1); 199 | } else 200 | { 201 | pipe.zremrangeByRank(key, annotation.size(), -1); 202 | } 203 | } 204 | } 205 | if (withIndexing) 206 | { 207 | if (!annotation.unique()) 208 | { 209 | Index index = field.getAnnotation(Index.class); 210 | if (index != null)//indexing 211 | { 212 | pipe.zadd(keyForIndex(table, column, v.toString()), System.currentTimeMillis(), String.valueOf(id)); 213 | } 214 | } 215 | } 216 | return true; 217 | } catch (Exception e) 218 | { 219 | return false; 220 | } 221 | } 222 | 223 | /** 224 | * blob type 225 | * 226 | * @param table 227 | * @param id 228 | * @param column 229 | * @param v 230 | * @param field 231 | * @return 232 | */ 233 | boolean writeBlob(String table, String id, String column, byte[] v, Field field) 234 | { 235 | try 236 | { 237 | if (v != null) 238 | { 239 | pipe.hset(keyForAllField(table, id).getBytes("UTF-8"), column.getBytes("UTF-8"), v); 240 | } else 241 | { 242 | pipe.hdel(keyForAllField(table, id).getBytes("UTF-8"), column.getBytes("UTF-8")); 243 | } 244 | return true; 245 | } catch (Exception e) 246 | { 247 | return false; 248 | } 249 | } 250 | 251 | boolean update(String table, String id, String column, Object v, Field field) 252 | { 253 | try 254 | { 255 | Value annotation = field.getAnnotation(Value.class); 256 | if (annotation.unique() || field.isAnnotationPresent(Index.class)) 257 | { 258 | return false; 259 | } 260 | if (v != null) 261 | { 262 | pipe.hset(keyForAllField(table, id), column, v.toString()); 263 | if (annotation.sort()) 264 | { 265 | final String key = keyForSorted(table, column); 266 | pipe.zadd(key, toDouble(v), String.valueOf(id)); 267 | if (annotation.size() > 0) 268 | { 269 | if (annotation.bigFirst()) 270 | { 271 | pipe.zremrangeByRank(key, 0, -annotation.size() - 1); 272 | } else 273 | { 274 | pipe.zremrangeByRank(key, annotation.size(), -1); 275 | } 276 | } 277 | } 278 | } else 279 | { 280 | pipe.hdel(keyForAllField(table, id), column); 281 | } 282 | return true; 283 | } catch (Exception e) 284 | { 285 | return false; 286 | } 287 | } 288 | 289 | /** 290 | * collection 291 | * 292 | * @param claz 293 | * @param collection 294 | * @param id 295 | * @param field 296 | */ 297 | void writeCollection(String table, Collection collection, String id, String column) 298 | { 299 | String key = keyForField(table, id, column); 300 | pipe.del(key); 301 | if (collection != null) 302 | { 303 | String[] vs = new String[collection.size()]; 304 | int i = 0; 305 | for (Object value : collection) 306 | { 307 | vs[i++] = value == null ? NULL : value.toString(); 308 | } 309 | pipe.rpush(key, vs); 310 | } 311 | } 312 | 313 | /** 314 | * map 315 | * 316 | * @param claz 317 | * @param map 318 | * @param id 319 | * @param field 320 | */ 321 | void writeMap(String table, Map map, String id, String column) 322 | { 323 | String key = keyForField(table, id, column); 324 | pipe.del(key); 325 | if (map != null) 326 | { 327 | Map m = new HashMap(); 328 | for (Map.Entry en : map.entrySet()) 329 | { 330 | Object k = en.getKey(); 331 | if (k != null) 332 | { 333 | m.put(k.toString(), en.getValue() == null ? NULL : en.getValue().toString()); 334 | } 335 | } 336 | pipe.hmset(key, m); 337 | } 338 | } 339 | 340 | /** 341 | * delete 342 | * 343 | * @param claz 344 | * @param id 345 | * @param field 346 | */ 347 | void delete(String table, String id, String column, Field field) 348 | { 349 | if (Collection.class.isAssignableFrom(field.getType()) || Map.class.isAssignableFrom(field.getType())) 350 | { 351 | pipe.del(keyForField(table, id, column)); 352 | } 353 | Value annotation = field.getAnnotation(Value.class); 354 | if (annotation != null && annotation.sort()) 355 | { 356 | pipe.zrem(keyForSorted(table, column), String.valueOf(id)); 357 | } 358 | } 359 | 360 | void delete(String table, String id) 361 | { 362 | pipe.del(keyForAllField(table, id)); 363 | } 364 | 365 | long incr(String s) 366 | { 367 | return je.incr(s); 368 | } 369 | 370 | boolean exists(String table, String id) 371 | { 372 | return je.exists(keyForAllField(table, id)); 373 | } 374 | 375 | boolean uniqueExists(String table, String column, String v) 376 | { 377 | return je.exists(keyForUnique(table, column, v)); 378 | } 379 | 380 | void flush() 381 | { 382 | pipe.sync(); 383 | } 384 | 385 | /** 386 | * decode 387 | * 388 | * @param 389 | * @param t 390 | * @param o 391 | * @return 392 | */ 393 | public static T decode(Class t, Object o) 394 | { 395 | if (t == byte[].class) 396 | { 397 | return isNull(o) ? null : (T) o; 398 | } 399 | String v = (String) o; 400 | if (t == Integer.class) 401 | { 402 | return isNull(v) ? null : (T) (Integer) Integer.parseInt(v); 403 | } 404 | if (t == int.class) 405 | { 406 | return isNull(v) ? (T) new Integer(0) : (T) (Integer) Integer.parseInt(v); 407 | } 408 | if (t == String.class) 409 | { 410 | return isNull(v) ? null : (T) v; 411 | } 412 | if (t == Long.class) 413 | { 414 | return isNull(v) ? null : (T) (Long) Long.parseLong(v); 415 | } 416 | if (t == long.class) 417 | { 418 | return isNull(v) ? (T) new Long(0) : (T) (Long) Long.parseLong(v); 419 | } 420 | if (t == Float.class) 421 | { 422 | return isNull(v) ? null : (T) (Float) Float.parseFloat(v); 423 | } 424 | if (t == float.class) 425 | { 426 | return isNull(v) ? (T) new Float(0) : (T) (Float) Float.parseFloat(v); 427 | } 428 | if (t == Double.class) 429 | { 430 | return isNull(v) ? null : (T) (Double) Double.parseDouble(v); 431 | } 432 | if (t == double.class) 433 | { 434 | return isNull(v) ? (T) new Double(0) : (T) (Double) Double.parseDouble(v); 435 | } 436 | if (t == Short.class) 437 | { 438 | return isNull(v) ? null : (T) (Short) Short.parseShort(v); 439 | } 440 | if (t == short.class) 441 | { 442 | return isNull(v) ? (T) new Short((short) 0) : (T) (Short) Short.parseShort(v); 443 | } 444 | if (t == Byte.class) 445 | { 446 | return isNull(v) ? null : (T) (Byte) Byte.parseByte(v); 447 | } 448 | if (t == byte.class) 449 | { 450 | return isNull(v) ? (T) new Byte((byte) 0) : (T) (Byte) Byte.parseByte(v); 451 | } 452 | if (t == Boolean.class) 453 | { 454 | return isNull(v) ? null : (T) (Boolean) Boolean.parseBoolean(v); 455 | } 456 | if (t == boolean.class) 457 | { 458 | return isNull(v) ? (T) Boolean.FALSE : (T) (Boolean) Boolean.parseBoolean(v); 459 | } 460 | throw new InvalidTypeException("不支持的类型:" + t); 461 | } 462 | 463 | private double toDouble(Object value) 464 | { 465 | if (value instanceof Integer) 466 | { 467 | return (Integer) value; 468 | } 469 | if (value instanceof Long) 470 | { 471 | return (Long) value; 472 | } 473 | if (value instanceof Float) 474 | { 475 | return (Float) value; 476 | } 477 | if (value instanceof Double) 478 | { 479 | return (Double) value; 480 | } 481 | if (value instanceof Short) 482 | { 483 | return (Short) value; 484 | } 485 | if (value instanceof Byte) 486 | { 487 | return (Byte) value; 488 | } 489 | throw new InvalidTypeException("不支持的类型:" + value.getClass()); 490 | } 491 | 492 | private static boolean isNull(Object v) 493 | { 494 | boolean r = (v == null || v.equals(NULL)); 495 | return r; 496 | } 497 | 498 | /** 499 | * rank 500 | * 501 | * @param claz 502 | * @param f 503 | * @param start 504 | * @param end 505 | * @return 506 | */ 507 | Set range(String table, String column, Field f, long start, long end) 508 | { 509 | String key = this.keyForSorted(table, column); 510 | Set s = null; 511 | Value annotation = f.getAnnotation(Value.class); 512 | if (annotation != null && annotation.sort()) 513 | { 514 | if (annotation.bigFirst()) 515 | { 516 | s = je.zrevrange(key, start, end); 517 | } else 518 | { 519 | s = je.zrange(key, start, end); 520 | } 521 | } 522 | return s; 523 | } 524 | 525 | /** 526 | * range by score 527 | * 528 | * @param table 529 | * @param column 530 | * @param f 531 | * @param start 532 | * @param end 533 | * @return 534 | */ 535 | Set scoreRange(String table, String column, Field f, double start, double end) 536 | { 537 | String key = this.keyForSorted(table, column); 538 | Set s = null; 539 | Value annotation = f.getAnnotation(Value.class); 540 | if (annotation != null && annotation.sort()) 541 | { 542 | if (annotation.bigFirst()) 543 | { 544 | s = je.zrevrangeByScore(key, start, end); 545 | } else 546 | { 547 | s = je.zrangeByScore(key, start, end); 548 | } 549 | } 550 | return s; 551 | } 552 | 553 | /** 554 | * index of rank 555 | * 556 | * @param claz 557 | * @param f 558 | * @param id 559 | * @return 560 | */ 561 | long rank(String table, String column, Field f, String id) 562 | { 563 | Long index = null; 564 | Value annotation = f.getAnnotation(Value.class); 565 | if (annotation != null && annotation.sort()) 566 | { 567 | String key = this.keyForSorted(table, column); 568 | if (annotation.bigFirst()) 569 | { 570 | index = je.zrevrank(key, id); 571 | } else 572 | { 573 | index = je.zrank(key, id); 574 | } 575 | } 576 | return index == null ? -1L : index; 577 | } 578 | 579 | boolean writeUnique(Object entity, Field unique, String table, String column, String id) 580 | { 581 | Object v; 582 | try 583 | { 584 | v = unique.get(entity); 585 | if (v == null) 586 | { 587 | return false; 588 | } 589 | } catch (Exception e) 590 | { 591 | return false; 592 | } 593 | long r = je.setnx(keyForUnique(table, column, v.toString()), id); 594 | return r == 1; 595 | } 596 | 597 | /** 598 | * get the future 599 | * 600 | * @param claz 601 | * @param id 602 | * @param field 603 | * @return 604 | */ 605 | Response readFuture(String table, String id, String column, Field field) throws UnsupportedEncodingException 606 | { 607 | if (Collection.class.isAssignableFrom(field.getType())) 608 | { 609 | return pipe.lrange(keyForField(table, id, column), 0, -1); 610 | } else if (Map.class.isAssignableFrom(field.getType())) 611 | { 612 | return pipe.hgetAll(keyForField(table, id, column)); 613 | } else if (field.getType() == byte[].class) 614 | { 615 | return pipe.hget(keyForAllField(table, id).getBytes("UTF-8"), column.getBytes("UTF-8")); 616 | } else 617 | { 618 | return pipe.hget(keyForAllField(table, id), column); 619 | } 620 | } 621 | 622 | /** 623 | * sync the future 624 | * 625 | */ 626 | void sync() 627 | { 628 | this.pipe.sync(); 629 | } 630 | 631 | static Collection initCollectionHolder(Field field) 632 | { 633 | if (field.getType() == List.class || field.getType() == Collection.class) 634 | { 635 | return new ArrayList(); 636 | } else if (field.getType() == Set.class) 637 | { 638 | return new LinkedHashSet(); 639 | } else 640 | { 641 | throw new InvalidTypeException("unsupported Collection subtype"); 642 | } 643 | } 644 | 645 | static Map initMapHolder(Field f) 646 | { 647 | return new HashMap(); 648 | } 649 | 650 | /** 651 | * remove unique field 652 | * 653 | * @param entity 654 | * @param unique 655 | */ 656 | void removeUnique(Object entity, Field unique, String table, String column) 657 | { 658 | try 659 | { 660 | pipe.del(keyForUnique(table, column, unique.get(entity).toString())); 661 | } catch (Exception e) 662 | { 663 | } 664 | } 665 | 666 | /** 667 | * delete index 668 | * 669 | * @param claz 670 | * @param field 671 | * @param v 672 | * @param id 673 | */ 674 | void deleteIndex(String table, String column, String v, String id) 675 | { 676 | pipe.zrem(keyForIndex(table, column, v), String.valueOf(id)); 677 | } 678 | 679 | /** 680 | * index size 681 | * 682 | * @param table 683 | * @param column 684 | * @param v 685 | * @return 686 | */ 687 | long indexSize(String table, String column, Object v) 688 | { 689 | String key = keyForIndex(table, column, v.toString()); 690 | return je.zcard(key); 691 | } 692 | 693 | /** 694 | * indexing ids 695 | * 696 | * @param claz 697 | * @param p 698 | * @param v 699 | * @return 700 | */ 701 | Set index(String table, String column, Object v, long start, long end) 702 | { 703 | String key = keyForIndex(table, column, v.toString()); 704 | Set set = je.zrange(key, start, end); 705 | return set; 706 | } 707 | 708 | String unique(String table, String column, String v) 709 | { 710 | String key = this.keyForUnique(table, column, v); 711 | return je.get(key); 712 | } 713 | 714 | /** 715 | * all ids for t 716 | * 717 | * @param t 718 | * @return 719 | */ 720 | Set all(String table, long start, long end) 721 | { 722 | String key = keyForAll(table); 723 | return je.zrange(key, start, end); 724 | } 725 | 726 | /** 727 | * keys 728 | * 729 | * 730 | * @param table 731 | * @return 732 | */ 733 | Set keys(String table) 734 | { 735 | return je.keys(table + "*"); 736 | } 737 | 738 | /** 739 | * keys 740 | * 741 | * 742 | * @param table 743 | * @return 744 | */ 745 | void delKeys(String table) 746 | { 747 | Set ks = je.keys(table + "*"); 748 | String[] kks = new String[ks.size()]; 749 | ks.toArray(kks); 750 | je.del(kks); 751 | } 752 | 753 | /** 754 | * the collection of entities create between start and end 755 | * 756 | * @param c 757 | * @param start 758 | * @param end 759 | * @return 760 | */ 761 | Set all(String table, Date start, Date end) 762 | { 763 | String key = keyForAll(table); 764 | return je.zrangeByScore(key, start == null ? 0 : start.getTime(), end == null ? System.currentTimeMillis() : end.getTime()); 765 | } 766 | 767 | /** 768 | * all size 769 | * 770 | * @param table 771 | * @return 772 | */ 773 | long allSize(String table) 774 | { 775 | String key = keyForAll(table); 776 | return je.zcard(key); 777 | } 778 | 779 | void addId(String table, String id) 780 | { 781 | String key = keyForAll(table); 782 | pipe.zadd(key, System.currentTimeMillis(), id); 783 | } 784 | 785 | void deleteId(String table, String id) 786 | { 787 | String key = keyForAll(table); 788 | pipe.zrem(key, id); 789 | } 790 | 791 | Date createTime(String table, String id) 792 | { 793 | String key = keyForAll(table); 794 | Double d = je.zscore(key, id); 795 | if (d != null) 796 | { 797 | return new Date((long) (double) d); 798 | } 799 | return null; 800 | } 801 | 802 | Jedis getJedis() 803 | { 804 | return je; 805 | } 806 | /** 807 | * reset status 808 | */ 809 | void reset() 810 | { 811 | je.resetState(); 812 | } 813 | 814 | } 815 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/repository/Rojo.java: -------------------------------------------------------------------------------- 1 | package org.rojo.repository; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.PrintStream; 5 | import java.lang.reflect.Constructor; 6 | import org.rojo.util.CacheoutListerner; 7 | import org.rojo.util.SoftCache; 8 | import java.lang.reflect.Field; 9 | import java.util.Collection; 10 | import java.util.Date; 11 | import java.util.LinkedHashSet; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import java.util.logging.Level; 15 | import java.util.logging.Logger; 16 | import org.rojo.annotations.Index; 17 | import org.rojo.exceptions.RepositoryError; 18 | import org.rojo.util.Cache; 19 | import redis.clients.jedis.Jedis; 20 | 21 | public class Rojo 22 | { 23 | 24 | private static final Logger LOG = Logger.getLogger(Rojo.class.getName()); 25 | private final RedisFacade store; 26 | private static Cache cache; 27 | private static int TIMES_CACHE_CLEAR; 28 | private static volatile long read; 29 | private static volatile long write; 30 | private static volatile boolean cacheable;//cache? 31 | 32 | static 33 | { 34 | /** 35 | * defaultGenerator init* 36 | */ 37 | new DefaultGenerator().configue("defaultGenerator"); 38 | try 39 | { 40 | TIMES_CACHE_CLEAR = Integer.parseInt(System.getProperty("rojo.times.cache.clear", "15000")); 41 | cacheable = Boolean.parseBoolean(System.getProperty("rojo.cacheable", "false")); 42 | String impl = System.getProperty("rojo.cache.impl", "org.rojo.util.LruCache"); 43 | int size = Integer.parseInt(System.getProperty("rojo.cache.size", "150000")); 44 | if (cacheable) 45 | { 46 | Class c = Class.forName(impl); 47 | Constructor con = c.getConstructor(int.class); 48 | cache = (Cache) con.newInstance(size); 49 | if (cache instanceof SoftCache) 50 | { 51 | ((SoftCache) cache).setTIMES_CACHE_CLEAR(TIMES_CACHE_CLEAR); 52 | } 53 | } 54 | } catch (Exception e) 55 | { 56 | LOG.log(Level.WARNING, "some property are missing,default values will be applied"); 57 | } 58 | } 59 | 60 | public Rojo(Jedis je) 61 | { 62 | store = new RedisFacade(je); 63 | } 64 | 65 | public Jedis getJedis() 66 | { 67 | return store.getJedis(); 68 | } 69 | 70 | public static void setCache(Cache cache) 71 | { 72 | Rojo.cache = cache; 73 | } 74 | 75 | public static Cache getCache() 76 | { 77 | return cache; 78 | } 79 | 80 | /** 81 | * all entities of ranking from start to end .(order by create time asc) 82 | * 83 | * @param 84 | * @param c 85 | * @param start index of zset 86 | * @param end index of zset 87 | * @return 88 | */ 89 | public Set all(Class c, long start, long end) 90 | { 91 | try 92 | { 93 | EntityRepresentation representation = EntityRepresentation.forClass(c); 94 | String table = representation.getTable(); 95 | Set s = store.all(table, start, end); 96 | read++; 97 | Set r = new LinkedHashSet<>(s.size()); 98 | for (String item : s) 99 | { 100 | r.add(this.get(c, item)); 101 | } 102 | return r; 103 | } catch (Exception e) 104 | { 105 | store.reset(); 106 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 107 | return null; 108 | } 109 | } 110 | 111 | /** 112 | * all class size 113 | * 114 | * @param c 115 | * @param start 116 | * @param end 117 | * @return 118 | */ 119 | public long allSize(Class c, Date start, Date end) 120 | { 121 | try 122 | { 123 | EntityRepresentation representation = EntityRepresentation.forClass(c); 124 | String table = representation.getTable(); 125 | Set s = store.all(table, start, end); 126 | read++; 127 | return s.size(); 128 | } catch (Exception e) 129 | { 130 | store.reset(); 131 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 132 | return -1; 133 | } 134 | } 135 | 136 | /** 137 | * all size 138 | * 139 | * @param c 140 | * @return 141 | */ 142 | public long allSize(Class c) 143 | { 144 | try 145 | { 146 | EntityRepresentation representation = EntityRepresentation.forClass(c); 147 | String table = representation.getTable(); 148 | long l = store.allSize(table); 149 | read++; 150 | return l; 151 | } catch (Exception e) 152 | { 153 | store.reset(); 154 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 155 | return -1; 156 | } 157 | } 158 | 159 | /** 160 | * the collection of entities create between start and end 161 | * 162 | * @param 163 | * @param c 164 | * @param start time of created 165 | * @param end time of created 166 | * @return 167 | */ 168 | public Set all(Class c, Date start, Date end) 169 | { 170 | try 171 | { 172 | EntityRepresentation representation = EntityRepresentation.forClass(c); 173 | String table = representation.getTable(); 174 | Set s = store.all(table, start, end); 175 | read++; 176 | Set r = new LinkedHashSet<>(s.size()); 177 | for (String item : s) 178 | { 179 | r.add(this.get(c, item)); 180 | } 181 | return r; 182 | } catch (Exception e) 183 | { 184 | store.reset(); 185 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 186 | return null; 187 | } 188 | } 189 | 190 | /** 191 | * the create time of entity 192 | * 193 | * @param c 194 | * @param id 195 | * @return 196 | */ 197 | public Date createTime(Class c, String id) 198 | { 199 | try 200 | { 201 | EntityRepresentation representation = EntityRepresentation.forClass(c); 202 | String table = representation.getTable(); 203 | read++; 204 | return store.createTime(table, id); 205 | } catch (Exception e) 206 | { 207 | store.reset(); 208 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 209 | return null; 210 | } 211 | } 212 | 213 | public void flush() 214 | { 215 | store.flush(); 216 | write++; 217 | } 218 | 219 | public String saveAndFlush(Object entity) 220 | { 221 | String id; 222 | if ((id = save(entity)) != null) 223 | { 224 | store.flush(); 225 | write++; 226 | EntityRepresentation representation = EntityRepresentation.forClass(entity.getClass()); 227 | if (Rojo.cacheable && representation.isCacheable()) 228 | { 229 | cache(entity, id); 230 | } 231 | } 232 | return id; 233 | } 234 | 235 | /** 236 | * write an entity 237 | * 238 | * @param entity 239 | * @return 240 | */ 241 | private String save(Object entity) 242 | { 243 | try 244 | { 245 | EntityRepresentation representation = EntityRepresentation.forClass(entity.getClass()); 246 | String id = representation.getId(entity); 247 | String table = representation.getTable(); 248 | boolean auto = representation.isAutoId(); 249 | boolean idCache = representation.isIdCache(); 250 | if (auto) 251 | { 252 | if (isEmpty(id)) 253 | { 254 | id = representation.getIdGenerator().id(entity.getClass(), table, store.getJedis()); 255 | write++; 256 | } 257 | } 258 | if (isEmpty(id))//null id 259 | { 260 | return null; 261 | } 262 | Field unique = representation.getUnique(); 263 | if (unique != null) 264 | { 265 | boolean result = store.writeUnique(entity, unique, table, representation.getColumn(unique.getName()), id); 266 | if (!result) 267 | { 268 | return null; 269 | } 270 | } 271 | Field[] fs = representation.getFields(); 272 | for (int i = 0; i < fs.length; i++) 273 | { 274 | Field field = fs[i]; 275 | if (field.get(entity) != null) 276 | { 277 | if (Collection.class.isAssignableFrom(field.getType())) 278 | { 279 | Collection collection = (Collection) field.get(entity); 280 | store.writeCollection(table, collection, id, representation.getColumns()[i]); 281 | } else if (Map.class.isAssignableFrom(field.getType())) 282 | { 283 | Map map = (Map) field.get(entity); 284 | store.writeMap(table, map, id, representation.getColumns()[i]); 285 | } else 286 | { 287 | Object v = field.get(entity); 288 | if (v instanceof byte[])//blob 289 | { 290 | store.writeBlob(table, id, representation.getColumns()[i], (byte[]) v, field); 291 | } else 292 | { 293 | store.write(table, id, representation.getColumns()[i], v, field, true); 294 | } 295 | } 296 | } 297 | } 298 | representation.setId(entity, id); 299 | if (idCache) 300 | { 301 | store.addId(table, id); 302 | } 303 | return id; 304 | } catch (Exception e) 305 | { 306 | store.reset(); 307 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 308 | return null; 309 | } 310 | } 311 | 312 | /** 313 | * write properties 314 | * 315 | * @param entity 316 | * @param ps 317 | * @return 318 | */ 319 | public boolean update(Object entity, String... ps) 320 | { 321 | try 322 | { 323 | Class claz = entity.getClass(); 324 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 325 | String table = representation.getTable(); 326 | String id = representation.getId(entity); 327 | if (id != null) 328 | { 329 | for (String p : ps) 330 | { 331 | Field f = representation.getField(p); 332 | String column = representation.getColumn(p); 333 | Object v = representation.readProperty(entity, f); 334 | if (Collection.class.isAssignableFrom(f.getType())) 335 | { 336 | store.writeCollection(table, (Collection) v, id, column); 337 | } else if (Map.class.isAssignableFrom(f.getType())) 338 | { 339 | store.writeMap(table, (Map) v, id, column); 340 | } else if (f.getType() == byte[].class)//blob 341 | { 342 | store.writeBlob(table, id, column, (byte[]) v, f); 343 | } else 344 | { 345 | store.update(table, id, column, v, f); 346 | } 347 | } 348 | return true; 349 | } 350 | } catch (Exception e) 351 | { 352 | store.reset(); 353 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 354 | } 355 | return false; 356 | } 357 | 358 | /** 359 | * update all properties exclude unique 360 | * 361 | * @param entity 362 | * @return 363 | */ 364 | public boolean update(Object entity) 365 | { 366 | try 367 | { 368 | Class claz = entity.getClass(); 369 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 370 | String table = representation.getTable(); 371 | String id = representation.getId(entity); 372 | if (id != null) 373 | { 374 | for (String p : representation.getColumns()) 375 | { 376 | Field f = representation.getField(p); 377 | String column = representation.getColumn(p); 378 | Object v = representation.readProperty(entity, f); 379 | if (Collection.class.isAssignableFrom(f.getType())) 380 | { 381 | store.writeCollection(table, (Collection) v, id, column); 382 | } else if (Map.class.isAssignableFrom(f.getType())) 383 | { 384 | store.writeMap(table, (Map) v, id, column); 385 | } else if (f.getType() == byte[].class)//blob 386 | { 387 | store.writeBlob(table, id, column, (byte[]) v, f); 388 | } else 389 | { 390 | store.update(table, id, column, v, f); 391 | } 392 | } 393 | return true; 394 | } 395 | } catch (Exception e) 396 | { 397 | store.reset(); 398 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 399 | } 400 | return false; 401 | } 402 | 403 | /** 404 | * write and flush 405 | * 406 | * @param entity 407 | * @param ps 408 | * @return 409 | */ 410 | public boolean updateAndFlush(Object entity, String... ps) 411 | { 412 | if (this.update(entity, ps)) 413 | { 414 | flush(); 415 | write++; 416 | return true; 417 | } 418 | return false; 419 | } 420 | 421 | /** 422 | * update all properties of entity 423 | * 424 | * @param entity 425 | * @return 426 | */ 427 | public boolean updateAndFlush(Object entity) 428 | { 429 | if (this.update(entity)) 430 | { 431 | flush(); 432 | write++; 433 | return true; 434 | } 435 | return false; 436 | } 437 | 438 | /** 439 | * read an entity 440 | * 441 | * @param 442 | * @param claz 443 | * @param id 444 | * @return 445 | */ 446 | public T get(Class claz, String id) 447 | { 448 | T entity = getFromCache(claz, id); 449 | if (entity != null) 450 | { 451 | return entity; 452 | } 453 | try 454 | { 455 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 456 | String table = representation.getTable(); 457 | read++; 458 | if (!store.exists(table, id)) 459 | { 460 | return null; 461 | } 462 | entity = claz.newInstance(); 463 | representation.setId(entity, id); 464 | store.processFields(entity, representation, id); 465 | read++; 466 | if (Rojo.cacheable && representation.isCacheable()) 467 | { 468 | cache(entity, id); 469 | } 470 | return entity; 471 | } catch (Exception e) 472 | { 473 | store.reset(); 474 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 475 | throw new RepositoryError(e); 476 | } 477 | } 478 | 479 | /** 480 | * exist 481 | * 482 | * @param claz 483 | * @param id 484 | * @return 485 | */ 486 | public boolean exist(Class claz, String id) 487 | { 488 | try 489 | { 490 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 491 | String table = representation.getTable(); 492 | read++; 493 | return store.exists(table, id); 494 | } catch (Exception e) 495 | { 496 | store.reset(); 497 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 498 | } 499 | return false; 500 | } 501 | 502 | /** 503 | * get a property 504 | * 505 | * @param 506 | * @param claz 507 | * @param id 508 | * @param p 509 | * @return 510 | */ 511 | public T get(Class claz, String id, String p) 512 | { 513 | try 514 | { 515 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 516 | Field f = representation.getField(p); 517 | String table = representation.getTable(); 518 | String column = representation.getColumn(p); 519 | if (Rojo.cacheable && representation.isCacheable()) 520 | { 521 | Object entity = getFromCache(claz, id); 522 | if (entity != null) 523 | { 524 | return (T) f.get(entity); 525 | } 526 | } 527 | read++; 528 | if (Collection.class.isAssignableFrom(f.getType())) 529 | { 530 | Collection holder = RedisFacade.initCollectionHolder(f); 531 | store.readValues(table, id, column, f, holder); 532 | return (T) holder; 533 | } else if (Map.class.isAssignableFrom(f.getType())) 534 | { 535 | Map map = RedisFacade.initMapHolder(f); 536 | store.readValues(table, id, column, f, map); 537 | return (T) map; 538 | } else 539 | { 540 | return store.readValue(table, id, column, f); 541 | } 542 | } catch (Exception e) 543 | { 544 | store.reset(); 545 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 546 | } 547 | return null; 548 | } 549 | 550 | /** 551 | * delete and flush 552 | * 553 | * @param entity 554 | */ 555 | public void deleteAndFlush(Object entity) 556 | { 557 | this.delete(entity); 558 | write++; 559 | flush(); 560 | } 561 | 562 | /** 563 | * delete by id 564 | * 565 | * @param claz 566 | * @param id 567 | */ 568 | public void deleteAndFlush(Class claz, String id) 569 | { 570 | Object temp = this.get(claz, id); 571 | if (temp != null) 572 | { 573 | this.deleteAndFlush(temp); 574 | } 575 | } 576 | 577 | /** 578 | * delete by class 579 | * 580 | * @param claz 581 | */ 582 | public void deleteAndFlush(Class claz) 583 | { 584 | try 585 | { 586 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 587 | String table = representation.getTable(); 588 | store.delKeys(table); 589 | read++; 590 | write++; 591 | if (Rojo.cacheable && representation.isCacheable()) 592 | { 593 | cache.evict(claz); 594 | } 595 | } catch (Exception e) 596 | { 597 | store.reset(); 598 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 599 | } 600 | } 601 | 602 | /** 603 | * delete entity 604 | * 605 | * @param entity 606 | */ 607 | public void delete(Object entity) 608 | { 609 | if (entity == null) 610 | { 611 | return; 612 | } 613 | Class claz = entity.getClass(); 614 | try 615 | { 616 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 617 | String table = representation.getTable(); 618 | String id = representation.getId(entity); 619 | if (id == null) 620 | { 621 | return; 622 | } 623 | Field unique = representation.getUnique(); 624 | if (unique != null) 625 | { 626 | store.removeUnique(entity, unique, table, representation.getColumn(unique.getName())); 627 | } 628 | Field[] fs = representation.getFields(); 629 | String[] columns = representation.getColumns(); 630 | for (int i = 0; i < fs.length; i++) 631 | { 632 | Field field = fs[i]; 633 | store.delete(table, id, columns[i], field);//collection or map 634 | Index index = field.getAnnotation(Index.class); 635 | if (index != null) 636 | { 637 | try 638 | { 639 | Object v = field.get(entity); 640 | if (v != null) 641 | { 642 | store.deleteIndex(table, columns[i], v.toString(), id); 643 | } 644 | } catch (Exception e) 645 | { 646 | LOG.log(Level.WARNING, "rojo error :{0}", e.getMessage()); 647 | } 648 | } 649 | } 650 | store.delete(table, id);//all simple properties 651 | store.deleteId(table, id);//id 652 | if (Rojo.cacheable && representation.isCacheable()) 653 | { 654 | evict(claz, id); 655 | } 656 | } catch (Exception e) 657 | { 658 | store.reset(); 659 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 660 | } 661 | } 662 | 663 | /** 664 | * the range 665 | * 666 | * @param 667 | * @param claz 668 | * @param p 669 | * @param start 670 | * @param end 671 | * @return 672 | */ 673 | public Set range(Class claz, String p, long start, long end) 674 | { 675 | try 676 | { 677 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 678 | Field f = representation.getField(p); 679 | String table = representation.getTable(); 680 | String column = representation.getColumn(f.getName()); 681 | Set s = store.range(table, column, f, start, end); 682 | read++; 683 | Set r = new LinkedHashSet<>(s.size()); 684 | for (String item : s) 685 | { 686 | r.add(this.get(claz, item)); 687 | } 688 | return r; 689 | } catch (Exception e) 690 | { 691 | store.reset(); 692 | LOG.log(Level.SEVERE, "rojo error :{0}", e.getMessage()); 693 | } 694 | return null; 695 | } 696 | 697 | /** 698 | * range by score 699 | * 700 | * @param 701 | * @param claz 702 | * @param p 703 | * @param start 704 | * @param end 705 | * @return 706 | */ 707 | public Set scoreRange(Class claz, String p, double start, double end) 708 | { 709 | try 710 | { 711 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 712 | Field f = representation.getField(p); 713 | String table = representation.getTable(); 714 | String column = representation.getColumn(f.getName()); 715 | Set s = store.scoreRange(table, column, f, start, end); 716 | read++; 717 | Set r = new LinkedHashSet<>(s.size()); 718 | for (String item : s) 719 | { 720 | r.add(this.get(claz, item)); 721 | } 722 | return r; 723 | } catch (Exception e) 724 | { 725 | store.reset(); 726 | LOG.log(Level.SEVERE, "rojo error :{0}", e.getMessage()); 727 | } 728 | return null; 729 | } 730 | 731 | /** 732 | * index of rank 733 | * 734 | * @param claz 735 | * @param id 736 | * @param p 737 | * @return 738 | */ 739 | public long rank(Class claz, String id, String p) 740 | { 741 | try 742 | { 743 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 744 | Field f = representation.getField(p); 745 | String table = representation.getTable(); 746 | String column = representation.getColumn(p); 747 | read++; 748 | return store.rank(table, column, f, id); 749 | } catch (Exception e) 750 | { 751 | store.reset(); 752 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 753 | return -1; 754 | } 755 | } 756 | 757 | /** 758 | * index size 759 | * 760 | * @param claz 761 | * @param p 762 | * @param v 763 | * @return 764 | */ 765 | public long indexSize(Class claz, String p, Object v) 766 | { 767 | try 768 | { 769 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 770 | String table = representation.getTable(); 771 | String column = representation.getColumn(p); 772 | long s = store.indexSize(table, column, v); 773 | read++; 774 | return s; 775 | } catch (Exception e) 776 | { 777 | store.reset(); 778 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 779 | } 780 | return -1; 781 | } 782 | 783 | /** 784 | * the indexings 785 | * 786 | * @param 787 | * @param claz 788 | * @param p 789 | * @param v 790 | * @return 791 | */ 792 | public Set index(Class claz, String p, Object v) 793 | { 794 | return this.index(claz, p, v, 0, -1); 795 | } 796 | 797 | /** 798 | * indexing with range(index sorted by createTime asc) 799 | * 800 | * @param 801 | * @param claz 802 | * @param p 803 | * @param v 804 | * @param start 805 | * @param end 806 | * @return 807 | */ 808 | public Set index(Class claz, String p, Object v, long start, long end) 809 | { 810 | try 811 | { 812 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 813 | String table = representation.getTable(); 814 | String column = representation.getColumn(p); 815 | Set s = store.index(table, column, v, start, end); 816 | read++; 817 | Set r = new LinkedHashSet<>(s.size()); 818 | for (String item : s) 819 | { 820 | r.add(this.get(claz, item)); 821 | } 822 | return r; 823 | } catch (Exception e) 824 | { 825 | store.reset(); 826 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 827 | } 828 | return null; 829 | } 830 | 831 | /** 832 | * unique object 833 | * 834 | * @param 835 | * @param claz 836 | * @param v 837 | * @return 838 | */ 839 | public T unique(Class claz, Object v) 840 | { 841 | try 842 | { 843 | EntityRepresentation representation = EntityRepresentation.forClass(claz); 844 | Field unique = representation.getUnique(); 845 | String table = representation.getTable(); 846 | String column = representation.getColumn(unique.getName()); 847 | String id = store.unique(table, column, v.toString()); 848 | read++; 849 | return id == null ? null : this.get(claz, id); 850 | } catch (Exception e) 851 | { 852 | store.reset(); 853 | LOG.log(Level.SEVERE, "rojo error :{0}", stackTrace(e)); 854 | } 855 | return null; 856 | } 857 | 858 | /** 859 | * cache 860 | * 861 | * @param entity 862 | */ 863 | private void cache(Object entity, String id) 864 | { 865 | if (cache != null) 866 | { 867 | cache.cache(entity, id); 868 | } 869 | } 870 | 871 | /** 872 | * 873 | * @param 874 | * @param claz 875 | * @param id 876 | * @return 877 | */ 878 | private T getFromCache(Class claz, String id) 879 | { 880 | return cache == null ? null : cache.get(claz, id); 881 | } 882 | 883 | /** 884 | * evict an object 885 | * 886 | * @param claz 887 | * @param id 888 | */ 889 | public static void evict(Class claz, String id) 890 | { 891 | if (cache != null) 892 | { 893 | cache.evict(claz, id); 894 | } 895 | } 896 | 897 | /** 898 | * evict entites of claz 899 | * 900 | * @param claz 901 | */ 902 | public static void clearCache(Class claz) 903 | { 904 | if (cache != null) 905 | { 906 | if (cache instanceof SoftCache) 907 | { 908 | ((SoftCache) cache).clear(claz); 909 | } 910 | } 911 | } 912 | 913 | /** 914 | * clear cache 915 | */ 916 | public static void clearCache() 917 | { 918 | if (cache != null) 919 | { 920 | cache.clear(); 921 | } 922 | } 923 | 924 | public static void setCacheoutListerner(CacheoutListerner cacheoutListerner) 925 | { 926 | if (cache != null) 927 | { 928 | cache.setCacheoutListerner(cacheoutListerner); 929 | } 930 | } 931 | 932 | public static CacheoutListerner getCacheoutListerner() 933 | { 934 | return cache == null ? null : cache.getCacheoutListerner(); 935 | } 936 | 937 | private static boolean isEmpty(String id) 938 | { 939 | return id == null || id.isEmpty(); 940 | } 941 | 942 | public static long read() 943 | { 944 | return read; 945 | } 946 | 947 | public static long write() 948 | { 949 | return write; 950 | } 951 | 952 | public static boolean isCacheable() 953 | { 954 | return cacheable; 955 | } 956 | 957 | public static void setCacheable(boolean cacheable) 958 | { 959 | Rojo.cacheable = cacheable; 960 | if (!cacheable) 961 | { 962 | Rojo.clearCache(); 963 | } else if (cache == null) 964 | { 965 | try 966 | { 967 | TIMES_CACHE_CLEAR = Integer.parseInt(System.getProperty("rojo.times.cache.clear", "15000")); 968 | String impl = System.getProperty("rojo.cache.impl", "org.rojo.util.LruCache"); 969 | int size = Integer.parseInt(System.getProperty("rojo.cache.size", "150000")); 970 | Class c = Class.forName(impl); 971 | Constructor con = c.getConstructor(Integer.class); 972 | cache = (Cache) con.newInstance(size); 973 | if (cache instanceof SoftCache) 974 | { 975 | ((SoftCache) cache).setTIMES_CACHE_CLEAR(TIMES_CACHE_CLEAR); 976 | } 977 | } catch (Exception e) 978 | { 979 | LOG.log(Level.SEVERE, "some property are missing,default values will be applied"); 980 | } 981 | } 982 | } 983 | 984 | /** 985 | * exception trace 986 | * 987 | * @param e 988 | * @return 989 | */ 990 | public static String stackTrace(Exception e) 991 | { 992 | try 993 | { 994 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 995 | PrintStream ps = new PrintStream(baos, true, "utf-8"); 996 | e.printStackTrace(ps); 997 | return baos.toString(); 998 | } catch (Exception ex) 999 | { 1000 | LOG.log(Level.SEVERE, "stackTrace error " + ex.getMessage()); 1001 | return null; 1002 | } 1003 | } 1004 | } 1005 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/test/BaseEntity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 基础entity(用于被继承) 3 | */ 4 | package org.rojo.test; 5 | 6 | import org.rojo.annotations.Entity; 7 | import org.rojo.annotations.Id; 8 | import org.rojo.annotations.Value; 9 | 10 | /** 11 | * 12 | * @author beykery 13 | */ 14 | @Entity(table = "be") 15 | public class BaseEntity 16 | { 17 | 18 | @Id(auto = true, generator = "common:id") 19 | private String id; 20 | @Value(column = "n", unique = true) 21 | private String name; 22 | @Value 23 | private String tests; 24 | 25 | public String getId() 26 | { 27 | return id; 28 | } 29 | 30 | public String getName() 31 | { 32 | return name; 33 | } 34 | 35 | public void setName(String name) 36 | { 37 | this.name = name; 38 | } 39 | 40 | public String getTests() 41 | { 42 | return tests; 43 | } 44 | 45 | public void setTests(String tests) 46 | { 47 | this.tests = tests; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/test/Test.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 测试 3 | */ 4 | package org.rojo.test; 5 | 6 | import java.io.UnsupportedEncodingException; 7 | import java.util.ArrayList; 8 | import java.util.Date; 9 | import java.util.HashMap; 10 | import java.util.HashSet; 11 | import java.util.List; 12 | import java.util.Map; 13 | import java.util.Set; 14 | import org.rojo.repository.Rojo; 15 | import redis.clients.jedis.Jedis; 16 | 17 | /** 18 | * 19 | * @author beykery 20 | */ 21 | public class Test 22 | { 23 | 24 | public static void main(String[] args) throws InterruptedException, UnsupportedEncodingException 25 | { 26 | long os = System.currentTimeMillis(); 27 | int times = 100; 28 | Jedis je = new Jedis("localhost", 6379); 29 | je.auth("ssm123"); 30 | Rojo re = new Rojo(je); 31 | new TestGenerator(je, "common:id").configue("common:id");//configue id generator 32 | for (int i = 1; i <= times; i++) 33 | { 34 | TestEntity ss = new TestEntity(); 35 | ss.setName("test名字" + i); 36 | ss.setAge(i); 37 | ss.setTests(null); 38 | List l = new ArrayList(); 39 | l.add("fd"); 40 | l.add("akjl;sfd"); 41 | l.add(null); 42 | ss.setList(l); 43 | Map map = new HashMap(); 44 | ss.setMap(map); 45 | map.put("a", 1); 46 | map.put("b", 2); 47 | map.put("c", null); 48 | Set set = new HashSet(); 49 | ss.setSet(set); 50 | set.add(1.2f); 51 | set.add(3.14159f); 52 | ss.setSex(i % 2 == 1 ? 1 : 0); 53 | ss.setContent("哈哈".getBytes("UTF-8")); 54 | re.saveAndFlush(ss); 55 | ss.setT1(41); 56 | ss.setT2(20); 57 | ss.setContent("哈哈111".getBytes("UTF-8")); 58 | re.updateAndFlush(ss, "t1", "t2", "content"); 59 | ss = re.get(TestEntity.class, ss.getId()); 60 | System.out.println(ss); 61 | Thread.sleep(10); 62 | } 63 | 64 | Rojo.clearCache(); 65 | for (int i = 1; i <= times; i++) 66 | { 67 | TestEntity te = re.get(TestEntity.class, i + "fkey"); 68 | System.out.println(te); 69 | } 70 | 71 | for (int i = 1; i <= times; i++) 72 | { 73 | TestEntity te = re.get(TestEntity.class, (i) + "fkey"); 74 | System.out.println(te); 75 | } 76 | 77 | Rojo.clearCache(); 78 | Set l = re.index(TestEntity.class, "sex", 0); 79 | System.out.println(l); 80 | 81 | TestEntity te = re.unique(TestEntity.class, "test名字6"); 82 | System.out.println("unique:" + te); 83 | 84 | Set r = re.range(TestEntity.class, "age", -5, -1); 85 | System.out.println(r); 86 | 87 | long rank = re.rank(TestEntity.class, "10fkey", "age"); 88 | System.out.println(rank); 89 | Set tes = re.all(TestEntity.class, 0, -1); 90 | System.out.println(tes); 91 | re.deleteAndFlush(te); 92 | tes = re.all(TestEntity.class, 0, -1); 93 | System.out.println(tes); 94 | Date ctime = re.createTime(TestEntity.class, "8fkey"); 95 | System.out.println(ctime); 96 | tes = re.all(TestEntity.class, new Date(os), ctime); 97 | System.out.println(tes); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/test/TestEntity.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 用来测试 3 | */ 4 | package org.rojo.test; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | import java.util.Set; 9 | import org.rojo.annotations.Entity; 10 | import org.rojo.annotations.Index; 11 | import org.rojo.annotations.Value; 12 | 13 | @Entity(table = "te", cache = true) 14 | public class TestEntity extends BaseEntity 15 | { 16 | 17 | @Value(column = "l") 18 | private List list; 19 | @Value(column = "s") 20 | private Set set; 21 | @Value(column = "m") 22 | private Map map; 23 | @Value(column = "a", sort = true, bigFirst = false, size = 5) 24 | private int age; 25 | @Value(column = "sex") 26 | @Index 27 | private int sex; 28 | @Value(column = "t1") 29 | private int t1; 30 | @Value(column = "t2") 31 | private long t2; 32 | @Value 33 | private byte[] content; 34 | 35 | public void setT1(int t1) 36 | { 37 | this.t1 = t1; 38 | } 39 | 40 | public int getT1() 41 | { 42 | return t1; 43 | } 44 | 45 | public void setT2(long t2) 46 | { 47 | this.t2 = t2; 48 | } 49 | 50 | public long getT2() 51 | { 52 | return t2; 53 | } 54 | 55 | public int getSex() 56 | { 57 | return sex; 58 | } 59 | 60 | public void setSex(int sex) 61 | { 62 | this.sex = sex; 63 | } 64 | 65 | public void setAge(int age) 66 | { 67 | this.age = age; 68 | } 69 | 70 | public int getAge() 71 | { 72 | return age; 73 | } 74 | 75 | public void setMap(Map map) 76 | { 77 | this.map = map; 78 | } 79 | 80 | public Map getMap() 81 | { 82 | return map; 83 | } 84 | 85 | public void setSet(Set set) 86 | { 87 | this.set = set; 88 | } 89 | 90 | public Set getSet() 91 | { 92 | return set; 93 | } 94 | 95 | public List getList() 96 | { 97 | return list; 98 | } 99 | 100 | public void setList(List strings) 101 | { 102 | this.list = strings; 103 | } 104 | 105 | public byte[] getContent() 106 | { 107 | return content; 108 | } 109 | 110 | public void setContent(byte[] content) 111 | { 112 | this.content = content; 113 | } 114 | 115 | @Override 116 | public String toString() 117 | { 118 | return this.getId(); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/test/TestGenerator.java: -------------------------------------------------------------------------------- 1 | /** 2 | * test id generator 3 | */ 4 | package org.rojo.test; 5 | 6 | import org.rojo.repository.IdGenerator; 7 | import redis.clients.jedis.Jedis; 8 | 9 | /** 10 | * 11 | * @author beykery 12 | */ 13 | public class TestGenerator extends IdGenerator 14 | { 15 | 16 | private final Jedis je; 17 | private final String name; 18 | 19 | public TestGenerator(Jedis je, String name) 20 | { 21 | this.je = je; 22 | this.name = name; 23 | } 24 | 25 | @Override 26 | public String id(Class claz, String table, Jedis je) 27 | { 28 | return je.incr(table+":::id") + "fkey"; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/test/TestSoft.java: -------------------------------------------------------------------------------- 1 | /** 2 | * test 3 | */ 4 | package org.rojo.test; 5 | 6 | import java.lang.ref.ReferenceQueue; 7 | import java.lang.ref.SoftReference; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * 13 | * @author beykery 14 | */ 15 | public class TestSoft 16 | { 17 | 18 | public static void main(String[] args) 19 | { 20 | Map> c = new HashMap>(); 21 | ReferenceQueue> rq = new ReferenceQueue>(); 22 | for (int i = 0;; i++) 23 | { 24 | Enty e = new Enty(); 25 | String id = String.valueOf(i); 26 | c.put(id, new SoftObjectReference(e, rq, id)); 27 | SoftObjectReference sr; 28 | int sum = 0; 29 | while ((sr = (SoftObjectReference) rq.poll()) != null) 30 | { 31 | sum++; 32 | c.remove(sr.id); 33 | System.out.println(sr.id+" out"); 34 | } 35 | if (sum > 0) 36 | { 37 | System.out.println("out!" + i + ":" + sum + ":" + c.size()); 38 | } 39 | } 40 | } 41 | 42 | private static class Enty 43 | { 44 | 45 | public Enty() 46 | { 47 | content = new byte[100 * 1024]; 48 | } 49 | 50 | private byte[] content; 51 | } 52 | 53 | private static class SoftObjectReference extends SoftReference 54 | { 55 | 56 | public final String id; 57 | public final Class claz; 58 | 59 | public SoftObjectReference(Object r, ReferenceQueue q, String id) 60 | { 61 | super(r, q); 62 | this.id = id; 63 | this.claz = r.getClass(); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/test/Work.java: -------------------------------------------------------------------------------- 1 | /* 2 | * To change this license header, choose License Headers in Project Properties. 3 | * To change this template file, choose Tools | Templates 4 | * and open the template in the editor. 5 | */ 6 | package org.rojo.test; 7 | 8 | import redis.clients.jedis.Jedis; 9 | 10 | /** 11 | * 12 | * @author beykery 13 | */ 14 | public class Work 15 | { 16 | 17 | public static void main(String[] args) 18 | { 19 | String[] items = new String[] 20 | { 21 | "1000006", 22 | "1000016", 23 | "1003142", 24 | "1003144", 25 | "1003145", 26 | "1003219", 27 | "1008432", 28 | "1008445", 29 | "1009089", 30 | "1009090", 31 | "1009094", 32 | "1009097", 33 | "1009108", 34 | "1009110", 35 | "1009116", 36 | "1009119", 37 | "1009126", 38 | "1009130", 39 | "1009132", 40 | "1009134", 41 | "1009137", 42 | "1009633", 43 | "1009634", 44 | "1009878", 45 | "1009880", 46 | "1009883", 47 | "1009885", 48 | "1010002", 49 | "1010012", 50 | "1010013", 51 | "1010035", 52 | "1012764", 53 | "1015455", 54 | "1015456", 55 | "1015460", 56 | "1015465", 57 | "1015466", 58 | "1015900", 59 | "1015940", 60 | "1015941", 61 | "1015942", 62 | "1015943", 63 | "1015950", 64 | "1015959", 65 | "1015963", 66 | "1015964", 67 | "1015968", 68 | "1015974", 69 | "1015978", 70 | "1015993", 71 | "1016013", 72 | "1016014", 73 | "1016015", 74 | "1016019", 75 | "1016021", 76 | "1016022", 77 | "1022570", 78 | "1022571", 79 | "1023649", 80 | "1025102", 81 | "1025103", 82 | "1025104", 83 | "1034196", 84 | "1034466", 85 | "1034468", 86 | "1034684", 87 | "1034685", 88 | "1034686", 89 | "1034687", 90 | "1034688", 91 | "1034689", 92 | "1034690", 93 | "1034691", 94 | "1034692", 95 | "1034693", 96 | "1034694", 97 | "1034695", 98 | "1034696", 99 | "1034697", 100 | "1034698", 101 | "1034699", 102 | "1034704", 103 | "1034705", 104 | "1034720", 105 | "1034721", 106 | "1034722", 107 | "1034723", 108 | "1034724", 109 | "1034725", 110 | "1034726", 111 | "1034727", 112 | "1034728", 113 | "1034729", 114 | "1034730", 115 | "1034731", 116 | "1034732", 117 | "1034733", 118 | "1034734", 119 | "1034735", 120 | "1034736", 121 | "1034737", 122 | "1034738" 123 | }; 124 | Jedis je = new Jedis("119.29.33.138", 7379); 125 | je.auth("a77af61dd9f8e6c974b9f092032998d0"); 126 | je.ping(); 127 | je.select(1); 128 | for (String item : items) 129 | { 130 | String s = je.hget("U_900001f3_H_" + item, "awLvl"); 131 | System.out.println(item + " " + s); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/util/Cache.java: -------------------------------------------------------------------------------- 1 | /** 2 | * cache 3 | */ 4 | package org.rojo.util; 5 | 6 | /** 7 | * 8 | * @author beykery 9 | */ 10 | public interface Cache 11 | { 12 | 13 | /** 14 | * cache 15 | * 16 | * @param entity 17 | * @param id 18 | */ 19 | public void cache(Object entity, String id); 20 | 21 | /** 22 | * get entity 23 | * 24 | * @param 25 | * @param claz 26 | * @param id 27 | * @return 28 | */ 29 | public T get(Class claz, String id); 30 | 31 | /** 32 | * evict an object 33 | * 34 | * @param claz 35 | * @param id 36 | */ 37 | public void evict(Class claz, String id); 38 | 39 | /** 40 | * evict objects 41 | * 42 | * @param claz 43 | */ 44 | public void evict(Class claz); 45 | 46 | /** 47 | * clean the cache 48 | * 49 | */ 50 | public void clear(); 51 | 52 | /** 53 | * listerner 54 | * 55 | * @param cacheoutListerner 56 | */ 57 | public void setCacheoutListerner(CacheoutListerner cacheoutListerner); 58 | 59 | /** 60 | * the listerner 61 | * 62 | * @return 63 | */ 64 | public CacheoutListerner getCacheoutListerner(); 65 | 66 | /** 67 | * the stats 68 | * 69 | * @return 70 | */ 71 | public Stats stats(); 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/util/CacheoutListerner.java: -------------------------------------------------------------------------------- 1 | /** 2 | * cache out listerner 3 | */ 4 | package org.rojo.util; 5 | 6 | /** 7 | * 8 | * @author beykery 9 | */ 10 | public abstract class CacheoutListerner 11 | { 12 | 13 | /** 14 | * 15 | * @param claz 16 | * @param id 17 | */ 18 | public abstract void onCacheout(Class claz, String id); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/util/LruCache.java: -------------------------------------------------------------------------------- 1 | package org.rojo.util; 2 | 3 | import java.util.Arrays; 4 | import java.util.Collections; 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | import java.util.TreeSet; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.locks.ReentrantLock; 10 | import java.lang.ref.WeakReference; 11 | import java.util.HashMap; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | 15 | /** 16 | * lrucache 17 | * 18 | * @author solr 19 | */ 20 | public class LruCache implements Cache 21 | { 22 | 23 | private final ConcurrentHashMap map; 24 | private final Map> keys; 25 | private final int upperWaterMark, lowerWaterMark; 26 | private final ReentrantLock markAndSweepLock = new ReentrantLock(true); 27 | private boolean isCleaning = false; // not volatile... piggybacked on other volatile vars 28 | private final boolean newThreadForCleanup; 29 | private final Stats stats = new Stats(); 30 | private final int acceptableWaterMark; 31 | private long oldestEntry = 0; // not volatile, only accessed in the cleaning method 32 | private CacheoutListerner evictionListener; 33 | private CleanupThread cleanupThread; 34 | 35 | public LruCache(int upperWaterMark, final int lowerWaterMark, int acceptableWatermark, 36 | int initialSize, boolean runCleanupThread, boolean runNewThreadForCleanup, 37 | CacheoutListerner evictionListener) 38 | { 39 | if (upperWaterMark < 1) 40 | { 41 | throw new IllegalArgumentException("upperWaterMark must be > 0"); 42 | } 43 | if (lowerWaterMark >= upperWaterMark) 44 | { 45 | throw new IllegalArgumentException("lowerWaterMark must be < upperWaterMark"); 46 | } 47 | map = new ConcurrentHashMap<>(initialSize); 48 | keys = new HashMap<>(); 49 | newThreadForCleanup = runNewThreadForCleanup; 50 | this.upperWaterMark = upperWaterMark; 51 | this.lowerWaterMark = lowerWaterMark; 52 | this.acceptableWaterMark = acceptableWatermark; 53 | this.evictionListener = evictionListener; 54 | if (runCleanupThread) 55 | { 56 | cleanupThread = new CleanupThread(this); 57 | cleanupThread.setName("LruCache cleanup"); 58 | cleanupThread.start(); 59 | } 60 | } 61 | 62 | public LruCache(int size, int lowerWatermark) 63 | { 64 | this(size, lowerWatermark, (int) Math.floor((lowerWatermark + size) / 2), 65 | (int) Math.ceil(0.75 * size), true, false, null); 66 | } 67 | 68 | /** 69 | * lru 70 | * 71 | * @param size 72 | */ 73 | public LruCache(int size) 74 | { 75 | this(size, (int) Math.floor(0.9 * size), (int) Math.floor(0.95 * size), 76 | (int) Math.ceil(0.75 * size), true, false, null); 77 | } 78 | 79 | private Object get(String key) 80 | { 81 | CacheEntry e = map.get(key); 82 | if (e == null) 83 | { 84 | stats.missCounter.incrementAndGet(); 85 | return null; 86 | } 87 | e.lastAccessed = stats.accessCounter.incrementAndGet(); 88 | return e.value; 89 | } 90 | 91 | private Object put(String key, Object val) 92 | { 93 | if (val == null) 94 | { 95 | return null; 96 | } 97 | CacheEntry e = new CacheEntry<>(key, val, stats.accessCounter.incrementAndGet()); 98 | CacheEntry oldCacheEntry = map.put(key, e); 99 | int currentSize; 100 | if (oldCacheEntry == null) 101 | { 102 | currentSize = stats.size.incrementAndGet(); 103 | } else 104 | { 105 | currentSize = stats.size.get(); 106 | } 107 | stats.putCounter.incrementAndGet(); 108 | // Check if we need to clear out old entries from the cache. 109 | // isCleaning variable is checked instead of markAndSweepLock.isLocked() 110 | // for performance because every put invokation will check until 111 | // the size is back to an acceptable level. 112 | // 113 | // There is a race between the check and the call to markAndSweep, but 114 | // it's unimportant because markAndSweep actually aquires the lock or returns if it can't. 115 | // 116 | // Thread safety note: isCleaning read is piggybacked (comes after) other volatile reads 117 | // in this method. 118 | if (currentSize > upperWaterMark && !isCleaning) 119 | { 120 | if (newThreadForCleanup) 121 | { 122 | new Thread() 123 | { 124 | @Override 125 | public void run() 126 | { 127 | markAndSweep(); 128 | } 129 | }.start(); 130 | } else if (cleanupThread != null) 131 | { 132 | cleanupThread.wakeThread(); 133 | } else 134 | { 135 | markAndSweep(); 136 | } 137 | } 138 | return oldCacheEntry == null ? null : oldCacheEntry.value; 139 | } 140 | 141 | /** 142 | * Removes items from the cache to bring the size down to an acceptable value 143 | * ('acceptableWaterMark'). 144 | *

145 | * It is done in two stages. In the first stage, least recently used items are 146 | * evicted. If, after the first stage, the cache size is still greater than 147 | * 'acceptableSize' config parameter, the second stage takes over. 148 | *

149 | * The second stage is more intensive and tries to bring down the cache size 150 | * to the 'lowerWaterMark' config parameter. 151 | */ 152 | private void markAndSweep() 153 | { 154 | // if we want to keep at least 1000 entries, then timestamps of 155 | // current through current-1000 are guaranteed not to be the oldest (but that does 156 | // not mean there are 1000 entries in that group... it's acutally anywhere between 157 | // 1 and 1000). 158 | // Also, if we want to remove 500 entries, then 159 | // oldestEntry through oldestEntry+500 are guaranteed to be 160 | // removed (however many there are there). 161 | 162 | if (!markAndSweepLock.tryLock()) 163 | { 164 | return; 165 | } 166 | try 167 | { 168 | long oldestEntry = this.oldestEntry; 169 | isCleaning = true; 170 | this.oldestEntry = oldestEntry; // volatile write to make isCleaning visible 171 | 172 | long timeCurrent = stats.accessCounter.get(); 173 | int sz = stats.size.get(); 174 | 175 | int numRemoved = 0; 176 | int numKept = 0; 177 | long newestEntry = timeCurrent; 178 | long newNewestEntry = -1; 179 | long newOldestEntry = Long.MAX_VALUE; 180 | 181 | int wantToKeep = lowerWaterMark; 182 | int wantToRemove = sz - lowerWaterMark; 183 | 184 | @SuppressWarnings("unchecked") // generic array's are anoying 185 | CacheEntry[] eset = new CacheEntry[sz]; 186 | int eSize = 0; 187 | 188 | // System.out.println("newestEntry="+newestEntry + " oldestEntry="+oldestEntry); 189 | // System.out.println("items removed:" + numRemoved + " numKept=" + numKept + " esetSz="+ eSize + " sz-numRemoved=" + (sz-numRemoved)); 190 | for (CacheEntry ce : map.values()) 191 | { 192 | // set lastAccessedCopy to avoid more volatile reads 193 | ce.lastAccessedCopy = ce.lastAccessed; 194 | long thisEntry = ce.lastAccessedCopy; 195 | 196 | // since the wantToKeep group is likely to be bigger than wantToRemove, check it first 197 | if (thisEntry > newestEntry - wantToKeep) 198 | { 199 | // this entry is guaranteed not to be in the bottom 200 | // group, so do nothing. 201 | numKept++; 202 | newOldestEntry = Math.min(thisEntry, newOldestEntry); 203 | } else if (thisEntry < oldestEntry + wantToRemove) 204 | { // entry in bottom group? 205 | // this entry is guaranteed to be in the bottom group 206 | // so immediately remove it from the map. 207 | evictEntry((String) ce.key); 208 | numRemoved++; 209 | } else // This entry *could* be in the bottom group. 210 | // Collect these entries to avoid another full pass... this is wasted 211 | // effort if enough entries are normally removed in this first pass. 212 | // An alternate impl could make a full second pass. 213 | { 214 | if (eSize < eset.length - 1) 215 | { 216 | eset[eSize++] = ce; 217 | newNewestEntry = Math.max(thisEntry, newNewestEntry); 218 | newOldestEntry = Math.min(thisEntry, newOldestEntry); 219 | } 220 | } 221 | } 222 | 223 | // System.out.println("items removed:" + numRemoved + " numKept=" + numKept + " esetSz="+ eSize + " sz-numRemoved=" + (sz-numRemoved)); 224 | // TODO: allow this to be customized in the constructor? 225 | int numPasses = 1; // maximum number of linear passes over the data 226 | 227 | // if we didn't remove enough entries, then make more passes 228 | // over the values we collected, with updated min and max values. 229 | while (sz - numRemoved > acceptableWaterMark && --numPasses >= 0) 230 | { 231 | oldestEntry = newOldestEntry == Long.MAX_VALUE ? oldestEntry : newOldestEntry; 232 | newOldestEntry = Long.MAX_VALUE; 233 | newestEntry = newNewestEntry; 234 | newNewestEntry = -1; 235 | wantToKeep = lowerWaterMark - numKept; 236 | wantToRemove = sz - lowerWaterMark - numRemoved; 237 | // iterate backward to make it easy to remove items. 238 | for (int i = eSize - 1; i >= 0; i--) 239 | { 240 | CacheEntry ce = eset[i]; 241 | long thisEntry = ce.lastAccessedCopy; 242 | if (thisEntry > newestEntry - wantToKeep) 243 | { 244 | // this entry is guaranteed not to be in the bottom 245 | // group, so do nothing but remove it from the eset. 246 | numKept++; 247 | // remove the entry by moving the last element to it's position 248 | eset[i] = eset[eSize - 1]; 249 | eSize--; 250 | newOldestEntry = Math.min(thisEntry, newOldestEntry); 251 | } else if (thisEntry < oldestEntry + wantToRemove) 252 | { // entry in bottom group? 253 | // this entry is guaranteed to be in the bottom group 254 | // so immediately remove it from the map. 255 | evictEntry((String) ce.key); 256 | numRemoved++; 257 | // remove the entry by moving the last element to it's position 258 | eset[i] = eset[eSize - 1]; 259 | eSize--; 260 | } else 261 | { 262 | // This entry *could* be in the bottom group, so keep it in the eset, 263 | // and update the stats. 264 | newNewestEntry = Math.max(thisEntry, newNewestEntry); 265 | newOldestEntry = Math.min(thisEntry, newOldestEntry); 266 | } 267 | } 268 | // System.out.println("items removed:" + numRemoved + " numKept=" + numKept + " esetSz="+ eSize + " sz-numRemoved=" + (sz-numRemoved)); 269 | } 270 | // if we still didn't remove enough entries, then make another pass while 271 | // inserting into a priority queue 272 | if (sz - numRemoved > acceptableWaterMark) 273 | { 274 | oldestEntry = newOldestEntry == Long.MAX_VALUE ? oldestEntry : newOldestEntry; 275 | newOldestEntry = Long.MAX_VALUE; 276 | newestEntry = newNewestEntry; 277 | newNewestEntry = -1; 278 | wantToKeep = lowerWaterMark - numKept; 279 | wantToRemove = sz - lowerWaterMark - numRemoved; 280 | PQueue queue = new PQueue<>(wantToRemove); 281 | for (int i = eSize - 1; i >= 0; i--) 282 | { 283 | CacheEntry ce = eset[i]; 284 | long thisEntry = ce.lastAccessedCopy; 285 | if (thisEntry > newestEntry - wantToKeep) 286 | { 287 | // this entry is guaranteed not to be in the bottom 288 | // group, so do nothing but remove it from the eset. 289 | numKept++; 290 | // removal not necessary on last pass. 291 | // eset[i] = eset[eSize-1]; 292 | // eSize--; 293 | 294 | newOldestEntry = Math.min(thisEntry, newOldestEntry); 295 | 296 | } else if (thisEntry < oldestEntry + wantToRemove) 297 | { // entry in bottom group? 298 | // this entry is guaranteed to be in the bottom group 299 | // so immediately remove it. 300 | evictEntry((String) ce.key); 301 | numRemoved++; 302 | 303 | // removal not necessary on last pass. 304 | // eset[i] = eset[eSize-1]; 305 | // eSize--; 306 | } else 307 | { 308 | // This entry *could* be in the bottom group. 309 | // add it to the priority queue 310 | 311 | // everything in the priority queue will be removed, so keep track of 312 | // the lowest value that ever comes back out of the queue. 313 | // first reduce the size of the priority queue to account for 314 | // the number of items we have already removed while executing 315 | // this loop so far. 316 | queue.myMaxSize = sz - lowerWaterMark - numRemoved; 317 | while (queue.size() > queue.myMaxSize && queue.size() > 0) 318 | { 319 | CacheEntry otherEntry = (CacheEntry) queue.pop(); 320 | newOldestEntry = Math.min(otherEntry.lastAccessedCopy, newOldestEntry); 321 | } 322 | if (queue.myMaxSize <= 0) 323 | { 324 | break; 325 | } 326 | Object o = queue.myInsertWithOverflow(ce); 327 | if (o != null) 328 | { 329 | newOldestEntry = Math.min(((CacheEntry) o).lastAccessedCopy, newOldestEntry); 330 | } 331 | } 332 | } 333 | // Now delete everything in the priority queue. 334 | // avoid using pop() since order doesn't matter anymore 335 | for (Object ce : queue.getValues()) 336 | { 337 | if (ce == null) 338 | { 339 | continue; 340 | } 341 | evictEntry((String) ((CacheEntry) ce).key); 342 | numRemoved++; 343 | } 344 | // System.out.println("items removed:" + numRemoved + " numKept=" + numKept + " initialQueueSize="+ wantToRemove + " finalQueueSize=" + queue.size() + " sz-numRemoved=" + (sz-numRemoved)); 345 | } 346 | oldestEntry = newOldestEntry == Long.MAX_VALUE ? oldestEntry : newOldestEntry; 347 | this.oldestEntry = oldestEntry; 348 | } finally 349 | { 350 | isCleaning = false; // set before markAndSweep.unlock() for visibility 351 | markAndSweepLock.unlock(); 352 | } 353 | } 354 | 355 | @Override 356 | public void cache(Object entity, String id) 357 | { 358 | String key = entity.getClass().getName() + ":" + id; 359 | this.put(key, entity); 360 | Set set = keys.get(entity.getClass()); 361 | if (set == null) 362 | { 363 | set = new HashSet<>(); 364 | keys.put(entity.getClass(), set); 365 | } 366 | set.add(key); 367 | } 368 | 369 | @Override 370 | public T get(Class claz, String id) 371 | { 372 | return (T) this.get(claz.getName() + ":" + id); 373 | } 374 | 375 | @Override 376 | public void evict(Class claz, String id) 377 | { 378 | this.evictEntry(claz.getName() + ":" + id); 379 | } 380 | 381 | @Override 382 | public void evict(Class claz) 383 | { 384 | Set set = keys.remove(claz); 385 | for (String k : set) 386 | { 387 | this.evictEntry(k); 388 | } 389 | } 390 | 391 | @Override 392 | public void setCacheoutListerner(CacheoutListerner cacheoutListerner) 393 | { 394 | this.evictionListener = cacheoutListerner; 395 | } 396 | 397 | @Override 398 | public CacheoutListerner getCacheoutListerner() 399 | { 400 | return this.evictionListener; 401 | } 402 | 403 | @Override 404 | public Stats stats() 405 | { 406 | return stats; 407 | } 408 | 409 | @Override 410 | public void clear() 411 | { 412 | map.clear(); 413 | } 414 | 415 | private static class PQueue extends PriorityQueue> 416 | { 417 | 418 | int myMaxSize; 419 | final Object[] heap; 420 | 421 | PQueue(int maxSz) 422 | { 423 | super(maxSz); 424 | heap = getHeapArray(); 425 | myMaxSize = maxSz; 426 | } 427 | 428 | @SuppressWarnings("unchecked") 429 | Iterable> getValues() 430 | { 431 | return (Iterable) Collections.unmodifiableCollection(Arrays.asList(heap)); 432 | } 433 | 434 | @Override 435 | protected boolean lessThan(CacheEntry a, CacheEntry b) 436 | { 437 | // reverse the parameter order so that the queue keeps the oldest items 438 | return b.lastAccessedCopy < a.lastAccessedCopy; 439 | } 440 | 441 | // necessary because maxSize is private in base class 442 | @SuppressWarnings("unchecked") 443 | public CacheEntry myInsertWithOverflow(CacheEntry element) 444 | { 445 | if (size() < myMaxSize) 446 | { 447 | add(element); 448 | return null; 449 | } else if (size() > 0 && !lessThan(element, (CacheEntry) heap[1])) 450 | { 451 | CacheEntry ret = (CacheEntry) heap[1]; 452 | heap[1] = element; 453 | updateTop(); 454 | return ret; 455 | } else 456 | { 457 | return element; 458 | } 459 | } 460 | } 461 | 462 | private void evictEntry(String key) 463 | { 464 | CacheEntry o = map.remove(key); 465 | if (o == null) 466 | { 467 | return; 468 | } 469 | try 470 | { 471 | int index = key.indexOf(':'); 472 | Set set = keys.get(Class.forName(key.substring(0, index))); 473 | set.remove(key); 474 | } catch (Exception e) 475 | { 476 | } 477 | stats.size.decrementAndGet(); 478 | stats.evictionCounter.incrementAndGet(); 479 | if (evictionListener != null) 480 | { 481 | evictionListener.onCacheout(o.value.getClass(), key); 482 | } 483 | } 484 | 485 | /** 486 | * Returns 'n' number of oldest accessed entries present in this cache. 487 | * 488 | * This uses a TreeSet to collect the 'n' oldest items ordered by ascending 489 | * last access time and returns a LinkedHashMap containing 'n' or less than 490 | * 'n' entries. 491 | * 492 | * @param n the number of oldest items needed 493 | * @return a LinkedHashMap containing 'n' or less than 'n' entries 494 | */ 495 | public Map getOldestAccessedItems(int n) 496 | { 497 | Map result = new LinkedHashMap<>(); 498 | if (n <= 0) 499 | { 500 | return result; 501 | } 502 | TreeSet tree = new TreeSet<>(); 503 | markAndSweepLock.lock(); 504 | try 505 | { 506 | for (Map.Entry entry : map.entrySet()) 507 | { 508 | CacheEntry ce = entry.getValue(); 509 | ce.lastAccessedCopy = ce.lastAccessed; 510 | if (tree.size() < n) 511 | { 512 | tree.add(ce); 513 | } else if (ce.lastAccessedCopy < tree.first().lastAccessedCopy) 514 | { 515 | tree.remove(tree.first()); 516 | tree.add(ce); 517 | } 518 | } 519 | } finally 520 | { 521 | markAndSweepLock.unlock(); 522 | } 523 | for (CacheEntry e : tree) 524 | { 525 | result.put(e.key, e.value); 526 | } 527 | return result; 528 | } 529 | 530 | public Map getLatestAccessedItems(int n) 531 | { 532 | Map result = new LinkedHashMap<>(); 533 | if (n <= 0) 534 | { 535 | return result; 536 | } 537 | TreeSet tree = new TreeSet<>(); 538 | // we need to grab the lock since we are changing lastAccessedCopy 539 | markAndSweepLock.lock(); 540 | try 541 | { 542 | for (Map.Entry entry : map.entrySet()) 543 | { 544 | CacheEntry ce = entry.getValue(); 545 | ce.lastAccessedCopy = ce.lastAccessed; 546 | if (tree.size() < n) 547 | { 548 | tree.add(ce); 549 | } else if (ce.lastAccessedCopy > tree.last().lastAccessedCopy) 550 | { 551 | tree.remove(tree.last()); 552 | tree.add(ce); 553 | } 554 | } 555 | } finally 556 | { 557 | markAndSweepLock.unlock(); 558 | } 559 | for (CacheEntry e : tree) 560 | { 561 | result.put(e.key, e.value); 562 | } 563 | return result; 564 | } 565 | 566 | public int size() 567 | { 568 | return stats.size.get(); 569 | } 570 | 571 | public Map getMap() 572 | { 573 | return map; 574 | } 575 | 576 | private static class CacheEntry implements Comparable> 577 | { 578 | 579 | K key; 580 | V value; 581 | volatile long lastAccessed = 0; 582 | long lastAccessedCopy = 0; 583 | 584 | public CacheEntry(K key, V value, long lastAccessed) 585 | { 586 | this.key = key; 587 | this.value = value; 588 | this.lastAccessed = lastAccessed; 589 | } 590 | 591 | public void setLastAccessed(long lastAccessed) 592 | { 593 | this.lastAccessed = lastAccessed; 594 | } 595 | 596 | @Override 597 | public int compareTo(CacheEntry that) 598 | { 599 | if (this.lastAccessedCopy == that.lastAccessedCopy) 600 | { 601 | return 0; 602 | } 603 | return this.lastAccessedCopy < that.lastAccessedCopy ? 1 : -1; 604 | } 605 | 606 | @Override 607 | public int hashCode() 608 | { 609 | return value.hashCode(); 610 | } 611 | 612 | @Override 613 | public boolean equals(Object obj) 614 | { 615 | return value.equals(obj); 616 | } 617 | 618 | @Override 619 | public String toString() 620 | { 621 | return "key: " + key + " value: " + value + " lastAccessed:" + lastAccessed; 622 | } 623 | } 624 | 625 | private boolean isDestroyed = false; 626 | 627 | public void destroy() 628 | { 629 | try 630 | { 631 | if (cleanupThread != null) 632 | { 633 | cleanupThread.stopThread(); 634 | } 635 | } finally 636 | { 637 | isDestroyed = true; 638 | } 639 | } 640 | 641 | public Stats getStats() 642 | { 643 | return stats; 644 | } 645 | 646 | public static interface EvictionListener 647 | { 648 | 649 | public void evictedEntry(String key, Object value); 650 | } 651 | 652 | private static class CleanupThread extends Thread 653 | { 654 | 655 | private final WeakReference cache; 656 | private boolean stop = false; 657 | 658 | public CleanupThread(LruCache c) 659 | { 660 | cache = new WeakReference<>(c); 661 | } 662 | 663 | @Override 664 | public void run() 665 | { 666 | while (true) 667 | { 668 | synchronized (this) 669 | { 670 | if (stop) 671 | { 672 | break; 673 | } 674 | try 675 | { 676 | this.wait(); 677 | } catch (InterruptedException e) 678 | { 679 | } 680 | } 681 | if (stop) 682 | { 683 | break; 684 | } 685 | LruCache c = cache.get(); 686 | if (c == null) 687 | { 688 | break; 689 | } 690 | c.markAndSweep(); 691 | } 692 | } 693 | 694 | void wakeThread() 695 | { 696 | synchronized (this) 697 | { 698 | this.notify(); 699 | } 700 | } 701 | 702 | void stopThread() 703 | { 704 | synchronized (this) 705 | { 706 | stop = true; 707 | this.notify(); 708 | } 709 | } 710 | } 711 | 712 | @Override 713 | protected void finalize() throws Throwable 714 | { 715 | try 716 | { 717 | if (!isDestroyed) 718 | { 719 | destroy(); 720 | } 721 | } finally 722 | { 723 | super.finalize(); 724 | } 725 | } 726 | } 727 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/util/PriorityQueue.java: -------------------------------------------------------------------------------- 1 | package org.rojo.util; 2 | 3 | /** 4 | * priority queue 5 | * 6 | * @author lucene 7 | * @param 8 | */ 9 | public abstract class PriorityQueue 10 | { 11 | 12 | public static final int MAX_ARRAY_LENGTH = Integer.MAX_VALUE - 256; 13 | private int size = 0; 14 | private final int maxSize; 15 | private final T[] heap; 16 | 17 | public PriorityQueue(int maxSize) 18 | { 19 | this(maxSize, true); 20 | } 21 | 22 | public PriorityQueue(int maxSize, boolean prepopulate) 23 | { 24 | final int heapSize; 25 | if (0 == maxSize) 26 | { 27 | // We allocate 1 extra to avoid if statement in top() 28 | heapSize = 2; 29 | } else if (maxSize > MAX_ARRAY_LENGTH) 30 | { 31 | // Don't wrap heapSize to -1, in this case, which 32 | // causes a confusing NegativeArraySizeException. 33 | // Note that very likely this will simply then hit 34 | // an OOME, but at least that's more indicative to 35 | // caller that this values is too big. We don't +1 36 | // in this case, but it's very unlikely in practice 37 | // one will actually insert this many objects into 38 | // the PQ: 39 | // Throw exception to prevent confusing OOME: 40 | throw new IllegalArgumentException("maxSize must be <= " + MAX_ARRAY_LENGTH + "; got: " + maxSize); 41 | } else 42 | { 43 | // NOTE: we add +1 because all access to heap is 44 | // 1-based not 0-based. heap[0] is unused. 45 | heapSize = maxSize + 1; 46 | } 47 | // T is unbounded type, so this unchecked cast works always: 48 | @SuppressWarnings("unchecked") 49 | final T[] h = (T[]) new Object[heapSize]; 50 | this.heap = h; 51 | this.maxSize = maxSize; 52 | 53 | if (prepopulate) 54 | { 55 | // If sentinel objects are supported, populate the queue with them 56 | T sentinel = getSentinelObject(); 57 | if (sentinel != null) 58 | { 59 | heap[1] = sentinel; 60 | for (int i = 2; i < heap.length; i++) 61 | { 62 | heap[i] = getSentinelObject(); 63 | } 64 | size = maxSize; 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Determines the ordering of objects in this priority queue. Subclasses must 71 | * define this one method. 72 | * 73 | * @return true iff parameter a is less than parameter 74 | * b. 75 | */ 76 | protected abstract boolean lessThan(T a, T b); 77 | 78 | /** 79 | * This method can be overridden by extending classes to return a sentinel 80 | * object which will be used by the 81 | * {@link PriorityQueue#PriorityQueue(int,boolean)} constructor to fill the 82 | * queue, so that the code which uses that queue can always assume it's full 83 | * and only change the top without attempting to insert any new object.
84 | * 85 | * Those sentinel values should always compare worse than any non-sentinel 86 | * value (i.e., {@link #lessThan} should always favor the non-sentinel 87 | * values).
88 | * 89 | * By default, this method returns false, which means the queue will not be 90 | * filled with sentinel values. Otherwise, the value returned will be used to 91 | * pre-populate the queue. Adds sentinel values to the queue.
92 | * 93 | * If this method is extended to return a non-null value, then the following 94 | * usage pattern is recommended: 95 | * 96 | *

 97 |    * // extends getSentinelObject() to return a non-null value.
 98 |    * PriorityQueue pq = new MyQueue(numHits); // save the
 99 |    * 'top' element, which is guaranteed to not be null. MyObject pqTop =
100 |    * pq.top();
101 |    * <...>
102 |    * // now in order to add a new element, which is 'better' than top (after //
103 |    * you've verified it is better), it is as simple as: pqTop.change(). pqTop =
104 |    * pq.updateTop();
105 |    * 
106 | * 107 | * NOTE: if this method returns a non-null value, it will be called by 108 | * the {@link PriorityQueue#PriorityQueue(int,boolean)} constructor 109 | * {@link #size()} times, relying on a new object to be returned and will not 110 | * check if it's null again. Therefore you should ensure any call to this 111 | * method creates a new instance and behaves consistently, e.g., it cannot 112 | * return null if it previously returned non-null. 113 | * 114 | * @return the sentinel object to use to pre-populate the queue, or null if 115 | * sentinel objects are not supported. 116 | */ 117 | protected T getSentinelObject() 118 | { 119 | return null; 120 | } 121 | 122 | /** 123 | * Adds an Object to a PriorityQueue in log(size) time. If one tries to add 124 | * more objects than maxSize from initialize an 125 | * {@link ArrayIndexOutOfBoundsException} is thrown. 126 | * 127 | * @return the new 'top' element in the queue. 128 | */ 129 | public final T add(T element) 130 | { 131 | size++; 132 | heap[size] = element; 133 | upHeap(); 134 | return heap[1]; 135 | } 136 | 137 | /** 138 | * Adds an Object to a PriorityQueue in log(size) time. It returns the object 139 | * (if any) that was dropped off the heap because it was full. This can be the 140 | * given parameter (in case it is smaller than the full heap's minimum, and 141 | * couldn't be added), or another object that was previously the smallest 142 | * value in the heap and now has been replaced by a larger one, or null if the 143 | * queue wasn't yet full with maxSize elements. 144 | */ 145 | public T insertWithOverflow(T element) 146 | { 147 | if (size < maxSize) 148 | { 149 | add(element); 150 | return null; 151 | } else if (size > 0 && !lessThan(element, heap[1])) 152 | { 153 | T ret = heap[1]; 154 | heap[1] = element; 155 | updateTop(); 156 | return ret; 157 | } else 158 | { 159 | return element; 160 | } 161 | } 162 | 163 | /** 164 | * Returns the least element of the PriorityQueue in constant time. 165 | */ 166 | public final T top() 167 | { 168 | // We don't need to check size here: if maxSize is 0, 169 | // then heap is length 2 array with both entries null. 170 | // If size is 0 then heap[1] is already null. 171 | return heap[1]; 172 | } 173 | 174 | /** 175 | * Removes and returns the least element of the PriorityQueue in log(size) 176 | * time. 177 | */ 178 | public final T pop() 179 | { 180 | if (size > 0) 181 | { 182 | T result = heap[1]; // save first value 183 | heap[1] = heap[size]; // move last to first 184 | heap[size] = null; // permit GC of objects 185 | size--; 186 | downHeap(); // adjust heap 187 | return result; 188 | } else 189 | { 190 | return null; 191 | } 192 | } 193 | 194 | /** 195 | * Should be called when the Object at top changes values. Still log(n) worst 196 | * case, but it's at least twice as fast to 197 | * 198 | *
199 |    * pq.top().change(); pq.updateTop();
200 |    * 
201 | * 202 | * instead of 203 | * 204 | *
205 |    * o = pq.pop(); o.change(); pq.push(o);
206 |    * 
207 | * 208 | * @return the new 'top' element. 209 | */ 210 | public final T updateTop() 211 | { 212 | downHeap(); 213 | return heap[1]; 214 | } 215 | 216 | /** 217 | * Returns the number of elements currently stored in the PriorityQueue. 218 | */ 219 | public final int size() 220 | { 221 | return size; 222 | } 223 | 224 | /** 225 | * Removes all entries from the PriorityQueue. 226 | */ 227 | public final void clear() 228 | { 229 | for (int i = 0; i <= size; i++) 230 | { 231 | heap[i] = null; 232 | } 233 | size = 0; 234 | } 235 | 236 | private final void upHeap() 237 | { 238 | int i = size; 239 | T node = heap[i]; // save bottom node 240 | int j = i >>> 1; 241 | while (j > 0 && lessThan(node, heap[j])) 242 | { 243 | heap[i] = heap[j]; // shift parents down 244 | i = j; 245 | j = j >>> 1; 246 | } 247 | heap[i] = node; // install saved node 248 | } 249 | 250 | private final void downHeap() 251 | { 252 | int i = 1; 253 | T node = heap[i]; // save top node 254 | int j = i << 1; // find smaller child 255 | int k = j + 1; 256 | if (k <= size && lessThan(heap[k], heap[j])) 257 | { 258 | j = k; 259 | } 260 | while (j <= size && lessThan(heap[j], node)) 261 | { 262 | heap[i] = heap[j]; // shift up child 263 | i = j; 264 | j = i << 1; 265 | k = j + 1; 266 | if (k <= size && lessThan(heap[k], heap[j])) 267 | { 268 | j = k; 269 | } 270 | } 271 | heap[i] = node; // install saved node 272 | } 273 | 274 | /** 275 | * This method returns the internal heap array as Object[]. 276 | * 277 | * @lucene.internal 278 | */ 279 | protected final Object[] getHeapArray() 280 | { 281 | return (Object[]) heap; 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/util/SoftCache.java: -------------------------------------------------------------------------------- 1 | /** 2 | * cache 3 | */ 4 | package org.rojo.util; 5 | 6 | import java.lang.ref.ReferenceQueue; 7 | import java.lang.ref.SoftReference; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | import java.util.concurrent.locks.Lock; 11 | import java.util.concurrent.locks.ReadWriteLock; 12 | import java.util.concurrent.locks.ReentrantReadWriteLock; 13 | 14 | /** 15 | * 16 | * @author beykery 17 | */ 18 | public class SoftCache implements Cache 19 | { 20 | 21 | private CacheoutListerner cacheoutListerner; 22 | private int TIMES_CACHE_CLEAR = 15000; 23 | private volatile int timeCache; 24 | private final ReadWriteLock lock = new ReentrantReadWriteLock(); 25 | private final Lock readLock = lock.readLock(); 26 | private final Lock writeLock = lock.writeLock(); 27 | private final Map>> cache = new HashMap<>(); 28 | private final ReferenceQueue> rq = new ReferenceQueue<>(); 29 | private final Stats stats = new Stats(); 30 | 31 | public SoftCache(int size) 32 | { 33 | } 34 | 35 | @Override 36 | public Stats stats() 37 | { 38 | return stats; 39 | } 40 | 41 | private class SoftObjectReference extends SoftReference 42 | { 43 | 44 | public final String id; 45 | public final Class claz; 46 | 47 | public SoftObjectReference(Object r, ReferenceQueue q, String id) 48 | { 49 | super(r, q); 50 | this.id = id; 51 | this.claz = r.getClass(); 52 | } 53 | } 54 | 55 | /** 56 | * cache 57 | * 58 | * @param entity 59 | * @param id 60 | */ 61 | @Override 62 | public void cache(Object entity, String id) 63 | { 64 | writeLock.lock(); 65 | try 66 | { 67 | Map> c = cache.get(entity.getClass()); 68 | if (c == null) 69 | { 70 | c = new HashMap<>(); 71 | cache.put(entity.getClass(), c); 72 | } 73 | Object old = c.put(id, new SoftObjectReference<>(entity, rq, id)); 74 | int currentSize; 75 | if (old == null) 76 | { 77 | currentSize = stats.size.incrementAndGet(); 78 | } else 79 | { 80 | currentSize = stats.size.get(); 81 | } 82 | stats.putCounter.incrementAndGet(); 83 | timeCache++; 84 | if (timeCache >= TIMES_CACHE_CLEAR) 85 | { 86 | SoftObjectReference sr; 87 | while ((sr = (SoftObjectReference) rq.poll()) != null) 88 | { 89 | c = cache.get(sr.claz); 90 | Object en = c.remove(sr.id); 91 | onCacheout(sr.claz, sr.id); 92 | } 93 | timeCache = 0; 94 | } 95 | } finally 96 | { 97 | writeLock.unlock(); 98 | } 99 | } 100 | 101 | /** 102 | * 103 | * @param 104 | * @param claz 105 | * @param id 106 | * @return 107 | */ 108 | @Override 109 | public T get(Class claz, String id) 110 | { 111 | readLock.lock(); 112 | try 113 | { 114 | Map> c = cache.get(claz); 115 | if (c != null) 116 | { 117 | SoftReference sr = c.get(id); 118 | if (sr != null) 119 | { 120 | Object r = sr.get(); 121 | if (r != null) 122 | { 123 | stats.accessCounter.incrementAndGet(); 124 | } 125 | return (T) r; 126 | } 127 | } 128 | stats.missCounter.incrementAndGet(); 129 | return null; 130 | } finally 131 | { 132 | readLock.unlock(); 133 | } 134 | } 135 | 136 | /** 137 | * evict an object 138 | * 139 | * @param claz 140 | * @param id 141 | */ 142 | @Override 143 | public void evict(Class claz, String id) 144 | { 145 | writeLock.lock(); 146 | try 147 | { 148 | Map> c = cache.get(claz); 149 | if (c != null) 150 | { 151 | c.remove(id); 152 | } 153 | } finally 154 | { 155 | writeLock.unlock(); 156 | } 157 | } 158 | 159 | @Override 160 | public void evict(Class claz) 161 | { 162 | this.clear(claz); 163 | } 164 | 165 | /** 166 | * evict entites of claz 167 | * 168 | * @param claz 169 | */ 170 | public void clear(Class claz) 171 | { 172 | writeLock.lock(); 173 | try 174 | { 175 | Map> c = cache.get(claz); 176 | if (c != null) 177 | { 178 | c.clear(); 179 | } 180 | } finally 181 | { 182 | writeLock.unlock(); 183 | } 184 | } 185 | 186 | /** 187 | * clear cache 188 | */ 189 | @SuppressWarnings("empty-statement") 190 | @Override 191 | public void clear() 192 | { 193 | writeLock.lock(); 194 | try 195 | { 196 | for (Map> m : cache.values()) 197 | { 198 | m.clear(); 199 | } 200 | SoftObjectReference sr; 201 | while ((sr = (SoftObjectReference) rq.poll()) != null) 202 | { 203 | onCacheout(sr.claz, sr.id); 204 | } 205 | } finally 206 | { 207 | writeLock.unlock(); 208 | } 209 | } 210 | 211 | /** 212 | * cacheout 213 | * 214 | * @param claz 215 | * @param id 216 | */ 217 | private void onCacheout(Class claz, String id) 218 | { 219 | stats.size.decrementAndGet(); 220 | if (cacheoutListerner != null) 221 | { 222 | cacheoutListerner.onCacheout(claz, id); 223 | } 224 | } 225 | 226 | @Override 227 | public CacheoutListerner getCacheoutListerner() 228 | { 229 | return cacheoutListerner; 230 | } 231 | 232 | @Override 233 | public void setCacheoutListerner(CacheoutListerner cacheoutListerner) 234 | { 235 | this.cacheoutListerner = cacheoutListerner; 236 | } 237 | 238 | public int getTIMES_CACHE_CLEAR() 239 | { 240 | return TIMES_CACHE_CLEAR; 241 | } 242 | 243 | public void setTIMES_CACHE_CLEAR(int TIMES_CACHE_CLEAR) 244 | { 245 | this.TIMES_CACHE_CLEAR = TIMES_CACHE_CLEAR; 246 | } 247 | 248 | } 249 | -------------------------------------------------------------------------------- /src/main/java/org/rojo/util/Stats.java: -------------------------------------------------------------------------------- 1 | package org.rojo.util; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | import java.util.concurrent.atomic.AtomicLong; 5 | 6 | /** 7 | * for stats 8 | * 9 | * @author beykery 10 | */ 11 | public class Stats 12 | { 13 | 14 | final AtomicLong accessCounter = new AtomicLong(0), 15 | putCounter = new AtomicLong(0), 16 | missCounter = new AtomicLong(); 17 | final AtomicInteger size = new AtomicInteger(); 18 | AtomicLong evictionCounter = new AtomicLong(); 19 | 20 | public long getLookups() 21 | { 22 | return (accessCounter.get() - putCounter.get() ) + missCounter.get(); 23 | } 24 | 25 | public long getHits() 26 | { 27 | return accessCounter.get() - putCounter.get() ; 28 | } 29 | 30 | public long getPuts() 31 | { 32 | return putCounter.get(); 33 | } 34 | 35 | public long getEvictions() 36 | { 37 | return evictionCounter.get(); 38 | } 39 | 40 | public int getSize() 41 | { 42 | return size.get(); 43 | } 44 | 45 | public long getMisses() 46 | { 47 | return missCounter.get(); 48 | } 49 | 50 | public void add(Stats other) 51 | { 52 | accessCounter.addAndGet(other.accessCounter.get()); 53 | putCounter.addAndGet(other.putCounter.get()); 54 | missCounter.addAndGet(other.missCounter.get()); 55 | evictionCounter.addAndGet(other.evictionCounter.get()); 56 | size.set(Math.max(size.get(), other.size.get())); 57 | } 58 | } 59 | --------------------------------------------------------------------------------