From b03cfbb451ebc68a9796eb5acb192fdd0d0b7e81 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Wed, 15 Jan 2025 14:41:46 +0000
Subject: [PATCH] jdbc: make connection short-lived (#459)

---
 opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/Storage.java          |  221 ++++++++++++----------
 opendj-server-legacy/src/main/java/org/opends/server/backends/jdbc/CachedConnection.java |  337 +++++++++++++++++++++++++++++++++
 2 files changed, 458 insertions(+), 100 deletions(-)

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 0000000..ede8c13
--- /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<String,Connection> cached= CacheBuilder.newBuilder()
+            .expireAfterAccess(Long.parseLong(System.getProperty("org.openidentityplatform.opendj.jdbc.ttl","15000")), TimeUnit.MILLISECONDS)
+            .removalListener(new RemovalListener<String, Connection>() {
+                @Override
+                public void onRemoval(RemovalNotification<String, Connection> notification) {
+                    try {
+                        if (!notification.getValue().isClosed()) {
+                            notification.getValue().close();
+                        }
+                    } catch (SQLException e) {
+                    }
+                }
+            })
+            .build(new CacheLoader<String, Connection>() {
+                @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<String, Class<?>> getTypeMap() throws SQLException {
+        return parent.getTypeMap();
+    }
+
+    @Override
+    public void setTypeMap(Map<String, Class<?>> 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> T unwrap(Class<T> 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 67b6959..eda1f42 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 @@
 		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 @@
 	@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 @@
 				throw new StorageRuntimeException(e);
 			}
 		}
-		try {
-			for (TreeName treeName : listTrees()) {
-				final PreparedStatement statement=con.prepareStatement("drop table "+getTableName(treeName));
-				execute(statement);
+		final Set<TreeName> 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 @@
 	//operation
 	@Override
 	public <T> T read(ReadOperation<T> 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<ByteString, ByteString> 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 @@
 
 		@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 @@
 	
 	private final class CursorImpl implements Cursor<ByteString, ByteString> {
 		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 @@
 			try {
 				return rc.next();
 			}catch (SQLException e) {
-				throw new RuntimeException(e);
+				throw new StorageRuntimeException(e);
 			}
 		}
 
@@ -307,7 +320,7 @@
 			try{
 				return rc.getRow()>0;
 			}catch (SQLException e) {
-				throw new RuntimeException(e);
+				throw new StorageRuntimeException(e);
 			}
 		}
 
@@ -319,7 +332,7 @@
 			try{
 				return ByteString.wrap(rc.getBytes("k"));
 			}catch (SQLException e) {
-				throw new RuntimeException(e);
+				throw new StorageRuntimeException(e);
 			}
 		}
 
@@ -331,7 +344,7 @@
 			try{
 				return ByteString.wrap(rc.getBytes("v"));
 			}catch (SQLException e) {
-				throw new RuntimeException(e);
+				throw new StorageRuntimeException(e);
 			}
 		}
 
@@ -343,19 +356,17 @@
 			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 @@
 				try{
 					rc.first();
 				}catch (SQLException e) {
-					throw new RuntimeException(e);
+					throw new StorageRuntimeException(e);
 				}
 			}
 			try{
@@ -379,7 +390,7 @@
 					}
 				}while(rc.next());
 			}catch (SQLException e) {
-				throw new RuntimeException(e);
+				throw new StorageRuntimeException(e);
 			}
 			return false;
 		}
@@ -390,7 +401,7 @@
 				try{
 					rc.first();
 				}catch (SQLException e) {
-					throw new RuntimeException(e);
+					throw new StorageRuntimeException(e);
 				}
 			}
 			if (!isDefined()){
@@ -406,7 +417,7 @@
 					}
 				}while(rc.next());
 			}catch (SQLException e) {
-				throw new RuntimeException(e);
+				throw new StorageRuntimeException(e);
 			}
 			return false;
 		}
@@ -417,7 +428,7 @@
 			try{
 				return rc.last();
 			}catch (SQLException e) {
-				throw new RuntimeException(e);
+				throw new StorageRuntimeException(e);
 			}
 		}
 
@@ -426,7 +437,7 @@
 			try{
 				rc.first();
 			}catch (SQLException e) {
-				throw new RuntimeException(e);
+				throw new StorageRuntimeException(e);
 			}
 			if (!isDefined()){
 				return false;
@@ -440,7 +451,7 @@
 					ct++;
 				}while(rc.next());
 			}catch (SQLException e) {
-				throw new RuntimeException(e);
+				throw new StorageRuntimeException(e);
 			}
 			return false;
 		}
@@ -449,20 +460,23 @@
 	@Override
 	public Set<TreeName> listTrees() {
 		final Set<TreeName> 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 @@
 					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 @@
 		
 		@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<ByteString, ByteString> openCursor(TreeName treeName) {
-			return tx.openCursor(treeName);
+			return txr.openCursor(treeName);
 		}
 	}
 	

--
Gitblit v1.10.0