diff --git a/src/com/wentch/redkale/source/DataDefaultSource.java b/src/com/wentch/redkale/source/DataDefaultSource.java index f473b0684..5f45861c6 100644 --- a/src/com/wentch/redkale/source/DataDefaultSource.java +++ b/src/com/wentch/redkale/source/DataDefaultSource.java @@ -609,17 +609,10 @@ public final class DataDefaultSource implements DataSource, Nameable { //------------------------------------ final EntityCache cache = info.getCache(); if (cache == null) return; - final Attribute attr = info.getPrimary(); - final Serializable[] keys2 = keys; - Serializable[] ids = cache.delete((T t) -> { - Serializable k = attr.get(t); - if (k == null) return false; - for (Serializable y : keys2) { - if (k.equals(y)) return true; - } - return false; - }); - if (cacheListener != null) cacheListener.deleteCache(info.getType(), ids); + for (Serializable key : keys) { + cache.delete(key); + } + if (cacheListener != null) cacheListener.deleteCache(info.getType(), keys); } catch (SQLException e) { throw new RuntimeException(e); } @@ -645,7 +638,7 @@ public final class DataDefaultSource implements DataSource, Nameable { delete((Connection) conn.getConnection(), loadEntityInfo(clazz), node); } - private void delete(final Connection conn, final EntityInfo info, FilterNode node) { + private void delete(final Connection conn, final EntityInfo info, final FilterNode node) { try { if (!info.isVirtualEntity()) { String sql = "DELETE FROM " + info.getTable() + node.createFilterSQLExpress(info, null); @@ -658,7 +651,7 @@ public final class DataDefaultSource implements DataSource, Nameable { //------------------------------------ final EntityCache cache = info.getCache(); if (cache == null) return; - Serializable[] ids = cache.delete(node.createFilterPredicate(info, null)); + Serializable[] ids = cache.delete(node); if (cacheListener != null) cacheListener.deleteCache(info.getType(), ids); } catch (SQLException e) { throw new RuntimeException(e); @@ -1088,9 +1081,8 @@ public final class DataDefaultSource implements DataSource, Nameable { if (node == null && bean != null) node = loadFilterBeanNode(bean.getClass()); final EntityCache cache = info.getCache(); if (cache != null && (info.isVirtualEntity() || cache.isFullLoaded())) { - Predicate filter = node == null ? null : node.createFilterPredicate(info, bean); if (node == null || node.isJoinAllCached()) { - return cache.getNumberResult(reckon, column == null ? null : info.getAttribute(column), filter); + return cache.getNumberResult(reckon, column, node, bean); } } final String sql = "SELECT " + reckon.getColumn((column == null || column.isEmpty() ? "*" : ("a." + column))) + " FROM " + info.getTable() + " a" @@ -1135,9 +1127,8 @@ public final class DataDefaultSource implements DataSource, Nameable { if (node == null && bean != null) node = loadFilterBeanNode(bean.getClass()); final EntityCache cache = info.getCache(); if (cache != null && (info.isVirtualEntity() || cache.isFullLoaded())) { - Predicate filter = node == null ? null : node.createFilterPredicate(info, bean); if (node == null || node.isJoinAllCached()) { - return cache.getMapResult(info.getAttribute(keyColumn), reckon, reckonColumn == null ? null : info.getAttribute(reckonColumn), filter); + return cache.getMapResult(keyColumn, reckon, reckonColumn, node, bean); } } final String sqlkey = info.getSQLColumn(keyColumn); @@ -1442,10 +1433,9 @@ public final class DataDefaultSource implements DataSource, Nameable { final EntityCache cache = info.getCache(); if (node == null && bean != null) node = loadFilterBeanNode(bean.getClass()); if (readcache && cache != null) { - Predicate filter = node == null ? null : node.createFilterPredicate(info, bean); if (node == null || node.isJoinAllCached()) { - if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(clazz.getSimpleName() + " cache query predicate = " + filter); - Sheet sheet = cache.querySheet(needtotal, selects, filter, flipper, FilterNode.createFilterComparator(info, flipper)); + if (debug.get() && info.isLoggable(Level.FINEST)) logger.finest(clazz.getSimpleName() + " cache query predicate = " + (node == null ? null : node.createFilterPredicate(info, bean))); + Sheet sheet = cache.querySheet(needtotal, selects, flipper, node, bean); if (!sheet.isEmpty() || info.isVirtualEntity() || cache.isFullLoaded()) return sheet; } } diff --git a/src/com/wentch/redkale/source/EntityCache.java b/src/com/wentch/redkale/source/EntityCache.java index 2bab3b937..1baf51ec1 100644 --- a/src/com/wentch/redkale/source/EntityCache.java +++ b/src/com/wentch/redkale/source/EntityCache.java @@ -21,15 +21,90 @@ import java.util.stream.*; /** * * @author zhangjx + * @param */ public final class EntityCache { + private static class UniqueSequence implements Serializable { + + private final Serializable[] value; + + public UniqueSequence(Serializable[] val) { + this.value = val; + } + + @Override + public int hashCode() { + return Arrays.deepHashCode(this.value); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + final UniqueSequence other = (UniqueSequence) obj; + if (value.length != other.value.length) return false; + for (int i = 0; i < value.length; i++) { + if (!value[i].equals(other.value[i])) return false; + } + return true; + } + + } + + private static interface UniqueAttribute extends Predicate { + + public Serializable getValue(T bean); + + @Override + public boolean test(FilterNode node); + + public static UniqueAttribute create(final Attribute[] attributes) { + if (attributes.length == 1) { + final Attribute attribute = attributes[0]; + return new UniqueAttribute() { + + @Override + public Serializable getValue(T bean) { + return attribute.get(bean); + } + + @Override + public boolean test(FilterNode node) { + return true; + } + }; + } else { + return new UniqueAttribute() { + + @Override + public Serializable getValue(T bean) { + final Serializable[] rs = new Serializable[attributes.length]; + for (int i = 0; i < rs.length; i++) { + rs[i] = attributes[i].get(bean); + } + return new UniqueSequence(rs); + } + + @Override + public boolean test(FilterNode node) { + return true; + } + }; + } + } + } + private static final Logger logger = Logger.getLogger(EntityCache.class.getName()); private final ConcurrentHashMap map = new ConcurrentHashMap(); private final CopyOnWriteArrayList list = new CopyOnWriteArrayList(); // CopyOnWriteArrayList 插入慢、查询快; 10w数据插入需要3.2秒; ConcurrentLinkedQueue 插入快、查询慢;10w数据查询需要 0.062秒, 查询慢40%; + private final HashMap, ConcurrentHashMap>> uniques = new HashMap<>(); + + private final Map> sortComparators = new ConcurrentHashMap<>(); + private final Class type; private final boolean needcopy; @@ -38,19 +113,17 @@ public final class EntityCache { private final Attribute primary; - //key是field的name - private final Map> attributes; - private final Reproduce reproduce; private boolean fullloaded; - public EntityCache(final Class type, Creator creator, Attribute primary, - Map> attributes) { - this.type = type; - this.creator = creator; - this.primary = primary; - this.attributes = attributes; + final EntityInfo info; + + public EntityCache(final EntityInfo info) { + this.info = info; + this.type = info.getType(); + this.creator = info.getCreator(); + this.primary = info.primary; this.needcopy = true; this.reproduce = Reproduce.create(type, type, (m) -> { char[] mn = m.getName().substring(3).toCharArray(); @@ -62,12 +135,23 @@ public final class EntityCache { return false; } }); + for (Unique unique : type.getAnnotationsByType(Unique.class)) { + final Attribute[] attrs = new Attribute[unique.columns().length]; + for (int i = 0; i < attrs.length; i++) { + attrs[i] = info.getAttribute(unique.columns()[i]); + } + this.uniques.put(UniqueAttribute.create(attrs), new ConcurrentHashMap<>()); + } } public void fullLoad(List all) { if (all == null) return; clear(); - all.stream().filter(x -> x != null).forEach(x -> this.map.put(this.primary.get(x), x)); + final HashMap, ConcurrentHashMap>> localUniques = this.uniques; + all.stream().filter(x -> x != null).forEach(x -> { + this.map.put(this.primary.get(x), x); + localUniques.forEach((k, v) -> v.computeIfAbsent(k.getValue(x), (c) -> new ConcurrentLinkedQueue<>()).add(x)); + }); this.list.addAll(all); this.fullloaded = true; } @@ -79,6 +163,7 @@ public final class EntityCache { public void clear() { this.fullloaded = false; this.list.clear(); + this.uniques.values().forEach(x -> x.clear()); this.map.clear(); } @@ -92,17 +177,14 @@ public final class EntityCache { return rs == null ? null : (needcopy ? reproduce.copy(this.creator.create(), rs) : rs); } - public T find(final Predicate filter) { - if (filter == null) return null; - Optional rs = listStream().filter(filter).findFirst(); - return rs.isPresent() ? (needcopy ? reproduce.copy(this.creator.create(), rs.get()) : rs.get()) : null; - } - public boolean exists(final Predicate filter) { return (filter != null) && listStream().filter(filter).findFirst().isPresent(); } - public Map getMapResult(final Attribute keyAttr, final Reckon reckon, final Attribute reckonAttr, final Predicate filter) { + public Map getMapResult(final String keyColumn, final Reckon reckon, final String reckonColumn, final FilterNode node, final FilterBean bean) { + final Attribute keyAttr = info.getAttribute(keyColumn); + final Predicate filter = node == null ? null : node.createFilterPredicate(this.info, bean); + final Attribute reckonAttr = reckonColumn == null ? null : info.getAttribute(reckonColumn); Stream stream = listStream(); if (filter != null) stream = stream.filter(filter); Collector collector = null; @@ -148,7 +230,9 @@ public final class EntityCache { return rs; } - public Number getNumberResult(final Reckon reckon, final Attribute attr, final Predicate filter) { + public Number getNumberResult(final Reckon reckon, final String column, final FilterNode node, final FilterBean bean) { + final Attribute attr = column == null ? null : info.getAttribute(column); + final Predicate filter = node == null ? null : node.createFilterPredicate(this.info, bean); Stream stream = listStream(); if (filter != null) stream = stream.filter(filter); switch (reckon) { @@ -213,48 +297,13 @@ public final class EntityCache { return -1; } - public Set querySet(final SelectColumn selects, final Predicate filter, final Comparator sort) { - return (Set) queryCollection(true, selects, filter, sort); + public Sheet querySheet(final SelectColumn selects, final Flipper flipper, final FilterNode node, final FilterBean bean) { + return querySheet(true, selects, flipper, node, bean); } - public List queryList(final SelectColumn selects, final Predicate filter, final Comparator sort) { - return (List) queryCollection(false, selects, filter, sort); - } - - public Collection queryCollection(final boolean set, final SelectColumn selects, final Predicate filter, final Comparator sort) { - final Collection rs = set ? new LinkedHashSet<>() : new ArrayList<>(); - Stream stream = listStream(); - if (filter != null) stream = stream.filter(filter); - if (sort != null) stream = stream.sorted(sort); - if (selects == null) { - Consumer action = x -> rs.add(needcopy ? reproduce.copy(creator.create(), x) : x); - if (sort != null) { - stream.forEachOrdered(action); - } else { - stream.forEach(action); - } - } else { - final List> attrs = new ArrayList<>(); - for (Map.Entry> en : this.attributes.entrySet()) { - if (selects.validate(en.getKey())) attrs.add(en.getValue()); - } - Consumer action = x -> { - final T item = creator.create(); - for (Attribute attr : attrs) { - attr.set(item, attr.get(x)); - } - rs.add(item); - }; - if (sort != null) { - stream.forEachOrdered(action); - } else { - stream.forEach(action); - } - } - return rs; - } - - public Sheet querySheet(final boolean needtotal, final SelectColumn selects, final Predicate filter, final Flipper flipper, final Comparator sort) { + public Sheet querySheet(final boolean needtotal, final SelectColumn selects, final Flipper flipper, final FilterNode node, final FilterBean bean) { + final Predicate filter = node == null ? null : node.createFilterPredicate(this.info, bean); + final Comparator comparator = FilterNode.createFilterComparator(this, flipper); long total = 0; if (needtotal) { Stream stream = listStream(); @@ -264,19 +313,19 @@ public final class EntityCache { if (needtotal && total == 0) return new Sheet<>(); Stream stream = listStream(); if (filter != null) stream = stream.filter(filter); - if (sort != null) stream = stream.sorted(sort); + if (comparator != null) stream = stream.sorted(comparator); if (flipper != null) stream = stream.skip(flipper.index()).limit(flipper.getSize()); final List rs = new ArrayList<>(); if (selects == null) { Consumer action = x -> rs.add(needcopy ? reproduce.copy(creator.create(), x) : x); - if (sort != null) { + if (comparator != null) { stream.forEachOrdered(action); } else { stream.forEach(action); } } else { final List> attrs = new ArrayList<>(); - for (Map.Entry> en : this.attributes.entrySet()) { + for (Map.Entry> en : info.attributes.entrySet()) { if (selects.validate(en.getKey())) attrs.add(en.getValue()); } Consumer action = x -> { @@ -286,7 +335,7 @@ public final class EntityCache { } rs.add(item); }; - if (sort != null) { + if (comparator != null) { stream.forEachOrdered(action); } else { stream.forEach(action); @@ -298,10 +347,11 @@ public final class EntityCache { public void insert(T value) { if (value == null) return; - T rs = reproduce.copy(this.creator.create(), value); //确保同一主键值的map与list中的对象必须共用。 + final T rs = reproduce.copy(this.creator.create(), value); //确保同一主键值的map与list中的对象必须共用。 T old = this.map.put(this.primary.get(rs), rs); if (old == null) { this.list.add(rs); + this.uniques.forEach((k, v) -> v.computeIfAbsent(k.getValue(rs), (c) -> new ConcurrentLinkedQueue<>()).add(rs)); } else { logger.log(Level.WARNING, "cache repeat insert data: " + value); } @@ -309,20 +359,28 @@ public final class EntityCache { public void delete(final Serializable id) { if (id == null) return; - T rs = this.map.remove(id); + final T rs = this.map.remove(id); if (rs != null) this.list.remove(rs); + this.uniques.forEach((k, v) -> v.computeIfPresent(k.getValue(rs), (x, u) -> { + u.remove(rs); + return u; + })); } - public Serializable[] delete(final Predicate filter) { - if (filter == null || this.list.isEmpty()) return new Serializable[0]; - Object[] rms = listStream().filter(filter).toArray(); + public Serializable[] delete(final FilterNode node) { + if (node == null || this.list.isEmpty()) return new Serializable[0]; + Object[] rms = listStream().filter(node.createFilterPredicate(this.info, null)).toArray(); Serializable[] ids = new Serializable[rms.length]; int i = -1; for (Object o : rms) { - T t = (T) o; + final T t = (T) o; ids[++i] = this.primary.get(t); this.map.remove(ids[i]); this.list.remove(t); + this.uniques.forEach((k, v) -> v.computeIfPresent(k.getValue(t), (x, u) -> { + u.remove(t); + return u; + })); } return ids; } @@ -396,4 +454,12 @@ public final class EntityCache { private Stream listStream() { return this.list.stream(); } + + protected Comparator getSortComparator(String sort) { + return this.sortComparators.get(sort); + } + + protected void putSortComparator(String sort, Comparator comparator) { + this.sortComparators.put(sort, comparator); + } } diff --git a/src/com/wentch/redkale/source/EntityInfo.java b/src/com/wentch/redkale/source/EntityInfo.java index d2e9b5549..6370abde8 100644 --- a/src/com/wentch/redkale/source/EntityInfo.java +++ b/src/com/wentch/redkale/source/EntityInfo.java @@ -13,7 +13,7 @@ import java.sql.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Function; +import java.util.function.*; import java.util.logging.*; import javax.persistence.*; @@ -38,18 +38,18 @@ public final class EntityInfo { private final Creator creator; //主键 - private final Attribute primary; + final Attribute primary; private final EntityCache cache; + //key是field的name, 不是sql字段。 + //存放所有与数据库对应的字段, 包括主键 + final Map> attributes = new HashMap<>(); + //key是field的name, value是Column的别名,即数据库表的字段名 //只有field.name 与 Column.name不同才存放在aliasmap里. private final Map aliasmap; - //key是field的name, 不是sql字段。 - //存放所有与数据库对应的字段, 包括主键 - private final Map> attributes = new HashMap<>(); - private final Map> updateAttributeMap = new HashMap<>(); final String querySQL; @@ -70,8 +70,6 @@ public final class EntityInfo { private final Map sortOrderbySqls = new ConcurrentHashMap<>(); - private final Map> sortComparators = new ConcurrentHashMap<>(); - //---------------------计算主键值---------------------------- private final int nodeid; @@ -225,7 +223,7 @@ public final class EntityInfo { //----------------cache-------------- Cacheable c = type.getAnnotation(Cacheable.class); if (this.table == null || (!cacheForbidden && c != null && c.value())) { - this.cache = new EntityCache<>(type, creator, primary, attributes); + this.cache = new EntityCache<>(this); } else { this.cache = null; } @@ -289,14 +287,6 @@ public final class EntityInfo { this.sortOrderbySqls.put(sort, sql); } - public Comparator getSortComparator(String sort) { - return this.sortComparators.get(sort); - } - - protected void putSortComparator(String sort, Comparator comparator) { - this.sortComparators.put(sort, comparator); - } - //根据field字段名获取数据库对应的字段名 public String getSQLColumn(String fieldname) { return this.aliasmap == null ? fieldname : aliasmap.getOrDefault(fieldname, fieldname); diff --git a/src/com/wentch/redkale/source/FilterBeanNode.java b/src/com/wentch/redkale/source/FilterBeanNode.java index 0f20effbc..7bd582e6e 100644 --- a/src/com/wentch/redkale/source/FilterBeanNode.java +++ b/src/com/wentch/redkale/source/FilterBeanNode.java @@ -239,10 +239,6 @@ final class FilterBeanNode extends FilterNode { @Override protected Predicate createFilterPredicate(final EntityInfo info, FilterBean bean) { - return createFilterPredicate(true, info, bean); - } - - private Predicate createFilterPredicate(final boolean first, final EntityInfo info, FilterBean bean) { //if ((this.joinSQL == null && first) || this.foreignEntity == null) return super.createFilterPredicate(info, bean); if (this.foreignEntity == null) return super.createFilterPredicate(info, bean); final Map foreign = new HashMap<>(); @@ -252,7 +248,7 @@ final class FilterBeanNode extends FilterNode { for (FilterNode n : this.nodes) { FilterBeanNode node = (FilterBeanNode) n; if (node.foreignEntity == null) { - Predicate f = node.createFilterPredicate(false, info, bean); + Predicate f = node.createFilterPredicate(info, bean); if (f == null) continue; final Predicate one = result; final Predicate two = f; diff --git a/src/com/wentch/redkale/source/FilterNode.java b/src/com/wentch/redkale/source/FilterNode.java index 61c6a16de..f56a1a63d 100644 --- a/src/com/wentch/redkale/source/FilterNode.java +++ b/src/com/wentch/redkale/source/FilterNode.java @@ -741,10 +741,10 @@ public class FilterNode { return null; } - protected static Comparator createFilterComparator(EntityInfo info, Flipper flipper) { + protected static Comparator createFilterComparator(EntityCache cache, Flipper flipper) { if (flipper == null || flipper.getSort() == null || flipper.getSort().isEmpty()) return null; final String sort = flipper.getSort(); - Comparator comparator = info.getSortComparator(sort); + Comparator comparator = cache.getSortComparator(sort); if (comparator != null) return comparator; for (String item : sort.split(",")) { if (item.trim().isEmpty()) continue; @@ -752,10 +752,10 @@ public class FilterNode { int pos = sub[0].indexOf('('); Attribute attr; if (pos <= 0) { - attr = info.getAttribute(sub[0]); + attr = cache.info.getAttribute(sub[0]); } else { //含SQL函数 int pos2 = sub[0].lastIndexOf(')'); - final Attribute pattr = info.getAttribute(sub[0].substring(pos + 1, pos2)); + final Attribute pattr = cache.info.getAttribute(sub[0].substring(pos + 1, pos2)); final String func = sub[0].substring(0, pos); if ("ABS".equalsIgnoreCase(func)) { if (pattr.type() == int.class || pattr.type() == Integer.class) { @@ -895,7 +895,7 @@ public class FilterNode { comparator = comparator.thenComparing(c); } } - info.putSortComparator(sort, comparator); + cache.putSortComparator(sort, comparator); return comparator; } diff --git a/src/com/wentch/redkale/source/Unique.java b/src/com/wentch/redkale/source/Unique.java new file mode 100644 index 000000000..fca52f4e0 --- /dev/null +++ b/src/com/wentch/redkale/source/Unique.java @@ -0,0 +1,24 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.wentch.redkale.source; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({FIELD}) +@Retention(RUNTIME) +@Repeatable(Uniques.class) +public @interface Unique { + + String[] columns(); +} diff --git a/src/com/wentch/redkale/source/Uniques.java b/src/com/wentch/redkale/source/Uniques.java new file mode 100644 index 000000000..f1397e63f --- /dev/null +++ b/src/com/wentch/redkale/source/Uniques.java @@ -0,0 +1,23 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.wentch.redkale.source; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({FIELD}) +@Retention(RUNTIME) +public @interface Uniques { + + Unique[] value(); +}