All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.h2.jdbcx.JdbcConnectionPool Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
 * and the EPL 1.0 (https://h2database.com/html/license.html).
 * Initial Developer: Christian d'Heureuse, www.source-code.biz
 *
 * This class is multi-licensed under LGPL, MPL 2.0, and EPL 1.0.
 *
 * This module is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 * See https://www.gnu.org/licenses/lgpl-3.0.html
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 */
package org.h2.jdbcx;

import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

import javax.sql.ConnectionEvent;
import javax.sql.ConnectionEventListener;
import javax.sql.ConnectionPoolDataSource;
import javax.sql.DataSource;
import javax.sql.PooledConnection;

import org.h2.message.DbException;

/**
 * A simple standalone JDBC connection pool.
 * It is based on the
 * 
 *  MiniConnectionPoolManager written by Christian d'Heureuse (Java 1.5)
 * . It is used as follows:
 * 
 * import java.sql.*;
 * import org.h2.jdbcx.JdbcConnectionPool;
 * public class Test {
 *     public static void main(String... args) throws Exception {
 *         JdbcConnectionPool cp = JdbcConnectionPool.create(
 *             "jdbc:h2:~/test", "sa", "sa");
 *         for (String sql : args) {
 *             Connection conn = cp.getConnection();
 *             conn.createStatement().execute(sql);
 *             conn.close();
 *         }
 *         cp.dispose();
 *     }
 * }
 * 
* * @author Christian d'Heureuse * (www.source-code.biz) * @author Thomas Mueller */ public final class JdbcConnectionPool implements DataSource, ConnectionEventListener, JdbcConnectionPoolBackwardsCompat { private static final int DEFAULT_TIMEOUT = 30; private static final int DEFAULT_MAX_CONNECTIONS = 10; private final ConnectionPoolDataSource dataSource; private final Queue recycledConnections = new ConcurrentLinkedQueue<>(); private PrintWriter logWriter; private volatile int maxConnections = DEFAULT_MAX_CONNECTIONS; private volatile int timeout = DEFAULT_TIMEOUT; private AtomicInteger activeConnections = new AtomicInteger(); private AtomicBoolean isDisposed = new AtomicBoolean(); private JdbcConnectionPool(ConnectionPoolDataSource dataSource) { this.dataSource = dataSource; if (dataSource != null) { try { logWriter = dataSource.getLogWriter(); } catch (SQLException e) { // ignore } } } /** * Constructs a new connection pool. * * @param dataSource the data source to create connections * @return the connection pool */ public static JdbcConnectionPool create(ConnectionPoolDataSource dataSource) { return new JdbcConnectionPool(dataSource); } /** * Constructs a new connection pool for H2 databases. * * @param url the database URL of the H2 connection * @param user the user name * @param password the password * @return the connection pool */ public static JdbcConnectionPool create(String url, String user, String password) { JdbcDataSource ds = new JdbcDataSource(); ds.setURL(url); ds.setUser(user); ds.setPassword(password); return new JdbcConnectionPool(ds); } /** * Sets the maximum number of connections to use from now on. * The default value is 10 connections. * * @param max the maximum number of connections */ public void setMaxConnections(int max) { if (max < 1) { throw new IllegalArgumentException("Invalid maxConnections value: " + max); } this.maxConnections = max; } /** * Gets the maximum number of connections to use. * * @return the max the maximum number of connections */ public int getMaxConnections() { return maxConnections; } /** * Gets the maximum time in seconds to wait for a free connection. * * @return the timeout in seconds */ @Override public int getLoginTimeout() { return timeout; } /** * Sets the maximum time in seconds to wait for a free connection. * The default timeout is 30 seconds. Calling this method with the * value 0 will set the timeout to the default value. * * @param seconds the timeout, 0 meaning the default */ @Override public void setLoginTimeout(int seconds) { if (seconds == 0) { seconds = DEFAULT_TIMEOUT; } this.timeout = seconds; } /** * Closes all unused pooled connections. * Exceptions while closing are written to the log stream (if set). */ public void dispose() { isDisposed.set(true); PooledConnection pc; while ((pc = recycledConnections.poll()) != null) { closeConnection(pc); } } /** * Retrieves a connection from the connection pool. If * maxConnections connections are already in use, the method * waits until a connection becomes available or timeout * seconds elapsed. When the application is finished using the connection, * it must close it in order to return it to the pool. * If no connection becomes available within the given timeout, an exception * with SQL state 08001 and vendor code 8001 is thrown. * * @return a new Connection object. * @throws SQLException when a new connection could not be established, * or a timeout occurred */ @Override public Connection getConnection() throws SQLException { long max = System.nanoTime() + timeout * 1_000_000_000L; int spin = 0; do { if (activeConnections.incrementAndGet() <= maxConnections) { try { return getConnectionNow(); } catch (Throwable t) { activeConnections.decrementAndGet(); throw t; } } else { activeConnections.decrementAndGet(); } if (--spin >= 0) { continue; } try { spin = 3; Thread.sleep(1); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } while (System.nanoTime() - max <= 0); throw new SQLException("Login timeout", "08001", 8001); } /** * INTERNAL */ @Override public Connection getConnection(String user, String password) { throw new UnsupportedOperationException(); } private Connection getConnectionNow() throws SQLException { if (isDisposed.get()) { throw new IllegalStateException("Connection pool has been disposed."); } PooledConnection pc = recycledConnections.poll(); if (pc == null) { pc = dataSource.getPooledConnection(); } Connection conn = pc.getConnection(); pc.addConnectionEventListener(this); return conn; } /** * This method usually puts the connection back into the pool. There are * some exceptions: if the pool is disposed, the connection is disposed as * well. If the pool is full, the connection is closed. * * @param pc the pooled connection */ private void recycleConnection(PooledConnection pc) { int active = activeConnections.decrementAndGet(); if (active < 0) { activeConnections.incrementAndGet(); throw new AssertionError(); } if (!isDisposed.get() && active < maxConnections) { recycledConnections.add(pc); if (isDisposed.get()) { dispose(); } } else { closeConnection(pc); } } private void closeConnection(PooledConnection pc) { try { pc.close(); } catch (SQLException e) { if (logWriter != null) { e.printStackTrace(logWriter); } } } /** * INTERNAL */ @Override public void connectionClosed(ConnectionEvent event) { PooledConnection pc = (PooledConnection) event.getSource(); pc.removeConnectionEventListener(this); recycleConnection(pc); } /** * INTERNAL */ @Override public void connectionErrorOccurred(ConnectionEvent event) { // not used } /** * Returns the number of active (open) connections of this pool. This is the * number of Connection objects that have been issued by * getConnection() for which Connection.close() has * not yet been called. * * @return the number of active connections. */ public int getActiveConnections() { return activeConnections.get(); } /** * INTERNAL */ @Override public PrintWriter getLogWriter() { return logWriter; } /** * INTERNAL */ @Override public void setLogWriter(PrintWriter logWriter) { this.logWriter = logWriter; } /** * Return an object of this class if possible. * * @param iface the class * @return this */ @Override @SuppressWarnings("unchecked") public T unwrap(Class iface) throws SQLException { try { if (isWrapperFor(iface)) { return (T) this; } throw DbException.getInvalidValueException("iface", iface); } catch (Exception e) { throw DbException.toSQLException(e); } } /** * Checks if unwrap can return an object of this class. * * @param iface the class * @return whether or not the interface is assignable from this class */ @Override public boolean isWrapperFor(Class iface) throws SQLException { return iface != null && iface.isAssignableFrom(getClass()); } /** * [Not supported] */ @Override public Logger getParentLogger() { return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy