From 6b8be01b286a0f9f882d95c01d5a54220b44eafb Mon Sep 17 00:00:00 2001 From: Andrew Golovashevich Date: Wed, 26 Mar 2025 21:02:14 +0300 Subject: [PATCH] Cache based on checking all weak references --- ...redStatementsCompilationLazyWeakCache.java | 115 ++++++++++++++++++ .../_SynchronizedEmbedKeyHashMap.java | 29 +++++ 2 files changed, 144 insertions(+) create mode 100644 src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationLazyWeakCache.java diff --git a/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationLazyWeakCache.java b/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationLazyWeakCache.java new file mode 100644 index 0000000..fa4c936 --- /dev/null +++ b/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/PreparedStatementsCompilationLazyWeakCache.java @@ -0,0 +1,115 @@ +package ru.langrafhomyak.db.jdbc_resources_manager; + +import org.intellij.lang.annotations.Language; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.lang.ref.WeakReference; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.function.Predicate; + +import static java.lang.System.identityHashCode; + +public class PreparedStatementsCompilationLazyWeakCache implements PreparedStatementsCompilationCache { + @NotNull + private final String _statementSource; + @NotNull + private final _SynchronizedEmbedKeyHashMap _map; + @NotNull + private final CleanupFunction _cleanupFn; + + public PreparedStatementsCompilationLazyWeakCache(@NotNull @Language("SQL") String statementSource) { + this._statementSource = statementSource; + this._map = new _SynchronizedEmbedKeyHashMap<>(); + this._cleanupFn = new CleanupFunction(); + } + + @Override + @NotNull + public PreparedStatement preparedStatementForConnection(@NotNull Connection connection) throws SQLException { + final int key = identityHashCode(connection); + ConnectionsMapNode node = this._map.get(key, connection); + if (node == null) { + /* + Assuming that user will clever enough to not use the same connection in different threads + and there wouldn't be a race condition for adding instance of this connection to map. + Anyway, technically there is no error if the same connection is added twice. + */ + this._map.cleanup(this._cleanupFn); + node = new ConnectionsMapNode(this, connection); + this._map.add(node); + } + return node.createWrapper(); + } + + private static class CleanupFunction implements Predicate { + @Override + public boolean test(ConnectionsMapNode node) { + return node._connectionRef.get() == null; + } + } + + private static class ConnectionsMapNode extends _SynchronizedEmbedKeyHashMap.Node { + @NotNull + private final WeakReference _connectionRef; + @NotNull + private final WeakReference _statement; + @Nullable + private PreparedStatement _wrapper; + @NotNull + private final Object _wrapperSync; + + public ConnectionsMapNode(@NotNull PreparedStatementsCompilationLazyWeakCache owner, @NotNull Connection connection) throws SQLException { + super(identityHashCode(connection)); + this._connectionRef = new WeakReference<>(connection); + this._statement = new WeakReference<>(connection.prepareStatement(owner._statementSource)); + this._wrapper = null; + this._wrapperSync = new Object(); + } + + @Override + protected boolean _checkKeyReference(@NotNull Object ref) { + return this._connectionRef.refersTo(ref); + } + + @NotNull + public PreparedStatement createWrapper() { + synchronized (this._wrapperSync) { + if (this._wrapper != null) + throw new IllegalStateException("Requested prepared statement already used by this connection (or not closed after using)"); + PreparedStatement orig = this._statement.get(); + if (orig == null) + throw new RuntimeException(""); + PreparedStatement wrapper = new BoundPreparedStatementWrapper(orig, this); + this._wrapper = wrapper; + return wrapper; + } + } + + public void unbindWrapper() { + synchronized (this._wrapperSync) { + this._wrapper = null; + } + } + } + + private static class BoundPreparedStatementWrapper extends _PreparedStatementWrapper_Delegates { + @NotNull + private final ConnectionsMapNode _node; + + BoundPreparedStatementWrapper(@NotNull PreparedStatement orig, @NotNull ConnectionsMapNode node) { + super(orig); + this._node = node; + } + + @Override + protected void _onClose() throws SQLException { + this._orig.clearParameters(); + this._orig.clearBatch(); + this._orig.clearWarnings(); + this._node.unbindWrapper(); + } + } +} diff --git a/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/_SynchronizedEmbedKeyHashMap.java b/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/_SynchronizedEmbedKeyHashMap.java index 9de6c6b..c6f305c 100644 --- a/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/_SynchronizedEmbedKeyHashMap.java +++ b/src/jvmMain/java/ru/langrafhomyak/db/jdbc_resources_manager/_SynchronizedEmbedKeyHashMap.java @@ -1,9 +1,13 @@ package ru.langrafhomyak.db.jdbc_resources_manager; +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Iterator; +import java.util.function.Predicate; + /* package */ class _SynchronizedEmbedKeyHashMap> { public static abstract class Node> { public final int keyHashCode; @@ -70,4 +74,29 @@ import org.jetbrains.annotations.Nullable; this._impl.trim(); } } + + /** + * @param nodePredicate {@code true} if should be removed + */ + public void cleanup(Predicate nodePredicate) { + synchronized (this._sync) { + final Iterator> it = this._impl.int2ObjectEntrySet().fastIterator(); + while (it.hasNext()) { + final Int2ObjectMap.Entry entry = it.next(); + final int key = entry.getIntKey(); + T node = entry.getValue(); + + while (node != null && nodePredicate.test(node)) { + this._impl.put(key, node._nextNode); + node = node._nextNode; + } + while (node != null) { + while (node._nextNode != null && nodePredicate.test(node._nextNode)) { + node._nextNode = node._nextNode._nextNode; + } + node = node._nextNode; + } + } + } + } }