Skip to content

Instantly share code, notes, and snippets.

@NF1198
Created July 18, 2018 23:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NF1198/76d777e78d45a0322251cd0156608cfd to your computer and use it in GitHub Desktop.
Save NF1198/76d777e78d45a0322251cd0156608cfd to your computer and use it in GitHub Desktop.
SQLiteSingleThreadedManager
/*
* Copyright (c) 2018, tauTerra, LLC; Nicholas Folse
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Gradle:
*
* dependencies {
* compile group: 'org.xerial', name: 'sqlite-jdbc', version: '3.23.1'
* }
*
*/
package com.tauterra.sqlite;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/**
* Usage:
* This class provides a basic framework for a single-threaded interface
* to an SQLite database. To use it, extend the class and call
* submitAndGet(...) or submitAndGetInTransaction(...). Both of these functions
* accept callables, so you can return results as necessary.
* If you're database is write-heavy and you don't need confirmation of writes,
* just remove the .get() statements and return the Futures.
*
* This class also provides convenience methods for beginning, commiting, and
* rolling back transactions. It's safe to use these methods from within
* the submit methods.
*
* Finally, this class provides a map of preparedStatements which you're
* free to use within yor implementation. Call prepareStatement(key, SQL) to
* prepare a statement with the specified SQL. You can retrieve the prepared
* statement by making a call to preparedStatement(key).
* @author Nicholas Folse <https://github.com/NF1198>
*/
public class SingleTreadedSQLiteManager implements AutoCloseable {
private final String connectionUrl;
private final ExecutorService exec;
private Connection conn;
private static String STMT_BEGIN_TRANS = "STMT_BEGIN_TRANS";
private static String STMT_COMMIT_TRANS = "STMT_COMMIT_TRANS";
private static String STMT_ROLLBACK_TRANS = "STMT_ROLLBACK_TRANS";
private Map<String, PreparedStatement> psmap = new HashMap<>();
public SingleTreadedSQLiteManager(String connectionUrl) {
this(Executors.newSingleThreadExecutor(), connectionUrl);
}
public SingleTreadedSQLiteManager(ExecutorService executor, String connectionUrl) {
this.exec = executor;
this.connectionUrl = connectionUrl;
}
protected <T> T submitAndGetInTransaction(Callable<T> task) throws SQLiteManagerException {
try {
ExecutorService e = this.exec;
return e.submit(() -> {
try {
beginTransaction();
T result = task.call();
commitTransaction();
return result;
} catch (Exception ex) {
rollbackTransaction();
throw new SQLiteManagerException("error with transaction", ex);
}
}).get();
} catch (InterruptedException | ExecutionException ex) {
throw new SQLiteManagerException("error executing task", ex);
}
}
protected <T> T submitAndGet(Callable<T> task) throws SQLiteManagerException {
try {
ExecutorService e = this.exec;
return e.submit(task).get();
} catch (InterruptedException | ExecutionException ex) {
throw new SQLiteManagerException("error executing task", ex);
}
}
protected Connection getConnection() throws SQLiteManagerException {
try {
if (this.conn == null || this.conn.isClosed()) {
this.conn = DriverManager.getConnection(this.connectionUrl);
}
return this.conn;
} catch (SQLException ex) {
throw new SQLiteManagerException("error reading connection", ex);
}
}
protected void beginTransaction() throws SQLException {
PreparedStatement stmt = prepareStatement(STMT_BEGIN_TRANS, "BEGIN TRANSACTION");
stmt.execute();
}
protected void commitTransaction() throws SQLException {
PreparedStatement stmt = prepareStatement(STMT_COMMIT_TRANS, "COMMIT TRANSACTION");
stmt.execute();
}
protected void rollbackTransaction() throws SQLiteManagerException {
try {
PreparedStatement stmt = prepareStatement(STMT_ROLLBACK_TRANS, "ROLLBACK TRANSACTION");
stmt.execute();
} catch (SQLException e) {
throw new SQLiteManagerException("error rolling back transaction", e);
}
}
protected PreparedStatement prepareStatement(String tag) throws SQLException {
PreparedStatement stmt = psmap.get(tag);
stmt.clearParameters();
return stmt;
}
protected PreparedStatement prepareStatement(String tag, String SQL) throws SQLException {
if (!psmap.containsKey(tag) || psmap.get(tag).isClosed()) {
Connection c = getConnection();
PreparedStatement stmt = c.prepareStatement(SQL);
psmap.put(tag, stmt);
}
PreparedStatement stmt = psmap.get(tag);
stmt.clearParameters();
return stmt;
}
@Override
public void close() throws Exception {
exec.shutdown();
exec.awaitTermination(120, TimeUnit.SECONDS);
for (PreparedStatement stmt : psmap.values()) {
stmt.close();
}
psmap.clear();
conn.close();
}
public static class SQLiteManagerException extends RuntimeException {
public SQLiteManagerException(String message) {
super(message);
}
public SQLiteManagerException(String message, Throwable cause) {
super(message, cause);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment