Class Database

java.lang.Object
de.murmelmeister.library.database.Database
All Implemented Interfaces:
AutoCloseable

public final class Database extends Object implements AutoCloseable
This class provides a thread-safe interface for managing database connections and executing SQL queries. It uses HikariCP for connection pooling and supports both synchronous and asynchronous operations. The class ensures that all database operations are performed within a transaction context, allowing for rollback in case of errors.
  • Field Details

    • SLOW_QUERY_THRESHOLD_MS

      private static final long SLOW_QUERY_THRESHOLD_MS
      See Also:
    • EXECUTOR_POOL_SIZE

      private static final int EXECUTOR_POOL_SIZE
      See Also:
    • NAME_PATTERN

      private static final Pattern NAME_PATTERN
    • WORKER_THREAD_FACTORY

      private static final ThreadFactory WORKER_THREAD_FACTORY
    • NETWORK_TIMEOUT_THREAD_FACTORY

      private static final ThreadFactory NETWORK_TIMEOUT_THREAD_FACTORY
    • logger

      private final org.slf4j.Logger logger
    • lock

      private final ReadWriteLock lock
    • executor

      private volatile ExecutorService executor
    • networkTimeoutExecutor

      private volatile ExecutorService networkTimeoutExecutor
    • dataSource

      private volatile com.zaxxer.hikari.HikariDataSource dataSource
    • transactionIsolationLevel

      private volatile Integer transactionIsolationLevel
  • Constructor Details

    • Database

      public Database()
  • Method Details

    • connect

      public void connect(com.zaxxer.hikari.HikariConfig config)
      Establishes a database connection using the provided HikariConfig. Configures and initializes the data source and executor as needed. Ensures thread-safe connection setup by acquiring a write lock during the configuration process. Logs a success message upon establishing the connection or throws an exception in case of failure.
      Parameters:
      config - The configuration object for setting up the HikariCP data source
      Throws:
      DatabaseException - If the connection setup fails, the lock cannot be acquired, or the thread is interrupted
    • connect

      public void connect(String propertyFileName)
      Establishes a connection to the database using the properties defined in the specified file. This method creates a HikariConfig object from the given file and delegates the connection setup to another method for further processing.
      Parameters:
      propertyFileName - The name of the file containing database configuration properties. Must not be null or empty. The file should include necessary configurations such as JDBC URL, username, and password.
    • connect

      public void connect(Properties properties)
      Establishes a connection to the database using the provided properties. Creates a HikariConfig object from the properties and delegates the connection setup to another method.
      Parameters:
      properties - The property object containing database configuration parameters. Must not be null and should include the necessary information such as JDBC URL, username, and password.
    • connect

      public void connect(String url, String username, String password)
      Establishes a connection to the database using the specified URL, username, and password. Creates a HikariConfig object configured with the provided parameters and delegates the connection setup to another method.
      Parameters:
      url - The JDBC URL of the database. Must not be null or empty.
      username - The username for the database connection. Must not be null or empty.
      password - The password for the database connection. Must not be null or empty.
    • setTransactionIsolationLevel

      public void setTransactionIsolationLevel(Integer isolationLevel)
      Configures the transaction isolation level used for all managed transactions. A null value restores the driver's default isolation level.
      Parameters:
      isolationLevel - The desired isolation level, typically one of the Connection TRANSACTION_* constants, or null to use the default.
    • disconnect

      public void disconnect()
      Closes the database connection and releases associated resources. This method ensures proper cleanup of the database connection, thread pool, and other associated components. It also manages concurrency with a write lock.

      The method attempts to:

      1. Acquire a write lock to ensure thread-safe closure of resources.

      2. Close and nullify the data source if it is not already closed.

      3. Shut down the thread pool executor, ensuring all tasks are completed or terminated.

      Exceptions are handled to maintain the application's stability, logging, and propagating issues where necessary.

      Throws:
      DatabaseException - If the write lock cannot be acquired within the timeout, or if there are errors while closing the data source.
    • close

      public void close()
      Closes the database, releasing any pooled connections and shutting down internal executors. This method is equivalent to disconnect() and allows usage with try-with-resources.
      Specified by:
      close in interface AutoCloseable
    • query

      public <T> T query(String sql, T fallback, ResultSetProcessor<T> processor, ParameterProcessor parameters)
      Executes the provided SQL query and processes the resulting ResultSet using the given ResultSetProcessor. If no result is found, a fallback value is returned.
      Type Parameters:
      T - The type of the result returned by the processor.
      Parameters:
      sql - The SQL query string to be executed.
      fallback - The value to return if no rows are found in the result set
      processor - The ResultSetProcessor that processes the ResultSet and transforms it into the desired result type.
      parameters - The ParameterProcessor used to set parameters in the prepared statement.
      Returns:
      The processed result from the query if it yields a result, or the fallback value if no results are found.
      Throws:
      DatabaseException - If an SQL exception occurs during query execution.
    • queryCallable

      public <T> T queryCallable(String sql, T fallback, ResultSetProcessor<T> processor, ParameterProcessor parameters)
      Executes a callable SQL query processes the resulting ResultSet using the given ResultSetProcessor. If no result is found, a fallback value is returned.
      Type Parameters:
      T - The type of the result returned by the processor.
      Parameters:
      sql - The SQL query to execute as a callable statement.
      fallback - The value to return if no rows are found in the result set.
      processor - A ResultSetProcessor to process the ResultSet and convert it into the desired type
      parameters - A ParameterProcessor for setting parameters on the CallableStatement
      Returns:
      An object of type T representing the processed result, or the fallback value if no rows were found
      Throws:
      DatabaseException - If the query fails to execute due to an SQLException
    • queryAsync

      public <T> CompletableFuture<T> queryAsync(String sql, T fallback, ResultSetProcessor<T> processor, ParameterProcessor parameters)
      Asynchronously executes a query with the provided SQL and parameters, processes the result set using the provided ResultSetProcessor, and returns a CompletableFuture containing the result of type T.
      Type Parameters:
      T - The type of the result to be returned.
      Parameters:
      sql - The SQL query to be executed; must not be null or empty.
      fallback - A fallback value to return if no results are found.
      processor - A processor responsible for converting the result set into an instance of type T.
      parameters - The parameter processor for managing query parameters.
      Returns:
      A CompletableFuture containing the processed result of type T or the fallback value if no results are found.
    • queryCallableAsync

      public <T> CompletableFuture<T> queryCallableAsync(String sql, T fallback, ResultSetProcessor<T> processor, ParameterProcessor parameters)
      Asynchronously executes a callable statement with the specified procedure name and parameters, processes the result set using the provided ResultSetProcessor, and returns a CompletableFuture containing the result of type T.
      Type Parameters:
      T - The type of the result to be returned.
      Parameters:
      sql - The SQL query to execute.
      fallback - A fallback value to return if no results are found.
      processor - A processor responsible for converting the result set into an instance of type T.
      parameters - The processor that manages SQL parameters for the query.
      Returns:
      A CompletableFuture containing the processed result of type T or the fallback value if no results are found.
    • queryList

      public <T> List<T> queryList(String sql, ResultSetProcessor<T> processor, ParameterProcessor parameters)
      Executes a query with the provided SQL and parameters, processes the result set using the provided ResultSetProcessor, and returns a list of results of type T.
      Type Parameters:
      T - The type of the elements to be returned in the result list.
      Parameters:
      sql - The SQL query to be executed; must not be null or empty.
      processor - A processor responsible for converting each row in the result set into an instance of type T.
      parameters - The parameters to be passed to the prepared statement.
      Returns:
      A list of results of type T obtained by processing the result set returned by the query.
      Throws:
      DatabaseException - If there is an error during execution of the query.
    • queryListCallable

      public <T> List<T> queryListCallable(String sql, ResultSetProcessor<T> processor, ParameterProcessor parameters)
      Executes the given SQL callable statement, processes the result set, and returns a list of objects.
      Type Parameters:
      T - The type of objects to be returned in the list
      Parameters:
      sql - The SQL callable statement to execute
      processor - The result set processor used to map the result set rows to objects of type T
      parameters - The parameter processor used to bind parameters to the statement
      Returns:
      A list of objects of type T created by processing the result set
      Throws:
      DatabaseException - If an SQL exception occurs while executing the query
    • queryListAsync

      public <T> CompletableFuture<List<T>> queryListAsync(String sql, ResultSetProcessor<T> processor, ParameterProcessor parameters)
      Asynchronously executes a query with the provided SQL and parameters, processes the result set using the provided ResultSetProcessor, and returns a CompletableFuture containing a list of results of type T.
      Type Parameters:
      T - The type of the elements to be returned in the result list.
      Parameters:
      sql - The SQL query to be executed; must not be null or empty.
      processor - A processor responsible for converting each row in the result set into an instance of type T.
      parameters - A processor that handles the parameters for the SQL query.
      Returns:
      A CompletableFuture containing a list of results of type T obtained by processing the result set returned by the query.
    • queryListCallableAsync

      public <T> CompletableFuture<List<T>> queryListCallableAsync(String sql, ResultSetProcessor<T> processor, ParameterProcessor parameters)
      Executes a SQL query asynchronously and processes the returned result set into a list of objects.
      Type Parameters:
      T - The type of objects in the resulting list
      Parameters:
      sql - The SQL query to execute
      processor - The processor used to convert each row of the result set into an object of type T
      parameters - The processor used to set the parameters for the SQL query
      Returns:
      A CompletableFuture containing a list of objects of type T resulting from the query
    • exists

      public boolean exists(String sql, ParameterProcessor parameters)
      Checks if a query, specified by the SQL string and processed parameters, returns any results. Executes the given SQL query using a prepared statement and checks if the result set contains at least one row.
      Parameters:
      sql - The SQL query string to execute
      parameters - The ParameterProcessor used to process the parameters of the SQL query
      Returns:
      true if the query result contains at least one row, false otherwise
      Throws:
      DatabaseException - If a database access error occurs during the execution of the query
    • existsCallable

      public boolean existsCallable(String sql, ParameterProcessor parameters)
      Executes a callable SQL query to check if any results exist.
      Parameters:
      sql - The SQL query to execute, which must be a callable statement
      parameters - An object that processes and sets the parameters for the callable statement
      Returns:
      true if the query result contains at least one row, false otherwise
      Throws:
      DatabaseException - If a database access error occurs during the execution of the query
    • existsAsync

      public CompletableFuture<Boolean> existsAsync(String sql, ParameterProcessor parameters)
      Asynchronously checks if a record exists in the database by executing a query with the provided SQL and parameters. This method is useful for verifying the existence of a record without returning any data.
      Parameters:
      sql - The SQL query to check the existence of a record
      parameters - The processor for the parameters to be applied in the SQL query
      Returns:
      A CompletableFuture which resolves to a Boolean indicating whether a record exists (true) or not (false)
    • existsCallableAsync

      public CompletableFuture<Boolean> existsCallableAsync(String sql, ParameterProcessor parameters)
      Asynchronously checks if a stored procedure exists in the database by executing it with the provided parameters. This method is useful for verifying the existence of a procedure without returning any data.
      Parameters:
      sql - The SQL query to evaluate for the callable statement
      parameters - The processor to handle the parameters for the callable query
      Returns:
      A CompletableFuture containing a Boolean value indicating whether the callable statement exists (true) or not (false)
    • getAutoIncrement

      public CompletableFuture<Long> getAutoIncrement(String tableName)
      Retrieves the auto-increment value for the specified table. The table name must consist of letters, digits, and underscores only.
      Parameters:
      tableName - The name of the table to retrieve the auto-increment value for; must not be null or empty.
      Returns:
      A CompletableFuture containing the auto-increment value, or null if the table does not exist.
      Throws:
      DatabaseException - If the table name is invalid, or if an error occurs during the operation.
    • update

      public int update(String sql, ParameterProcessor parameters)
      Executes an update operation on the database using the provided SQL statement and parameter processor within a transactional context.
      Parameters:
      sql - The SQL update query to be executed
      parameters - The processor used to prepare the statement with necessary parameters
      Returns:
      The number of rows affected by the update operation
    • update

      public int update(String sql)
      Executes the given SQL update statement.
      Parameters:
      sql - The SQL statement to execute, typically an update, insert, or delete statement
      Returns:
      The number of rows affected by the SQL statement
    • updateAndGetGeneratedKeys

      public long updateAndGetGeneratedKeys(String sql, ParameterProcessor parameters)
      Executes the given SQL update statement within a transaction and retrieves the generated key for the updated record.
      Parameters:
      sql - The SQL update statement to be executed
      parameters - The parameter processor for preparing the SQL statement
      Returns:
      The generated key as a long value, or 0 if no keys were generated.
    • updateCallable

      public int updateCallable(String sql, ParameterProcessor parameters)
      Executes a database update operation using a callable SQL statement within a transaction.
      Parameters:
      sql - The SQL string for the callable statement to execute
      parameters - An object implementing ParameterProcessor to process and set parameters for the callable statement
      Returns:
      The number of rows affected by the update operation
    • updateBatch

      public int[] updateBatch(String sql, ParameterProcessor parameters)
      Executes a batch update using the provided SQL query and parameter processor and returns an array of update counts indicating the number of rows affected by each batch statement.
      Parameters:
      sql - The SQL query to be executed in batch; must not be null or empty
      parameters - A processor that applies parameters to the SQL query for batch execution; must not be null
      Returns:
      An array of update counts containing the number of rows affected for each statement in the batch
    • createTable

      public int createTable(String name, String columns)
      Creates a new table in the database with the specified name and columns.
      Parameters:
      name - The name of the table to be created. Must follow naming restrictions allowing only alphanumeric characters and underscores.
      columns - The column definitions for the table, formatted as a comma-separated list.
      Returns:
      The number of rows affected by the execution of the SQL statement.
      Throws:
      DatabaseException - If the table name is invalid.
    • runSqlScript

      public void runSqlScript(Path scriptPath)
      Executes a SQL script from the given file path. The script is split on semicolons while respecting single and double-quoted strings as well as line and block comments. All statements are executed within a single transaction, rolling back entirely if any statement fails.
      Parameters:
      scriptPath - The path to the SQL script file
    • runSqlScript

      public void runSqlScript(Reader scriptReader)
      Executes a SQL script provided via Reader. Semicolons separate statements outside quoted strings. Line comments starting with -- and block comments between /* and *\/ are ignored. All statements run inside a single transaction.
      Parameters:
      scriptReader - The reader supplying the SQL script content
    • executeInTransaction

      private <T> T executeInTransaction(Database.ConnectionOperation<T> operation)
      Executes a database operation within a transactional context. This method manages the transaction lifecycle, including beginning, committing, and rolling back the transaction if an exception occurs. It also ensures the connection's state is properly restored after the operation.
      Type Parameters:
      T - The type of result returned by the operation
      Parameters:
      operation - The database operation to execute, represented as a ConnectionOperation<T>
      Returns:
      The result of the database operation
      Throws:
      DatabaseException - If the database operation fails or an error occurs while managing the transaction
    • getCallableStatement

      private CallableStatement getCallableStatement(Connection connection, String sql, ParameterProcessor parameters) throws SQLException
      Prepares a CallableStatement using the provided database connection and SQL string, then applies parameter operations to the statement.
      Parameters:
      connection - The active database connection used to prepare the CallableStatement
      sql - The SQL string used to create the CallableStatement
      parameters - The operations to apply to the CallableStatement for parameter configuration
      Returns:
      The CallableStatement with the applied parameter operations
      Throws:
      SQLException - If a database access error occurs or the SQL string is invalid
    • getBatchStatement

      private PreparedStatement getBatchStatement(Connection connection, String sql, ParameterProcessor parameters) throws SQLException
      Prepares a batch SQL statement using the provided connection, SQL query, and parameter operation. Remember to use statement.addBatch() to add each batch statement to the prepared statement.
      Parameters:
      connection - The database connection to be used for preparing the statement.
      sql - The SQL query string to prepare as a batch statement.
      parameters - A functional operation to apply parameters to the prepared statement.
      Returns:
      The prepared batch SQL statement after applying the parameter operation.
      Throws:
      SQLException - If an error occurs while preparing the statement or applying the parameters.
    • getGeneratedKeysStatement

      private PreparedStatement getGeneratedKeysStatement(Connection connection, String sql, ParameterProcessor parameters) throws SQLException
      Prepares a PreparedStatement capable of returning generated keys using the provided SQL query. A given parameter operation is applied to the prepared statement before returning it.
      Parameters:
      connection - The database connection to be used for preparing the PreparedStatement. Must not be null and should represent a valid, open connection.
      sql - The SQL query string to prepare the PreparedStatement with. Must not be null or empty.
      parameters - An operation defining how to set parameters and operate on the PreparedStatement.
      Returns:
      The prepared and parameterized PreparedStatement ready for execution, configured to return generated keys.
      Throws:
      SQLException - If an error occurs while preparing or handling the PreparedStatement.
    • getPreparedStatement

      private PreparedStatement getPreparedStatement(Connection connection, String sql, ParameterProcessor parameters) throws SQLException
      Prepares a PreparedStatement using the provided SQL and connection, and applies a parameter operation on the prepared statement before returning it.
      Parameters:
      connection - The database connection to be used for preparing the PreparedStatement. Must not be null and should represent a valid, open connection.
      sql - The SQL query string to prepare the PreparedStatement with. Must not be null or empty.
      parameters - An operation defining how to set parameters and execute the PreparedStatement.
      Returns:
      The prepared and parameterized PreparedStatement ready for execution.
      Throws:
      SQLException - If an error occurs while preparing or handling the PreparedStatement.
    • createExecutor

      private ExecutorService createExecutor()
      Creates and returns a fixed thread pool executor service with a predefined pool size.
      Returns:
      An instance of ExecutorService configured with a fixed thread pool
    • createNetworkTimeoutExecutor

      private ExecutorService createNetworkTimeoutExecutor()
      Creates the executor used for JDBC network timeout callbacks.
      Returns:
      An executor service dedicated to network timeout tasks
    • requireExecutor

      private ExecutorService requireExecutor()
      Ensures that an active ExecutorService is available. This method checks if the current executor service is not null and not shut down. If the executor service is unavailable, an DatabaseException is thrown.
      Returns:
      The active ExecutorService instance.
      Throws:
      DatabaseException - If the executor service is null or has been shut down.
    • requireNetworkTimeoutExecutor

      private ExecutorService requireNetworkTimeoutExecutor()
      Ensures that an active executor for network timeout callbacks is available.
      Returns:
      The executor dedicated to network timeout tasks
      Throws:
      DatabaseException - If the executor is unavailable
    • requireDataSource

      private com.zaxxer.hikari.HikariDataSource requireDataSource()
      Ensures that a data source is available and returns the current HikariDataSource. If no data source is available, an DatabaseException is thrown.
      Returns:
      The currently configured HikariDataSource
      Throws:
      DatabaseException - If no data source is connected
    • normalizeIsolationLevel

      private int normalizeIsolationLevel(int isolationLevel)
      Validates and returns the supplied isolation level if it matches one of the JDBC constants.
      Parameters:
      isolationLevel - The isolation level to validate
      Returns:
      The same isolation level when valid
      Throws:
      DatabaseException - If the isolation level is not a supported JDBC constant
    • logSlowQuery

      private void logSlowQuery(long startTime, String sql)
      Logs a warning if a database query took longer than the defined slow query threshold.
      Parameters:
      startTime - The start time of the query in nanoseconds
      sql - The SQL query that was executed
    • shutdownExecutor

      private void shutdownExecutor(ExecutorService executorService)
      Shuts down the provided executor, waiting briefly for tasks to finish and forcing termination if necessary.
      Parameters:
      executorService - The executor to shut down; may be null
    • executeSqlStatement

      private void executeSqlStatement(Statement statement, StringBuilder buffer) throws SQLException
      Executes the SQL contained in the provided buffer if it is non-empty.
      Parameters:
      statement - The JDBC statement used for execution
      buffer - The buffer holding the SQL statement
      Throws:
      SQLException - If execution fails
    • closeDataSourceQuietly

      private void closeDataSourceQuietly(com.zaxxer.hikari.HikariDataSource source)
      Closes the given data source, logging but ignoring any exceptions.
      Parameters:
      source - The HikariDataSource to close; may be null
    • getProcedureQuery

      public static String getProcedureQuery(String name, String parameters, String body)
      Generates a SQL query string for creating a stored procedure in the database. The procedure is created only if it does not already exist. The query includes the procedure name, its parameters, and the procedure body.
      Parameters:
      name - The name of the procedure. Must match the required naming pattern. If the name is invalid, an DatabaseException is thrown.
      parameters - A string specifying the parameters of the procedure, including types. This string is directly appended to the generated SQL.
      body - The body of the procedure, containing the SQL logic to be executed within the procedure.
      Returns:
      A SQL string representing the creation of the specified stored procedure.
      Throws:
      DatabaseException - If the procedure name does not match the expected pattern.