From b03cfbb451ebc68a9796eb5acb192fdd0d0b7e81 Mon Sep 17 00:00:00 2001 From: Valery Kharseko Date: Wed, 15 Jan 2025 17:41:46 +0300 Subject: [PATCH] jdbc: make connection short-lived (#459) --- .../backends/jdbc/CachedConnection.java | 337 ++++++++++++++++++ .../opends/server/backends/jdbc/Storage.java | 221 ++++++------ 2 files changed, 458 insertions(+), 100 deletions(-) create mode 100644 opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java new file mode 100644 index 0000000000..ede8c1354d --- /dev/null +++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java @@ -0,0 +1,337 @@ +/* + * The contents of this file are subject to the terms of the Common Development and + * Distribution License (the License). You may not use this file except in compliance with the + * License. + * + * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the + * specific language governing permission and limitations under the License. + * + * When distributing Covered Software, include this CDDL Header Notice in each file and include + * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL + * Header, with the fields enclosed by brackets [] replaced by your own identifying + * information: "Portions Copyright [year] [name of copyright owner]". + * + * Copyright 2024 3A Systems, LLC. + */ +package org.opends.server.backends.jdbc; + +import com.google.common.cache.*; + +import java.sql.*; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; + +public class CachedConnection implements Connection { + final Connection parent; + + static LoadingCache cached= CacheBuilder.newBuilder() + .expireAfterAccess(Long.parseLong(System.getProperty("org.openidentityplatform.opendj.jdbc.ttl","15000")), TimeUnit.MILLISECONDS) + .removalListener(new RemovalListener() { + @Override + public void onRemoval(RemovalNotification notification) { + try { + if (!notification.getValue().isClosed()) { + notification.getValue().close(); + } + } catch (SQLException e) { + } + } + }) + .build(new CacheLoader() { + @Override + public Connection load(String connectionString) throws Exception { + return DriverManager.getConnection(connectionString); + } + }); + + public CachedConnection(Connection parent) { + this.parent = parent; + } + + static CachedConnection getConnection(String connectionString) throws Exception { + Connection con=cached.get(connectionString); + try { + if (con != null && !con.isValid(0)) { + cached.invalidate(connectionString); + con.close(); + con = cached.get(connectionString); + } + } catch (SQLException e) { + con = null; + } + con.setAutoCommit(false); + return new CachedConnection(con); + } + + @Override + public Statement createStatement() throws SQLException { + return parent.createStatement(); + } + + @Override + public PreparedStatement prepareStatement(String sql) throws SQLException { + return parent.prepareStatement(sql); + } + + @Override + public CallableStatement prepareCall(String sql) throws SQLException { + return parent.prepareCall(sql); + } + + @Override + public String nativeSQL(String sql) throws SQLException { + return parent.nativeSQL(sql); + } + + @Override + public void setAutoCommit(boolean autoCommit) throws SQLException { + parent.setAutoCommit(autoCommit); + } + + @Override + public boolean getAutoCommit() throws SQLException { + return parent.getAutoCommit(); + } + + @Override + public void commit() throws SQLException { + parent.commit(); + } + + @Override + public void rollback() throws SQLException { + parent.rollback(); + } + + @Override + public void close() throws SQLException { + //rollback(); + } + + @Override + public boolean isClosed() throws SQLException { + return parent.isClosed(); + } + + @Override + public DatabaseMetaData getMetaData() throws SQLException { + return parent.getMetaData(); + } + + @Override + public void setReadOnly(boolean readOnly) throws SQLException { + parent.setReadOnly(readOnly); + } + + @Override + public boolean isReadOnly() throws SQLException { + return parent.isReadOnly(); + } + + @Override + public void setCatalog(String catalog) throws SQLException { + parent.setCatalog(catalog); + } + + @Override + public String getCatalog() throws SQLException { + return parent.getCatalog(); + } + + @Override + public void setTransactionIsolation(int level) throws SQLException { + parent.setTransactionIsolation(level); + } + + @Override + public int getTransactionIsolation() throws SQLException { + return parent.getTransactionIsolation(); + } + + @Override + public SQLWarning getWarnings() throws SQLException { + return parent.getWarnings(); + } + + @Override + public void clearWarnings() throws SQLException { + parent.clearWarnings(); + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { + return parent.createStatement(resultSetType, resultSetConcurrency); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + return parent.prepareStatement(sql, resultSetType, resultSetConcurrency); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { + return parent.prepareCall(sql, resultSetType, resultSetConcurrency) ; + } + + @Override + public Map> getTypeMap() throws SQLException { + return parent.getTypeMap(); + } + + @Override + public void setTypeMap(Map> map) throws SQLException { + parent.setTypeMap(map); + } + + @Override + public void setHoldability(int holdability) throws SQLException { + parent.setHoldability(holdability); + } + + @Override + public int getHoldability() throws SQLException { + return parent.getHoldability(); + } + + @Override + public Savepoint setSavepoint() throws SQLException { + return parent.setSavepoint(); + } + + @Override + public Savepoint setSavepoint(String name) throws SQLException { + return parent.setSavepoint(name); + } + + @Override + public void rollback(Savepoint savepoint) throws SQLException { + parent.rollback(savepoint); + } + + @Override + public void releaseSavepoint(Savepoint savepoint) throws SQLException { + parent.releaseSavepoint(savepoint); + } + + @Override + public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + return parent.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + return parent.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { + return parent.prepareCall(sql, resultSetType, resultSetConcurrency, resultSetHoldability); + } + + @Override + public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { + return parent.prepareStatement(sql, autoGeneratedKeys); + } + + @Override + public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { + return parent.prepareStatement(sql, columnIndexes); + } + + @Override + public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { + return parent.prepareStatement(sql, columnNames); + } + + @Override + public Clob createClob() throws SQLException { + return parent.createClob(); + } + + @Override + public Blob createBlob() throws SQLException { + return parent.createBlob(); + } + + @Override + public NClob createNClob() throws SQLException { + return parent.createNClob(); + } + + @Override + public SQLXML createSQLXML() throws SQLException { + return parent.createSQLXML(); + } + + @Override + public boolean isValid(int timeout) throws SQLException { + return parent.isValid(timeout); + } + + @Override + public void setClientInfo(String name, String value) throws SQLClientInfoException { + parent.setClientInfo(name, value); + } + + @Override + public void setClientInfo(Properties properties) throws SQLClientInfoException { + parent.setClientInfo(properties); + } + + @Override + public String getClientInfo(String name) throws SQLException { + return parent.getClientInfo(name); + } + + @Override + public Properties getClientInfo() throws SQLException { + return parent.getClientInfo(); + } + + @Override + public Array createArrayOf(String typeName, Object[] elements) throws SQLException { + return parent.createArrayOf(typeName, elements); + } + + @Override + public Struct createStruct(String typeName, Object[] attributes) throws SQLException { + return parent.createStruct(typeName, attributes); + } + + @Override + public void setSchema(String schema) throws SQLException { + parent.setSchema(schema); + } + + @Override + public String getSchema() throws SQLException { + return parent.getSchema(); + } + + @Override + public void abort(Executor executor) throws SQLException { + parent.abort(executor); + } + + @Override + public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { + parent.setNetworkTimeout(executor, milliseconds); + } + + @Override + public int getNetworkTimeout() throws SQLException { + return parent.getNetworkTimeout(); + } + + @Override + public T unwrap(Class iface) throws SQLException { + return parent.unwrap(iface); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return parent.isWrapperFor(iface); + } +} diff --git a/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Storage.java b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Storage.java index 67b6959fce..eda1f42b63 100644 --- a/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Storage.java +++ b/opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Storage.java @@ -82,13 +82,19 @@ boolean execute(PreparedStatement statement) throws SQLException { return statement.execute(); } - Connection con; + Connection getConnection() throws Exception { + final Connection con=CachedConnection.getConnection(config.getDBDirectory()); + return con; + } + + + AccessMode accessMode=AccessMode.READ_ONLY; @Override public void open(AccessMode accessMode) throws Exception { - con=DriverManager.getConnection(config.getDBDirectory()); - con.setAutoCommit(false); - con.setReadOnly(!AccessMode.READ_WRITE.equals(accessMode)); - storageStatus = StorageStatus.working(); + try (final Connection con=getConnection()) { + this.accessMode = accessMode; + storageStatus = StorageStatus.working(); + } } private StorageStatus storageStatus = StorageStatus.lockedDown(LocalizableMessage.raw("closed")); @@ -100,14 +106,6 @@ public StorageStatus getStorageStatus() { @Override public void close() { storageStatus = StorageStatus.lockedDown(LocalizableMessage.raw("closed")); - try { - if (con != null && !con.isClosed()) { - con.close(); - } - } catch (SQLException e) { - logger.error(LocalizableMessage.raw("close(): %s",e),e); - } - con=null; } String getTableName(TreeName treeName) { @@ -124,13 +122,25 @@ public void removeStorageFiles() throws StorageRuntimeException { throw new StorageRuntimeException(e); } } - try { - for (TreeName treeName : listTrees()) { - final PreparedStatement statement=con.prepareStatement("drop table "+getTableName(treeName)); - execute(statement); + final Set trees=listTrees(); + if (!trees.isEmpty()) { + try (final Connection con = getConnection()) { + try { + for (final TreeName treeName : trees) { + try (final PreparedStatement statement = con.prepareStatement("drop table " + getTableName(treeName))) { + execute(statement); + } + } + con.commit(); + } catch (SQLException e) { + try { + con.rollback(); + } catch (SQLException e2) {} + throw new StorageRuntimeException(e); + } + } catch (Exception e) { + throw new StorageRuntimeException(e); } - }catch (Throwable e) { - throw new StorageRuntimeException(e); } if (!isOpen) { close(); @@ -140,107 +150,111 @@ public void removeStorageFiles() throws StorageRuntimeException { //operation @Override public T read(ReadOperation readOperation) throws Exception { - return readOperation.run(new ReadableTransactionImpl()); + try(final Connection con=getConnection()) { + return readOperation.run(new ReadableTransactionImpl(con)); + } } @Override public void write(WriteOperation writeOperation) throws Exception { - try { - writeOperation.run(new WriteableTransactionTransactionImpl()); - con.commit(); - } catch (Exception e) { + try (final Connection con=getConnection()) { try { - con.rollback(); - } catch (SQLException ex) {} - throw e; + writeOperation.run(new WriteableTransactionTransactionImpl(con)); + con.commit(); + } catch (Exception e) { + try { + con.rollback(); + } catch (SQLException ex) {} + throw e; + } } } private class ReadableTransactionImpl implements ReadableTransaction { + final Connection con; + boolean isReadOnly=true; + + public ReadableTransactionImpl(Connection con) { + this.con=con; + } + @Override public ByteString read(TreeName treeName, ByteSequence key) { - try { - final PreparedStatement statement=con.prepareStatement("select v from "+getTableName(treeName)+" where k=?"); + try (final PreparedStatement statement=con.prepareStatement("select v from "+getTableName(treeName)+" where k=?")){ statement.setBytes(1,key.toByteArray()); try(ResultSet rc=executeResultSet(statement)) { return rc.next() ? ByteString.wrap(rc.getBytes("v")) : null; } }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } @Override public Cursor openCursor(TreeName treeName) { - return new CursorImpl(treeName); + return new CursorImpl(isReadOnly,con,treeName); } @Override public long getRecordCount(TreeName treeName) { - try { - final PreparedStatement statement=con.prepareStatement("select count(*) from "+getTableName(treeName)); - try(ResultSet rc=executeResultSet(statement)) { - return rc.next() ? rc.getLong(1) : 0; - } + try (final PreparedStatement statement=con.prepareStatement("select count(*) from "+getTableName(treeName)); + final ResultSet rc=executeResultSet(statement)){ + return rc.next() ? rc.getLong(1) : 0; }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } } private final class WriteableTransactionTransactionImpl extends ReadableTransactionImpl implements WriteableTransaction { - public WriteableTransactionTransactionImpl() { - super(); - try { - if (con.isReadOnly()) { - throw new ReadOnlyStorageException(); - } - } catch (SQLException e) { - throw new RuntimeException(e); + public WriteableTransactionTransactionImpl(Connection con) { + super(con); + if (!accessMode.isWriteable()) { + throw new ReadOnlyStorageException(); } + isReadOnly=false; } @Override public void openTree(TreeName treeName, boolean createOnDemand) { if (createOnDemand) { - try { - final PreparedStatement statement=con.prepareStatement("create table if not exists "+getTableName(treeName)+" (k bytea primary key,v bytea)"); + try (final PreparedStatement statement=con.prepareStatement("create table if not exists "+getTableName(treeName)+" (k bytea primary key,v bytea)")){ execute(statement); + con.commit(); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } } public void clearTree(TreeName treeName) { - try { - final PreparedStatement statement=con.prepareStatement("truncate table "+getTableName(treeName)); + try (final PreparedStatement statement=con.prepareStatement("delete from "+getTableName(treeName))){ execute(statement); + con.commit(); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } @Override public void deleteTree(TreeName treeName) { - try { - final PreparedStatement statement=con.prepareStatement("drop table "+getTableName(treeName)); + try (final PreparedStatement statement=con.prepareStatement("drop table "+getTableName(treeName))){ execute(statement); + con.commit(); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } @Override public void put(TreeName treeName, ByteSequence key, ByteSequence value) { - try { - delete(treeName,key); - final PreparedStatement statement=con.prepareStatement("insert into "+getTableName(treeName)+" (k,v) values(?,?) "); + delete(treeName,key); + try (final PreparedStatement statement=con.prepareStatement("insert into "+getTableName(treeName)+" (k,v) values(?,?) ")){ statement.setBytes(1,key.toByteArray()); statement.setBytes(2,value.toByteArray()); execute(statement); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } @@ -263,12 +277,11 @@ public boolean update(TreeName treeName, ByteSequence key, UpdateFunction f) { @Override public boolean delete(TreeName treeName, ByteSequence key) { - try { - final PreparedStatement statement=con.prepareStatement("delete from "+getTableName(treeName)+" where k=?"); + try (final PreparedStatement statement=con.prepareStatement("delete from "+getTableName(treeName)+" where k=?")){ statement.setBytes(1,key.toByteArray()); execute(statement); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } return true; } @@ -276,20 +289,20 @@ public boolean delete(TreeName treeName, ByteSequence key) { private final class CursorImpl implements Cursor { final TreeName treeName; - //final WriteableTransactionTransactionImpl tx; - ResultSet rc; - - public CursorImpl(TreeName treeName) { + final PreparedStatement statement; + final ResultSet rc; + final boolean isReadOnly; + public CursorImpl(boolean isReadOnly, Connection con, TreeName treeName) { this.treeName=treeName; - //this.tx=tx; + this.isReadOnly=isReadOnly; try { - final PreparedStatement statement=con.prepareStatement("select k,v from "+getTableName(treeName)+" order by k", - ResultSet.TYPE_SCROLL_SENSITIVE, - ResultSet.CONCUR_UPDATABLE); + statement=con.prepareStatement("select k,v from "+getTableName(treeName)+" order by k", + isReadOnly?ResultSet.TYPE_SCROLL_INSENSITIVE:ResultSet.TYPE_SCROLL_SENSITIVE, + isReadOnly?ResultSet.CONCUR_READ_ONLY:ResultSet.CONCUR_UPDATABLE); rc=executeResultSet(statement); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } @@ -298,7 +311,7 @@ public boolean next() { try { return rc.next(); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } @@ -307,7 +320,7 @@ public boolean isDefined() { try{ return rc.getRow()>0; }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } @@ -319,7 +332,7 @@ public ByteString getKey() throws NoSuchElementException { try{ return ByteString.wrap(rc.getBytes("k")); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } @@ -331,7 +344,7 @@ public ByteString getValue() throws NoSuchElementException { try{ return ByteString.wrap(rc.getBytes("v")); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } @@ -343,19 +356,17 @@ public void delete() throws NoSuchElementException, UnsupportedOperationExceptio try{ rc.deleteRow(); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } @Override public void close() { - if (rc!=null) { - try{ - rc.close(); - }catch (SQLException e) { - throw new RuntimeException(e); - } - rc = null; + try{ + rc.close(); + statement.close(); + }catch (SQLException e) { + throw new StorageRuntimeException(e); } } @@ -366,7 +377,7 @@ public boolean positionToKeyOrNext(ByteSequence key) { try{ rc.first(); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } try{ @@ -379,7 +390,7 @@ public boolean positionToKeyOrNext(ByteSequence key) { } }while(rc.next()); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } return false; } @@ -390,7 +401,7 @@ public boolean positionToKey(ByteSequence key) { try{ rc.first(); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } if (!isDefined()){ @@ -406,7 +417,7 @@ public boolean positionToKey(ByteSequence key) { } }while(rc.next()); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } return false; } @@ -417,7 +428,7 @@ public boolean positionToLastKey() { try{ return rc.last(); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } } @@ -426,7 +437,7 @@ public boolean positionToIndex(int index) { try{ rc.first(); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } if (!isDefined()){ return false; @@ -440,7 +451,7 @@ public boolean positionToIndex(int index) { ct++; }while(rc.next()); }catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } return false; } @@ -449,20 +460,23 @@ public boolean positionToIndex(int index) { @Override public Set listTrees() { final Set res=new HashSet<>(); - try(ResultSet rs = con.getMetaData().getTables(null, null, "OpenDJ%", new String[]{"TABLE"})) { + try(final Connection con=getConnection(); + final ResultSet rs = con.getMetaData().getTables(null, null, "OpenDJ%", new String[]{"TABLE"})) { while (rs.next()) { res.add(TreeName.valueOf(rs.getString("TABLE_NAME").substring(6))); } - } catch (SQLException e) { - throw new RuntimeException(e); + } catch (Exception e) { + throw new StorageRuntimeException(e); } return res; } private final class ImporterImpl implements Importer { - final WriteableTransactionTransactionImpl tx; - + final Connection con; + final ReadableTransactionImpl txr; + final WriteableTransactionTransactionImpl txw; + final Boolean isOpen; public ImporterImpl() { @@ -474,15 +488,22 @@ public ImporterImpl() { throw new StorageRuntimeException(e); } } - tx=new WriteableTransactionTransactionImpl(); + try { + con = getConnection(); + }catch (Exception e){ + throw new StorageRuntimeException(e); + } + txr =new ReadableTransactionImpl(con); + txw =new WriteableTransactionTransactionImpl(con); } @Override public void close() { try { con.commit(); + con.close(); } catch (SQLException e) { - throw new RuntimeException(e); + throw new StorageRuntimeException(e); } if (!isOpen) { Storage.this.close(); @@ -491,22 +512,22 @@ public void close() { @Override public void clearTree(TreeName name) { - tx.clearTree(name); + txw.clearTree(name); } @Override public void put(TreeName treeName, ByteSequence key, ByteSequence value) { - tx.put(treeName, key, value); + txw.put(treeName, key, value); } @Override public ByteString read(TreeName treeName, ByteSequence key) { - return tx.read(treeName, key); + return txr.read(treeName, key); } @Override public SequentialCursor openCursor(TreeName treeName) { - return tx.openCursor(treeName); + return txr.openCursor(treeName); } }