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

net.freeutils.util.db.DBManager Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright © 2003-2024 Amichai Rothman
 *
 *  This file is part of JElementary - the Java Elementary Utilities package.
 *
 *  JElementary is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  JElementary 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 General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with JElementary.  If not, see .
 *
 *  For additional info see https://www.freeutils.net/source/jelementary/
 */

package net.freeutils.util.db;

import java.io.Closeable;
import java.sql.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import javax.sql.DataSource;
import net.freeutils.util.Containers;
import net.freeutils.util.Reflect;
import net.freeutils.util.config.*;


/**
 * The {@code DBManager} class provides database management functionality.
 * 

* A sample usage (ignoring proper error handling) might look like the following: *


 * // configure and open connection
 * DBManager db = new DBManager();
 * db.configure(new Configuration() {{
 *     // for MySQL
 *     set("dataSourceClass", "com.mysql.jdbc.jdbc2.optional.MysqlConnectionPoolDataSource");
 *     set("user", "myUser");
 *     set("password", "myPassword");
 *     set("serverName", "localhost");
 *     set("databaseName", "myDB");
 *     set("autoCommit", "false");
 * }});
 * db.open();
 *
 * // run non-transactional queries
 * User user = db.runner().queryBean(User.class, "SELECT * FROM users WHERE id = ?", userId);
 *
 * // run transactional queries
 * DBRunner runner = db.transactionRunner();
 * try {
 *     runner.update("DELETE FROM users WHERE id = ?", userId);
 *     runner.update("DELETE FROM friends WHERE user_id = ?", userId);
 *     runner.update("DELETE FROM friends WHERE friend_id = ?", userId);
 *     runner.commit();
 * } catch (SQLException sqle) {
 *     runner.rollback();
 *     throw sqle;
 * } finally {
 *     runner.close();
 * }
 *
 * // close db
 * db.close();
 * 
* * @author Yaniv Kunda * @author Amichai Rothman * @since 2009-01-14 */ public class DBManager implements Configurable, Closeable { protected volatile String dataSourceClassName; protected volatile Map defaultAttributes; protected volatile int maxConnections; protected volatile long connectionTimeout; protected volatile DBConnectionPool pool; /** * Creates a new data source of the given class name with the given attributes. *

* Attributes are set by calling their corresponding setter methods on the data source * * @param dataSourceClassName the data source class name * @param attributes the attributes to set in the data source, by invoking * the corresponding setter methods * @return the newly created data source * @throws SQLException if an error occurs */ protected static DataSource createDataSource(String dataSourceClassName, Map attributes) throws SQLException { DataSource dataSource; try { dataSource = Reflect.newInstance(dataSourceClassName); } catch (Exception e) { throw new SQLException("can't create datasource " + dataSourceClassName, e); } Reflect.setProperties(dataSource, attributes); return dataSource; } /** * Configures the DBManager using the given configuration section. *

* The configuration properties used by this class are the * dataSourceClass, user, password and maxConnections properties. *

* Other properties are vendor-specific and passed transparently to its * DataSource instance using setters. *

* For details on which DataSource class name to specify for your JDBC driver, * and which configuration properties it supports, see the vendor's documentation. *

* Note that connection URL properties are often, but not always, equivalent to * DataSource properties, so you should specifically search for vendor documentation * on DataSource properties rather than connection URL properties if possible. * * @param conf the configuration section containing the configuration data * @throws Exception if an error occurs which prevents the DBManager from being configured */ @Override public void configure(Configuration conf) throws Exception { dataSourceClassName = conf.get("dataSourceClass"); defaultAttributes = conf.strings(); // validate mandatory attributes if (!defaultAttributes.containsKey("user") || !defaultAttributes.containsKey("password")) throw new ConfigurationException("missing user/password"); maxConnections = conf.getInt("maxConnections", 25); connectionTimeout = conf.getLong("connectionTimeout", 10000L); } /** * Opens the data source and initializes the connection pool. * * @throws SQLException if an error occurs * @throws IllegalStateException if this manager has not been configured */ public void open() throws SQLException { if (dataSourceClassName == null) throw new IllegalStateException("DBManager is not configured"); if (pool != null) throw new IllegalStateException("DBManager is already open"); DataSource dataSource = createDataSource(dataSourceClassName, defaultAttributes); pool = new DBConnectionPool(dataSource, connectionTimeout, maxConnections); // test connection Connection conn = getConnection(); try { testConnection(conn); } finally { conn.close(); } } /** * Closes all connections and releases all resources. * This instance must not be used after it is closed. */ @Override public void close() { if (pool != null) pool.close(); pool = null; } /** * Returns a non-pooled connection with the given attributes added to the default attributes. *

* Attributes are set by calling their corresponding setter methods on the data source. *

* The caller must close the connection when done using it. *

* This method is useful for databases that require passing special connection * attributes to perform one-time operations such as creating or destroying a database. * * @param attributes the attributes to add to the default attributes, ordered as * name1, value1, name2, value2, ... , nameN, valueN * @return a connection * @throws SQLException if an error occurs */ public Connection getConnection(String... attributes) throws SQLException { if (defaultAttributes == null) throw new IllegalStateException("DBManager has not been configured"); Map finalAttributes = new LinkedHashMap<>(defaultAttributes); Containers.putAll(finalAttributes, (Object[])attributes); return createDataSource(dataSourceClassName, finalAttributes).getConnection(); } /** * Retrieves a pooled connection with the default attributes. *

* Note: in order for the connection to be returned to the pool * it must be {@link java.sql.Connection#close() closed}, for example: *

     * Connection conn = db.getConnection();
     * try {
     *     // run queries...
     * } finally {
     *     conn.close();
     * }
     * 
* * @return a pooled connection * @throws SQLException if an error occurs */ public Connection getConnection() throws SQLException { if (defaultAttributes == null) throw new IllegalStateException("DBManager has not been configured"); if (pool == null) throw new IllegalStateException("DBManager is closed"); try { return pool.getConnection(); } catch (InterruptedException ie) { throw new SQLException("interrupted while getting connection", ie); } catch (TimeoutException te) { throw new SQLException("timeout while getting connection", te); } } /** * Closes the given connection, silently ignoring any errors. * * @param conn the connection to close */ public static void closeSilently(Connection conn) { try { if (conn != null) conn.close(); } catch (SQLException ignore) {} } /** * Closes the given statement, silently ignoring any errors. * * @param statement the statement to close */ public static void closeSilently(Statement statement) { try { if (statement != null) statement.close(); } catch (SQLException ignore) {} } /** * Tests whether the connection is functional. * If not, it must not be used for any further queries. * * @param conn the connection to test * @return true if the connection is functional, false if not */ public static boolean testConnection(Connection conn) { try { return !conn.isClosed() && conn.isValid(0); } catch (Throwable t) { return false; } } /** * Returns a DBRunner on which queries can be run. The runner will retain a * single connection for the transaction, and must be committed/rolled back * and closed by caller. *

* Subclasses can override this method to return a custom DBRunner subclass. * * @return a new DBRunner * @throws SQLException if an error occurs */ public DBRunner transactionRunner() throws SQLException { return new DBRunner(getConnection()); } /** * Returns a DBRunner on which queries can be run. Each query will be run possibly * on a different connection, updates will be committed automatically, and no * resources will be held in between queries. *

* Subclasses can override this method to return a custom DBRunner subclass. * * @return a new DBRunner * @throws SQLException if an error occurs */ public DBRunner runner() throws SQLException { return new DBRunner(this); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy