package jane.core; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.Lock; import jane.core.SContext.Record; import jane.core.SContext.Safe; import jane.core.Storage.Helper; import jane.core.Storage.WalkHandler; /** * 通用key类型的数据库表类 */ public final class Table<K, V extends Bean<V>, S extends Safe<V>> extends TableBase<V> { private final Storage.Table<K, V> _stoTable; // 存储引擎的表对象 private final Map<K, V> _cache; // 读缓存. 有大小限制,溢出自动清理 private final ConcurrentMap<K, V> _cacheMod; // 写缓存. 不会溢出,保存到数据库存储引擎后清理 /** * 创建一个数据库表 * @param tableName 表名 * @param stoTable 存储引擎的表对象. null表示此表是内存表 * @param lockName 此表关联的锁名 * @param cacheSize 此表的读缓存记录数量上限. 如果是内存表则表示超过此上限则会自动丢弃 * @param stubV 记录value的存根对象,不要用于记录有用的数据. 这里只用于标记删除的字段,如果为null则表示此表是内存表 */ Table(int tableId, String tableName, Storage.Table<K, V> stoTable, String lockName, int cacheSize, V stubV) { super(tableId, tableName, stubV, (lockName != null && !(lockName = lockName.trim()).isEmpty() ? lockName.hashCode() : tableId) * 0x9e3779b1); _stoTable = stoTable; if(cacheSize < 1) cacheSize = 1; _cache = Util.newConcurrentLRUMap(cacheSize, tableName); _cacheMod = (stoTable != null ? Util.<K, V>newConcurrentHashMap() : null); if(stoTable != null) _tables.add(this); } /** * 根据记录的key获取锁的ID(lockId) * <p> * 用于事务的加锁({@link Procedure#lock}) */ public int lockId(K k) { return _lockId ^ k.hashCode(); } /** * 尝试依次加锁并保存此表已修改的记录 * <p> * @param counts 长度必须>=3,用于保存3个统计值,分别是保存前所有修改的记录数,保存后的剩余记录数,保存的记录数 */ @Override protected void trySaveModified(long[] counts) { counts[0] += _cacheMod.size(); long n = 0; try { for(K k : _cacheMod.keySet()) { Lock lock = Procedure.tryLock(lockId(k)); if(lock != null) { try { ++n; V v = _cacheMod.get(k); if(v == _deleted) _stoTable.remove(k); else { v.setSaveState(1); _stoTable.put(k, v); } _cacheMod.remove(k, v); } finally { lock.unlock(); } } } } finally { counts[1] += _cacheMod.size(); counts[2] += n; } } /** * 在所有事务暂停的情况下直接依次保存此表已修改的记录 */ @Override protected int saveModified() { for(Entry<K, V> e : _cacheMod.entrySet()) { K k = e.getKey(); V v = e.getValue(); if(v == _deleted) _stoTable.remove(k); else { v.setSaveState(1); _stoTable.put(k, v); } } int m = _cacheMod.size(); _cacheMod.clear(); return m; } /** * 获取读缓存记录数 */ @Override public int getCacheSize() { return _cache.size(); } /** * 获取写缓存记录数 */ @Override public int getCacheModSize() { return _cacheMod.size(); } /** * 根据记录的key获取value * <p> * 会自动添加到读cache中<br> * 必须在事务中已加锁的状态下调用此方法 */ @Deprecated public V getUnsafe(K k) { _readCount.incrementAndGet(); V v = _cache.get(k); if(v != null) return v; if(_cacheMod == null) return null; v = _cacheMod.get(k); if(v != null) { if(v == _deleted) return null; _cache.put(k, v); return v; } _readStoCount.incrementAndGet(); v = _stoTable.get(k); if(v != null) { v.setSaveState(1); _cache.put(k, v); } return v; } /** * 同getUnsafe,但同时设置修改标记 */ @Deprecated public V getModified(K k) { V v = getUnsafe(k); if(v != null) modify(k, v); return v; } /** * 同getUnsafe,但增加的安全封装,可回滚修改,但没有加锁检查 */ public S getNoLock(K k) { V v = getUnsafe(k); SContext sctx = SContext.current(); return v != null ? sctx.addRecord(this, k, v) : sctx.getRecord(this, k); } /** * 同getNoLock,但有加锁检查 */ public S get(K k) { if(!Procedure.isLockedByCurrentThread(lockId(k))) throw new IllegalAccessError("get unlocked record! table=" + _tableName + ",key=" + k); return getNoLock(k); } /** * 追加一个锁并获取其字段. 有可能因重锁而导致有记录被其它事务修改而抛出Redo异常 */ public S lockGet(K k) throws InterruptedException { Procedure proc = Procedure.getCurProcedure(); if(proc == null) throw new IllegalStateException("invalid lockGet out of procedure"); proc.appendLock(lockId(k)); return getNoLock(k); } /** * 根据记录的key获取value * <p> * 不会自动添加到读cache中<br> * 必须在事务中已加锁的状态下调用此方法<br> * <b>注意</b>: 不能在同一事务里使用NoCache方式(或混合Cache方式)get同一个记录多次并且对这些记录有多次修改,否则会触发modify函数中的异常 */ @Deprecated public V getNoCacheUnsafe(K k) { _readCount.incrementAndGet(); V v = _cache.get(k); if(v != null) return v; if(_cacheMod == null) return null; v = _cacheMod.get(k); if(v != null) return v != _deleted ? v : null; _readStoCount.incrementAndGet(); return _stoTable.get(k); } /** * 同getNoCacheUnsafe,但增加的安全封装,可回滚修改<br> * <b>注意</b>: 不能在同一事务里使用NoCache方式(或混合Cache方式)get同一个记录多次并且对这些记录有多次修改,否则会触发modify函数中的异常 */ public S getNoCache(K k) { V v = getNoCacheUnsafe(k); SContext sctx = SContext.current(); return v != null ? sctx.addRecord(this, k, v) : sctx.getRecord(this, k); } /** * 根据记录的key获取value * <p> * 只在读和写cache中获取<br> * 必须在事务中已加锁的状态下调用此方法 */ @Deprecated public V getCacheUnsafe(K k) { _readCount.incrementAndGet(); V v = _cache.get(k); if(v != null) return v; if(_cacheMod == null) return null; v = _cacheMod.get(k); return v != null && v != _deleted ? v : null; } /** * 同getCacheUnsafe,但增加的安全封装,可回滚修改 */ public S getCache(K k) { V v = getCacheUnsafe(k); SContext sctx = SContext.current(); return v != null ? sctx.addRecord(this, k, v) : sctx.getRecord(this, k); } /** * 标记记录已修改的状态 * <p> * 必须在事务中已加锁的状态下调用此方法 * @param v 必须是get获取到的对象引用. 如果不是,则应该调用put方法 */ public void modify(K k, V v) { Procedure.incVersion(lockId(k)); if(!v.modified() && _cacheMod != null) { V vOld = _cacheMod.put(k, v); if(vOld == null) { v.setSaveState(2); DBManager.instance().incModCount(); } else if(vOld != v) { _cacheMod.put(k, vOld); throw new IllegalStateException("modify unmatched record: t=" + _tableName + ",k=" + k + ",vOld=" + vOld + ",v=" + v); } } } @SuppressWarnings("unchecked") void modify(Object ko, Object vo) { K k = (K)ko; V v = (V)vo; Procedure.incVersion(lockId(k)); if(!v.modified() && _cacheMod != null) { V vOld = _cacheMod.put(k, v); if(vOld == null) { v.setSaveState(2); DBManager.instance().incModCount(); } else if(vOld != v) { // 可能之前已经覆盖或删除过记录,然后再modify的话,就忽略本次modify了,因为SContext.commit无法识别这种情况 _cacheMod.put(k, vOld); } } } /** * 根据记录的key保存value * <p> * 必须在事务中已加锁的状态下调用此方法 * @param v 如果是get获取到的对象引用,可调用modify来提高性能 */ @Deprecated public void putUnsafe(K k, V v) { V vOld = _cache.put(k, v); if(vOld == v) modify(k, v); else { Procedure.incVersion(lockId(k)); if(!v.stored()) { if(_cacheMod != null) { vOld = _cacheMod.put(k, v); if(vOld == null) { v.setSaveState(2); DBManager.instance().incModCount(); } } } else { if(vOld != null) _cache.put(k, vOld); else _cache.remove(k); throw new IllegalStateException("put shared record: t=" + _tableName + ",k=" + k + ",vOld=" + vOld + ",v=" + v); } } } /** * 同putUnsafe,但增加的安全封装,可回滚修改 */ public void put(final K k, V v) { final V vOld = getNoCacheUnsafe(k); if(vOld == v) return; if(v.stored()) throw new IllegalStateException("put shared record: t=" + _tableName + ",k=" + k + ",v=" + v); if(!Procedure.isLockedByCurrentThread(lockId(k))) throw new IllegalAccessError("put unlocked record! table=" + _tableName + ",key=" + k); SContext.current().addOnRollbackDirty(new Runnable() { @Override public void run() { if(vOld != null) { vOld.setSaveState(0); putUnsafe(k, vOld); } else removeUnsafe(k); } }); putUnsafe(k, v); } @SuppressWarnings("deprecation") public void put(K k, S s) { put(k, s.unsafe()); s.record(new Record<>(this, k, s)); } /** * 根据记录的key删除记录 * <p> * 必须在事务中已加锁的状态下调用此方法 */ @Deprecated public void removeUnsafe(K k) { Procedure.incVersion(lockId(k)); _cache.remove(k); if(_cacheMod != null && _cacheMod.put(k, _deleted) == null) DBManager.instance().incModCount(); } /** * 同removeUnsafe,但增加的安全封装,可回滚修改 */ public void remove(final K k) { final V vOld = getNoCacheUnsafe(k); if(vOld == null) return; if(!Procedure.isLockedByCurrentThread(lockId(k))) throw new IllegalAccessError("remove unlocked record! table=" + _tableName + ",key=" + k); SContext.current().addOnRollbackDirty(new Runnable() { @Override public void run() { vOld.setSaveState(0); putUnsafe(k, vOld); } }); removeUnsafe(k); } /** * 只在读cache中遍历此表的所有记录 * <p> * 遍历时注意先根据记录的key获取锁再调用get获得其value, 必须在事务中调用此方法<br> * 注意此遍历方法是无序的 * @param handler 遍历过程中返回false可中断遍历 */ public boolean walkCache(WalkHandler<K> handler) { for(K k : _cache.keySet()) if(!Helper.onWalkSafe(handler, k)) return false; return true; } /** * 按记录key的顺序遍历此表的所有记录 * <p> * 遍历时注意先根据记录的key获取锁再调用get获得其value(取锁操作必须在事务中)<br> * 注意: 遍历的key列表仅从数据库层获取,当前没有checkpoint的cache记录会被无视,所以get获取到的key可能不是最新,而且得到的value有可能为null * @param handler 遍历过程中返回false可中断遍历 * @param from 需要遍历的最小key. null表示最小值 * @param to 需要遍历的最大key. null表示最大值 * @param inclusive 遍历是否包含from和to的key * @param reverse 是否按反序遍历 */ public boolean walk(WalkHandler<K> handler, K from, K to, boolean inclusive, boolean reverse) { return _stoTable != null ? _stoTable.walk(handler, from, to, inclusive, reverse) : walkCache(handler); } public boolean walk(WalkHandler<K> handler, boolean reverse) { return walk(handler, null, null, true, reverse); } public boolean walk(WalkHandler<K> handler) { return walk(handler, null, null, true, false); } }