package jane.core; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Lock; import jane.core.SContext.RecordLong; import jane.core.SContext.Safe; import jane.core.Storage.Helper; import jane.core.Storage.WalkHandlerLong; import jane.core.map.LongConcurrentHashMap; import jane.core.map.LongMap; import jane.core.map.LongMap.LongIterator; import jane.core.map.LongMap.MapIterator; /** * 使用ID类型作为key的数据库表类 * <p> * ID类型即>=0的long类型, 会比使用Long类型作为key的通用表效率高,且支持自增长ID(从1开始)<br> * <b>注意</b>: 一个表要事先确定插入记录是只使用自增长ID还是只指定ID插入,如果都使用则会导致ID冲突 */ public final class TableLong<V extends Bean<V>, S extends Safe<V>> extends TableBase<V> { private final Storage.TableLong<V> _stoTable; // 存储引擎的表对象 private final LongMap<V> _cache; // 读缓存. 有大小限制,溢出自动清理 private final LongMap<V> _cacheMod; // 写缓存. 不会溢出,保存到数据库存储引擎后清理 private final AtomicLong _idCounter = new AtomicLong(); // 用于自增长ID的统计器, 当前值表示当前表已存在的最大ID值 private final AtomicBoolean _idCounterMod = new AtomicBoolean(); // idCounter是否待存状态(有修改未存库) private int _autoIdBegin = Const.autoIdBegin; // 自增长ID的初始值, 可运行时指定 private int _autoIdStride = Const.autoIdStride; // 自增长ID的分配跨度, 可运行时指定 /** * 创建一个数据库表 * @param tableName 表名 * @param stoTable 存储引擎的表对象. null表示此表是内存表 * @param lockName 此表关联的锁名 * @param cacheSize 此表的读缓存记录数量上限. 如果是内存表则表示超过此上限则会自动丢弃 * @param stubV 记录value的存根对象,不要用于记录有用的数据. 这里只用于标记删除的字段,如果为null则表示此表是内存表 */ TableLong(int tableId, String tableName, Storage.TableLong<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.newLongConcurrentLRUMap(cacheSize, tableName); _cacheMod = (stoTable != null ? new LongConcurrentHashMap<V>() : null); if(stoTable != null) { _idCounter.set(_stoTable.getIdCounter()); _tables.add(this); } } /** * 指定表的自增长ID参数 * <p> * 表的自增长参数默认由配置决定<br> * 每个表的自增长参数应该保证始终不变,否则可能因记录ID冲突而导致记录覆盖,所以此方法只适合在初始化表后立即调用一次 * @param begin 自增长ID的初始值. 范围:[1,] * @param stride 自增长ID的分配跨度. 范围:[1,] */ public void setAutoId(int begin, int stride) { if(begin < 1) begin = 1; if(stride < 1) stride = 1; _autoIdBegin = begin; _autoIdStride = stride; } public int getAutoIdBegin() { return _autoIdBegin; } public int getAutoIdStride() { return _autoIdStride; } /** * 根据记录的key获取锁的ID(lockId) * <p> * 用于事务的加锁({@link Procedure#lock}) */ public int lockId(long k) { return _lockId ^ (int)k ^ (int)(k >> 32); } /** * 尝试依次加锁并保存此表已修改的记录 * <p> * @param counts 长度必须>=3,用于保存3个统计值,分别是保存前所有修改的记录数,保存后的剩余记录数,保存的记录数 */ @Override protected void trySaveModified(long[] counts) { counts[0] += _cacheMod.size(); long n = 0; try { for(LongIterator it = _cacheMod.keyIterator(); it.hasNext();) { long k = it.next(); 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(MapIterator<V> it = _cacheMod.entryIterator(); it.moveToNext();) { long k = it.key(); V v = it.value(); if(v == _deleted) _stoTable.remove(k); else { v.setSaveState(1); _stoTable.put(k, v); } } int m = _cacheMod.size(); _cacheMod.clear(); _stoTable.setIdCounter(_idCounter.get()); _idCounterMod.set(false); 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(long 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(long k) { V v = getUnsafe(k); if(v != null) modify(k, v); return v; } /** * 同getUnsafe,但增加的安全封装,可回滚修改,但没有加锁检查 */ public S getNoLock(long 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(long k) { if(!Procedure.isLockedByCurrentThread(lockId(k))) throw new IllegalAccessError("get unlocked record! table=" + _tableName + ",key=" + k); return getNoLock(k); } /** * 追加一个锁并获取其字段. 有可能因重锁而导致有记录被其它事务修改而抛出Redo异常 */ public S lockGet(long 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(long 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(long 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(long 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(long 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(long 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(long k, Object vo) { 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> * 必须在事务中已加锁的状态下调用此方法<br> * 如果使用自增长ID来插入记录的表,则不能用此方法来插入新的记录 * @param v 如果是get获取到的对象引用,可调用modify来提高性能 */ @Deprecated public void putUnsafe(long 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 long 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(long k, S s) { put(k, s.unsafe()); s.record(new RecordLong<>(this, k, s)); } /** * 分配自增长的新ID值作为key * <p> * 必须在事务中调用此方法<br> * 自增长ID的分配规则由配置的autoIdBegin和autoIdStride决定,也可以通过setAutoId方法来指定<br> * 如果此表的记录有不是使用此方法插入的,请谨慎使用此方法,可能因记录ID冲突而导致分配性能降低 * @return 返回插入的自增长ID值 */ public long allocId() { if(_idCounterMod.compareAndSet(false, true)) DBManager.instance().incModCount(); for(;;) { long k = _idCounter.getAndIncrement() * _autoIdStride + _autoIdBegin; if(getNoCacheUnsafe(k) == null) return k; } } /** * 获取分配自增长ID的当前计数器值(用于下一次分配) */ public long getIdCounter() { return _idCounter.get(); } /** * 设置分配自增长ID的当前计数器值(用于下一次分配) * <p> * 警告: 应在知道此方法意义的情况下谨慎调用. */ public void setIdCounter(long idCounter) { _idCounter.set(idCounter); } /** * 根据记录的key删除记录 * <p> * 必须在事务中已加锁的状态下调用此方法 */ @Deprecated public void removeUnsafe(long k) { Procedure.incVersion(lockId(k)); _cache.remove(k); if(_cacheMod != null && _cacheMod.put(k, _deleted) == null) DBManager.instance().incModCount(); } /** * 同removeUnsafe,但增加的安全封装,可回滚修改 */ public void remove(final long 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(WalkHandlerLong handler) { for(LongIterator it = _cache.keyIterator(); it.hasNext();) if(!Helper.onWalkSafe(handler, it.next())) 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(WalkHandlerLong handler, long from, long to, boolean inclusive, boolean reverse) { return _stoTable != null ? _stoTable.walk(handler, from, to, inclusive, reverse) : walkCache(handler); } public boolean walk(WalkHandlerLong handler, boolean reverse) { return walk(handler, 0, Long.MAX_VALUE, true, reverse); } public boolean walk(WalkHandlerLong handler) { return walk(handler, 0, Long.MAX_VALUE, true, false); } }