PreparedStatementsCompilationPhantomCache moved to java; map of connections extracted to separate class
This commit is contained in:
parent
356c36a514
commit
76943a0af0
@ -1,3 +1,4 @@
|
|||||||
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||||
import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.*
|
import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.*
|
||||||
import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.plugin.xomrk
|
import ru.landgrafhomyak.kotlin.kmp_gradle_build_helper.plugin.xomrk
|
||||||
@ -27,12 +28,26 @@ xomrk {
|
|||||||
optInContracts()
|
optInContracts()
|
||||||
explicitApi()
|
explicitApi()
|
||||||
|
|
||||||
jvm()
|
jvm {
|
||||||
|
withJava()
|
||||||
|
|
||||||
|
/*compilations.configureEach {
|
||||||
|
compileJavaTaskProvider?.configure {
|
||||||
|
targetCompatibility = "1.8"
|
||||||
|
}
|
||||||
|
compileTaskProvider.configure {
|
||||||
|
compilerOptions { jvmTarget = JvmTarget.JVM_1_8 }
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
jvmMain {
|
jvmMain {
|
||||||
dependencies {
|
dependencies {
|
||||||
|
compileOnly("org.jetbrains:annotations:26.0.2")
|
||||||
compileOnly("com.intellij:annotations:9.0.4")
|
compileOnly("com.intellij:annotations:9.0.4")
|
||||||
|
compileOnly("com.intellij:annotations:9.0.4")
|
||||||
|
implementation("it.unimi.dsi:fastutil:6.3")
|
||||||
implementation("ru.landgrafhomyak.utility:highlevel-try-finally:0.4")
|
implementation("ru.landgrafhomyak.utility:highlevel-try-finally:0.4")
|
||||||
implementation("ru.landgrafhomyak.utility:reference-counter:0.1")
|
implementation("ru.landgrafhomyak.utility:reference-counter:0.1")
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,118 @@
|
|||||||
|
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 static java.lang.System.identityHashCode;
|
||||||
|
|
||||||
|
public class PreparedStatementsCompilationPhantomCache /*implements ReadOnlyProperty<Object,?>*/ {
|
||||||
|
@NotNull
|
||||||
|
private final String _statementSource;
|
||||||
|
@NotNull
|
||||||
|
private final _SynchronizedEmbedKeyHashMap<ConnectionsMapNode> _map;
|
||||||
|
|
||||||
|
public PreparedStatementsCompilationPhantomCache(@NotNull @Language("SQL") String statementSource) {
|
||||||
|
this._statementSource = statementSource;
|
||||||
|
this._map = new _SynchronizedEmbedKeyHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
node = new ConnectionsMapNode(this, connection);
|
||||||
|
this._map.add(node);
|
||||||
|
}
|
||||||
|
return node.createWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ConnectionsMapNode extends _SynchronizedEmbedKeyHashMap.Node<ConnectionsMapNode> {
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private final BoundPhantomReference _connectionRef;
|
||||||
|
@NotNull
|
||||||
|
private final WeakReference<PreparedStatement> _statement;
|
||||||
|
@Nullable
|
||||||
|
private PreparedStatement _wrapper;
|
||||||
|
@NotNull
|
||||||
|
private final Object _wrapperSync;
|
||||||
|
|
||||||
|
public ConnectionsMapNode(@NotNull PreparedStatementsCompilationPhantomCache owner, @NotNull Connection connection) throws SQLException {
|
||||||
|
super(identityHashCode(connection));
|
||||||
|
this._connectionRef = owner.new BoundPhantomReference(connection, this);
|
||||||
|
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 class BoundPhantomReference extends _PhantomCacheCleanup.CleanablePhantomReference<Object> {
|
||||||
|
@NotNull
|
||||||
|
private final ConnectionsMapNode descriptor;
|
||||||
|
|
||||||
|
public BoundPhantomReference(@NotNull Connection ref, @NotNull ConnectionsMapNode descriptor) {
|
||||||
|
super(ref);
|
||||||
|
this.descriptor = descriptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void cleanup() {
|
||||||
|
PreparedStatementsCompilationPhantomCache.this._map.remove(this.descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BoundPreparedStatementWrapper extends PreparedStatementWrapper {
|
||||||
|
@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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,73 @@
|
|||||||
|
package ru.langrafhomyak.db.jdbc_resources_manager;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
/* package */ class _SynchronizedEmbedKeyHashMap<T extends _SynchronizedEmbedKeyHashMap.Node<T>> {
|
||||||
|
public static abstract class Node<T extends _SynchronizedEmbedKeyHashMap.Node<T>> {
|
||||||
|
public final int keyHashCode;
|
||||||
|
@Nullable
|
||||||
|
/*private*/ T _nextNode;
|
||||||
|
|
||||||
|
public Node(int keyHashCode) {
|
||||||
|
this.keyHashCode = keyHashCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract boolean _checkKeyReference(@NotNull Object ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
private final Int2ObjectOpenHashMap<T> _impl;
|
||||||
|
@NotNull
|
||||||
|
private final Object _sync;
|
||||||
|
|
||||||
|
public _SynchronizedEmbedKeyHashMap() {
|
||||||
|
this._impl = new Int2ObjectOpenHashMap<>();
|
||||||
|
this._sync = new Object();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public T get(int keyHashCode, @NotNull Object ref) {
|
||||||
|
synchronized (this._sync) {
|
||||||
|
T p = this._impl.get(keyHashCode);
|
||||||
|
while (p != null) {
|
||||||
|
if (p._checkKeyReference(ref))
|
||||||
|
return p;
|
||||||
|
p = p._nextNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void add(@NotNull T node) {
|
||||||
|
synchronized (this._sync) {
|
||||||
|
node._nextNode = this._impl.put(node.keyHashCode, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void remove(@NotNull T node) {
|
||||||
|
synchronized (this._sync) {
|
||||||
|
REMOVED:
|
||||||
|
{
|
||||||
|
T p = this._impl.get(node.keyHashCode);
|
||||||
|
if (p == node) {
|
||||||
|
this._impl.put(node.keyHashCode, p._nextNode);
|
||||||
|
break REMOVED;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (p != null) {
|
||||||
|
T n = p._nextNode;
|
||||||
|
if (n == node) {
|
||||||
|
p._nextNode = n._nextNode;
|
||||||
|
break REMOVED;
|
||||||
|
}
|
||||||
|
p = n;
|
||||||
|
}
|
||||||
|
throw new IllegalArgumentException("Node not found");
|
||||||
|
}
|
||||||
|
this._impl.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -23,15 +23,17 @@ import java.sql.Statement
|
|||||||
import java.sql.Time
|
import java.sql.Time
|
||||||
import java.sql.Timestamp
|
import java.sql.Timestamp
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
|
import kotlin.jvm.Throws
|
||||||
import ru.landrafhomyak.utility.reference_counter.CloseableReferenceCounter
|
import ru.landrafhomyak.utility.reference_counter.CloseableReferenceCounter
|
||||||
|
|
||||||
@Suppress("UsePropertyAccessSyntax")
|
@Suppress("UsePropertyAccessSyntax")
|
||||||
internal abstract class PreparedStatementWrapper(protected val _orig: PreparedStatement) : PreparedStatement {
|
internal abstract class PreparedStatementWrapper(@JvmField protected val _orig: PreparedStatement) : PreparedStatement {
|
||||||
private val _refcnt = CloseableReferenceCounter("This prepared statement was returned to pool to be used in future")
|
private val _refcnt = CloseableReferenceCounter("This prepared statement was returned to pool to be used in future")
|
||||||
private var _currentQuery: ResultSetWrapper? = null
|
private var _currentQuery: ResultSetWrapper? = null
|
||||||
private var _closeOnCompletion: Boolean = false
|
private var _closeOnCompletion: Boolean = false
|
||||||
private var _isClosed = false
|
private var _isClosed = false
|
||||||
|
|
||||||
|
@Throws(SQLException::class)
|
||||||
protected abstract fun _onClose()
|
protected abstract fun _onClose()
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
@ -312,7 +314,7 @@ internal abstract class PreparedStatementWrapper(protected val _orig: PreparedSt
|
|||||||
override fun getResultSetType() =
|
override fun getResultSetType() =
|
||||||
this._refcnt.withRef(this._orig::getFetchSize)
|
this._refcnt.withRef(this._orig::getFetchSize)
|
||||||
|
|
||||||
override fun addBatch(sql: String?) =
|
override fun addBatch(sql: String?): Unit =
|
||||||
throw SQLException("addBatch(String) not allowed on PreparedStatement")
|
throw SQLException("addBatch(String) not allowed on PreparedStatement")
|
||||||
|
|
||||||
override fun clearBatch() =
|
override fun clearBatch() =
|
||||||
|
@ -1,132 +0,0 @@
|
|||||||
package ru.langrafhomyak.db.jdbc_resources_manager
|
|
||||||
|
|
||||||
import java.sql.Connection
|
|
||||||
import java.sql.PreparedStatement
|
|
||||||
import java.util.concurrent.locks.ReentrantLock
|
|
||||||
import kotlin.concurrent.withLock
|
|
||||||
import kotlin.properties.ReadOnlyProperty
|
|
||||||
import kotlin.reflect.KProperty
|
|
||||||
import org.intellij.lang.annotations.Language
|
|
||||||
|
|
||||||
public class PreparedStatementsCompilationPhantomCache(
|
|
||||||
@Language("SQL")
|
|
||||||
private val _statementSource: String
|
|
||||||
) : ReadOnlyProperty<Any?, PreparedStatementsCompilationPhantomCache.Accessor> {
|
|
||||||
private val _mapSync = ReentrantLock()
|
|
||||||
private val _map = arrayOfNulls<ConnectionsMapNode>(7)
|
|
||||||
private var _connectionsCount = 0
|
|
||||||
|
|
||||||
override fun getValue(thisRef: Any?, property: KProperty<*>): Accessor =
|
|
||||||
this.Accessor()
|
|
||||||
|
|
||||||
public inner class Accessor {
|
|
||||||
public operator fun invoke(connection: Connection): PreparedStatement =
|
|
||||||
this@PreparedStatementsCompilationPhantomCache.getForConnection(connection)
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ConnectionsMapNode(
|
|
||||||
owner: PreparedStatementsCompilationPhantomCache,
|
|
||||||
connection: Connection,
|
|
||||||
@JvmField
|
|
||||||
var nextNode: ConnectionsMapNode?,
|
|
||||||
@JvmField
|
|
||||||
val connectionHashCode: Int
|
|
||||||
) {
|
|
||||||
@JvmField
|
|
||||||
val connection = owner.BoundPhantomReference(connection, this)
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var statement: PreparedStatement? = null
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
val lock = ReentrantLock()
|
|
||||||
|
|
||||||
@JvmField
|
|
||||||
var wrapper: BoundPreparedStatementWrapper? = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun _resizeIfNeeded() {
|
|
||||||
if (this._connectionsCount / 0.75 > this._map.size) {
|
|
||||||
val newMap = arrayOfNulls<ConnectionsMapNode>(this._map.size * 2)
|
|
||||||
for (s in this._map) {
|
|
||||||
var p = s
|
|
||||||
while (p != null) {
|
|
||||||
val i = Integer.remainderUnsigned(p.connectionHashCode, this._map.size)
|
|
||||||
val n = p.nextNode
|
|
||||||
p.nextNode = newMap[i]
|
|
||||||
newMap[i] = p
|
|
||||||
p = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun _getNode(connection: Connection): ConnectionsMapNode {
|
|
||||||
val newNode = this._mapSync.withLock {
|
|
||||||
val connectionHashCode = connection.hashCode()
|
|
||||||
val index = Integer.remainderUnsigned(connectionHashCode, this._map.size)
|
|
||||||
var p = this._map[index]
|
|
||||||
while (p != null) {
|
|
||||||
if (p.connection.refersTo(connection))
|
|
||||||
return@_getNode p
|
|
||||||
p = p.nextNode
|
|
||||||
}
|
|
||||||
val node = ConnectionsMapNode(this, connection, this._map[index], connectionHashCode)
|
|
||||||
this._map[index] = node
|
|
||||||
this._connectionsCount++
|
|
||||||
this._resizeIfNeeded()
|
|
||||||
return@withLock node
|
|
||||||
}
|
|
||||||
|
|
||||||
newNode.statement = connection.prepareStatement(this._statementSource)
|
|
||||||
return newNode
|
|
||||||
}
|
|
||||||
|
|
||||||
public fun getForConnection(connection: Connection): PreparedStatement {
|
|
||||||
val node = this._getNode(connection)
|
|
||||||
val wrapper: PreparedStatement
|
|
||||||
node.lock.withLock {
|
|
||||||
if (node.wrapper != null)
|
|
||||||
throw IllegalStateException("Requested prepared statement already used by this connection (or not closed after using)")
|
|
||||||
wrapper = BoundPreparedStatementWrapper(node.statement!!, node)
|
|
||||||
node.wrapper = wrapper
|
|
||||||
}
|
|
||||||
return wrapper
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private inner class BoundPhantomReference(
|
|
||||||
ref: Connection,
|
|
||||||
private val descriptor: ConnectionsMapNode
|
|
||||||
) : _PhantomCacheCleanup.CleanablePhantomReference<Connection>(ref) {
|
|
||||||
override fun cleanup() {
|
|
||||||
this@PreparedStatementsCompilationPhantomCache._mapSync.withLock {
|
|
||||||
val i = Integer.remainderUnsigned(this.descriptor.connectionHashCode, this@PreparedStatementsCompilationPhantomCache._map.size)
|
|
||||||
var r = this@PreparedStatementsCompilationPhantomCache._map[i]
|
|
||||||
if (r === this.descriptor) {
|
|
||||||
this@PreparedStatementsCompilationPhantomCache._map[i] = r.nextNode
|
|
||||||
}
|
|
||||||
while (r != null) {
|
|
||||||
val n = r.nextNode
|
|
||||||
if (n === this.descriptor) {
|
|
||||||
r.nextNode = n.nextNode
|
|
||||||
break
|
|
||||||
}
|
|
||||||
r = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class BoundPreparedStatementWrapper(orig: PreparedStatement, private val _node: ConnectionsMapNode) : PreparedStatementWrapper(orig) {
|
|
||||||
override fun _onClose() {
|
|
||||||
this._orig.clearParameters()
|
|
||||||
this._orig.clearBatch()
|
|
||||||
this._orig.clearWarnings()
|
|
||||||
this._orig.close()
|
|
||||||
this._node.lock.withLock {
|
|
||||||
this._node.wrapper = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Reference in New Issue
Block a user