From 7de19f142f27fa142dfde35bfb40408e108ba7fa Mon Sep 17 00:00:00 2001 From: redkale Date: Wed, 29 Mar 2023 20:06:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0Cacheable.continuousid?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../org/redkale/persistence/Cacheable.java | 13 +- .../java/org/redkale/source/EntityCache.java | 114 ++++++++++++++---- .../java/org/redkale/source/EntityInfo.java | 9 +- 3 files changed, 108 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/redkale/persistence/Cacheable.java b/src/main/java/org/redkale/persistence/Cacheable.java index 7efd43ba7..eb128b1c9 100644 --- a/src/main/java/org/redkale/persistence/Cacheable.java +++ b/src/main/java/org/redkale/persistence/Cacheable.java @@ -17,8 +17,7 @@ package org.redkale.persistence; import static java.lang.annotation.ElementType.TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; -import java.lang.annotation.Retention; -import java.lang.annotation.Target; +import java.lang.annotation.*; /** * Specifies whether an entity should be cached if caching is enabled @@ -53,10 +52,18 @@ public @interface Cacheable { int interval() default 0; /** - * DataSource是否直接返回对象的真实引用, 而不是copy一份 + * (Optional) DataSource是否直接返回对象的真实引用, 而不是copy一份 * * @return boolean */ boolean direct() default false; + /** + * (Optional) 主键字段是否同时满足: 1、类型为int;2、主键值可为数组下标;3、记录总数有限;
+ * 用于EntityCache的全量数据是否用Array存储,主键值作为数组下标 + * + * @return boolean + */ + boolean continuousid() default false; + } diff --git a/src/main/java/org/redkale/source/EntityCache.java b/src/main/java/org/redkale/source/EntityCache.java index cdd421bb9..bf98afb9a 100644 --- a/src/main/java/org/redkale/source/EntityCache.java +++ b/src/main/java/org/redkale/source/EntityCache.java @@ -38,6 +38,11 @@ public final class EntityCache { // CopyOnWriteArrayList 插入慢、查询快; 10w数据插入需要3.2秒; ConcurrentLinkedQueue 插入快、查询慢;10w数据查询需要 0.062秒, 查询慢40%; private Collection list = new ConcurrentLinkedQueue(); + //continuousid=true此字段值才有效 + private T[] array; + + private final IntFunction mapFunc = c -> array[c]; + //Flipper.sort转换成Comparator的缓存 private final Map> sortComparators = new ConcurrentHashMap<>(); @@ -75,18 +80,22 @@ public final class EntityCache { //@Cacheable的定时更新秒数,为0表示不定时更新 final int interval; + //@Cacheable的主键字段是否同时满足: 1、类型为int;2、主键值可为数组下标;3、记录总数有限; + final boolean continuousid; + //@Cacheable的定时器 private ScheduledThreadPoolExecutor scheduler; private CompletableFuture> loadFuture; public EntityCache(final EntityInfo info, final Cacheable c) { - this(info, c != null ? c.interval() : 0, c != null && c.direct()); + this(info, c != null ? c.interval() : 0, c != null && c.direct(), c != null && c.continuousid()); } - EntityCache(final EntityInfo info, final int cacheInterval, final boolean cacheDirect) { + EntityCache(final EntityInfo info, final int cacheInterval, final boolean cacheDirect, final boolean cacheContinuousid) { this.info = info; this.interval = cacheInterval < 0 ? 0 : cacheInterval; + this.continuousid = cacheContinuousid && info.getPrimary().type() == int.class; this.type = info.getType(); this.arrayer = info.getArrayer(); this.creator = info.getCreator(); @@ -144,6 +153,7 @@ public final class EntityCache { } if (info.fullloader == null) { this.list = new ConcurrentLinkedQueue(); + this.array = null; this.map = new ConcurrentHashMap(); this.fullloaded = true; loading.set(false); @@ -154,6 +164,7 @@ public final class EntityCache { this.loadFuture = (CompletableFuture) allFuture; if (allFuture == null) { this.list = new ConcurrentLinkedQueue(); + this.array = null; this.map = new ConcurrentHashMap(); this.fullloaded = true; loading.set(false); @@ -175,6 +186,24 @@ public final class EntityCache { }); } this.list = all2 == null ? new ConcurrentLinkedQueue() : new ConcurrentLinkedQueue(all2); + if (continuousid && all2 != null && !all2.isEmpty()) { + Collections.sort(all2, (T o1, T o2) -> { + int v1 = o1 == null ? 0 : (Integer) primary.get(o1); + int v2 = o2 == null ? 0 : (Integer) primary.get(o2); + return v1 - v2; + }); + if ((Integer) primary.get(all2.get(0)) != 0) { + all2.add(null); + Collections.sort(all2, (T o1, T o2) -> { + int v1 = o1 == null ? 0 : (Integer) primary.get(o1); + int v2 = o2 == null ? 0 : (Integer) primary.get(o2); + return v1 - v2; + }); + } + this.array = all2.toArray(arrayer); + } else { + this.array = null; + } this.map = newmap2; } catch (Throwable t) { logger.log(Level.SEVERE, type + " schedule(interval=" + interval + "s) Cacheable error", t); @@ -194,6 +223,24 @@ public final class EntityCache { }); } this.list = new ConcurrentLinkedQueue(all); + if (continuousid && all != null && !all.isEmpty()) { + Collections.sort(all, (T o1, T o2) -> { + int v1 = o1 == null ? 0 : (Integer) primary.get(o1); + int v2 = o2 == null ? 0 : (Integer) primary.get(o2); + return v1 - v2; + }); + if ((Integer) primary.get(all.get(0)) != 0) { + all.add(null); + Collections.sort(all, (T o1, T o2) -> { + int v1 = o1 == null ? 0 : (Integer) primary.get(o1); + int v2 = o2 == null ? 0 : (Integer) primary.get(o2); + return v1 - v2; + }); + } + this.array = all.toArray(arrayer); + } else { + this.array = null; + } this.map = newmap; this.fullloaded = true; loading.set(false); @@ -208,6 +255,7 @@ public final class EntityCache { public int clear() { this.fullloaded = false; this.list = new ConcurrentLinkedQueue(); + this.array = null; this.map = new ConcurrentHashMap(); if (this.scheduler != null) { this.scheduler.shutdownNow(); @@ -240,28 +288,47 @@ public final class EntityCache { Class t = pks[0].getClass(); if (t == int[].class) { int[] ids = (int[]) pks[0]; - T[] array = arrayer.apply(ids.length); - for (int i = 0; i < array.length; i++) { + T[] result = arrayer.apply(ids.length); + for (int i = 0; i < result.length; i++) { T rs = map.get(ids[i]); - array[i] = rs == null ? null : (needCopy ? newReproduce.apply(this.creator.create(), rs) : rs); + result[i] = rs == null ? null : (needCopy ? newReproduce.apply(this.creator.create(), rs) : rs); } - return array; + return result; } else if (t == long[].class) { long[] ids = (long[]) pks[0]; - T[] array = arrayer.apply(ids.length); - for (int i = 0; i < array.length; i++) { + T[] result = arrayer.apply(ids.length); + for (int i = 0; i < result.length; i++) { T rs = map.get(ids[i]); - array[i] = rs == null ? null : (needCopy ? newReproduce.apply(this.creator.create(), rs) : rs); + result[i] = rs == null ? null : (needCopy ? newReproduce.apply(this.creator.create(), rs) : rs); } - return array; + return result; } } - T[] array = arrayer.apply(pks.length); - for (int i = 0; i < array.length; i++) { - T rs = map.get(pks[i]); - array[i] = rs == null ? null : (needCopy ? newReproduce.apply(this.creator.create(), rs) : rs); + if (continuousid) { + T[] array0 = array; + if (array0 == null) { + return arrayer.apply(pks.length); + } + T[] result = arrayer.apply(pks.length); + if (needCopy) { + for (int i = 0; i < result.length; i++) { + T rs = array0[(Integer) pks[i]]; + result[i] = rs == null ? null : newReproduce.apply(this.creator.create(), rs); + } + } else { + for (int i = 0; i < result.length; i++) { + T rs = array0[(Integer) pks[i]]; + result[i] = rs == null ? null : rs; + } + } + return result; } - return array; + T[] result = arrayer.apply(pks.length); + for (int i = 0; i < result.length; i++) { + T rs = map.get(pks[i]); + result[i] = rs == null ? null : (needCopy ? newReproduce.apply(this.creator.create(), rs) : rs); + } + return result; } public T find(final SelectColumn selects, final Serializable pk) { @@ -285,6 +352,9 @@ public final class EntityCache { } public T[] finds(final SelectColumn selects, Serializable... pks) { + if (selects == null) { + return finds(pks); + } if (pks == null || pks.length == 0) { return arrayer.apply(0); } @@ -300,8 +370,8 @@ public final class EntityCache { ids2 = (long[]) pks[0]; } } - T[] array = arrayer.apply(size); - for (int i = 0; i < array.length; i++) { + T[] result = arrayer.apply(size); + for (int i = 0; i < result.length; i++) { Serializable id = ids1 == null ? (ids2 == null ? pks[i] : ids2[i]) : ids1[i]; T rs = map.get(id); if (rs == null) { @@ -320,9 +390,9 @@ public final class EntityCache { } rs = t; } - array[i] = rs; + result[i] = rs; } - return array; + return result; } public T find(final SelectColumn selects, FilterNode node) { @@ -846,7 +916,7 @@ public final class EntityCache { if (entity == null || node == null) { return (T[]) Creator.newArray(type, 0); } - T[] rms = this.list.stream().filter(node.createPredicate(this)).toArray(Creator.arrayFunction(type)); + T[] rms = this.list.stream().filter(node.createPredicate(this)).toArray(arrayer); tableLock.lock(); //表锁, 可优化成行锁 try { for (T rs : rms) { @@ -875,7 +945,7 @@ public final class EntityCache { if (attr == null || node == null) { return (T[]) Creator.newArray(type, 0); } - T[] rms = this.list.stream().filter(node.createPredicate(this)).toArray(Creator.arrayFunction(type)); + T[] rms = this.list.stream().filter(node.createPredicate(this)).toArray(arrayer); for (T rs : rms) { attr.set(rs, fieldValue); } @@ -914,7 +984,7 @@ public final class EntityCache { if (flipper != null && flipper.getLimit() > 0) { stream = stream.limit(flipper.getLimit()); } - T[] rms = stream.filter(node.createPredicate(this)).toArray(Creator.arrayFunction(type)); + T[] rms = stream.filter(node.createPredicate(this)).toArray(arrayer); tableLock.lock(); //表锁, 可优化成行锁 try { for (T rs : rms) { diff --git a/src/main/java/org/redkale/source/EntityInfo.java b/src/main/java/org/redkale/source/EntityInfo.java index 55f2111a6..fb21b397f 100644 --- a/src/main/java/org/redkale/source/EntityInfo.java +++ b/src/main/java/org/redkale/source/EntityInfo.java @@ -5,11 +5,11 @@ */ package org.redkale.source; -import java.io.*; +import java.io.Serializable; import java.lang.reflect.*; import java.util.*; import java.util.concurrent.*; -import java.util.concurrent.locks.*; +import java.util.concurrent.locks.ReentrantLock; import java.util.function.*; import java.util.logging.*; import org.redkale.annotation.Comment; @@ -689,7 +689,10 @@ public final class EntityInfo { Cacheable c1 = type.getAnnotation(Cacheable.class); javax.persistence.Cacheable c2 = type.getAnnotation(javax.persistence.Cacheable.class); if (this.table == null || (!cacheForbidden && c1 != null && c1.value()) || (!cacheForbidden && c2 != null && c2.value())) { - this.cache = new EntityCache<>(this, c1 == null ? c2.interval() : c1.interval(), c1 == null ? c2.direct() : c1.direct()); + this.cache = new EntityCache<>(this, + c1 == null ? (c2 == null ? 0 : c2.interval()) : c1.interval(), + c1 == null ? (c2 == null ? false : c2.direct()) : c1.direct(), + c1 == null ? false : c1.continuousid()); } else { this.cache = null; }