/** * Internal iterator so we can page through the class. This is used by the {@link Dao#iterator} * methods. * * @param <T> The class that the code will be operating on. * @param <ID> The class of the ID column associated with the class. The T class does not require an * ID field. The class needs an ID parameter however so you can use Void or Object to satisfy * the compiler. * @author graywatson */ public class SelectIterator<T, ID> implements CloseableIterator<T> { private static final Logger logger = LoggerFactory.getLogger(SelectIterator.class); private final Class<?> dataClass; private final Dao<T, ID> classDao; private final ConnectionSource connectionSource; private final DatabaseConnection connection; private final CompiledStatement compiledStmt; private final DatabaseResults results; private final GenericRowMapper<T> rowMapper; private final String statement; private boolean first = true; private boolean closed; private boolean alreadyMoved; private T last; private int rowC; /** If the statement parameter is null then this won't log information */ public SelectIterator( Class<?> dataClass, Dao<T, ID> classDao, GenericRowMapper<T> rowMapper, ConnectionSource connectionSource, DatabaseConnection connection, CompiledStatement compiledStmt, String statement, ObjectCache objectCache) throws SQLException { this.dataClass = dataClass; this.classDao = classDao; this.rowMapper = rowMapper; this.connectionSource = connectionSource; this.connection = connection; this.compiledStmt = compiledStmt; this.results = compiledStmt.runQuery(objectCache); this.statement = statement; if (statement != null) { logger.debug("starting iterator @{} for '{}'", hashCode(), statement); } } /** * Returns whether or not there are any remaining objects in the table. Can be called before * next(). * * @throws SQLException If there was a problem getting more results via SQL. */ public boolean hasNextThrow() throws SQLException { if (closed) { return false; } if (alreadyMoved) { // we do this so multiple hasNext() calls can be made, result would be true or closed is true return true; } boolean result; if (first) { first = false; result = results.first(); } else { result = results.next(); } if (!result) { close(); } alreadyMoved = true; return result; } /** * Returns whether or not there are any remaining objects in the table. Can be called before * next(). * * @throws IllegalStateException If there was a problem getting more results via SQL. */ public boolean hasNext() { try { return hasNextThrow(); } catch (SQLException e) { last = null; closeQuietly(); // unfortunately, can't propagate back the SQLException throw new IllegalStateException("Errors getting more results of " + dataClass, e); } } public T first() throws SQLException { if (closed) { return null; } first = false; if (results.first()) { return getCurrent(); } else { return null; } } public T previous() throws SQLException { if (closed) { return null; } first = false; if (results.previous()) { return getCurrent(); } else { return null; } } public T current() throws SQLException { if (closed) { return null; } if (first) { return first(); } else { return getCurrent(); } } public T nextThrow() throws SQLException { if (closed) { return null; } if (!alreadyMoved) { boolean hasResult; if (first) { first = false; hasResult = results.first(); } else { hasResult = results.next(); } // move forward if (!hasResult) { first = false; return null; } } first = false; return getCurrent(); } /** * Returns the next object in the table. * * @throws IllegalStateException If there was a problem extracting the object from SQL. */ public T next() { SQLException sqlException = null; try { T result = nextThrow(); if (result != null) { return result; } } catch (SQLException e) { sqlException = e; } // we have to throw if there is no next or on a SQLException last = null; closeQuietly(); throw new IllegalStateException("Could not get next result for " + dataClass, sqlException); } public T moveRelative(int offset) throws SQLException { if (closed) { return null; } first = false; if (results.moveRelative(offset)) { return getCurrent(); } else { return null; } } /** * Removes the last object returned by next() by calling delete on the dao associated with the * object. * * @throws IllegalStateException If there was no previous next() call. * @throws SQLException If the delete failed. */ public void removeThrow() throws SQLException { if (last == null) { throw new IllegalStateException( "No last " + dataClass + " object to remove. Must be called after a call to next."); } if (classDao == null) { // we may never be able to get here since it should only be null for queryForAll methods throw new IllegalStateException( "Cannot remove " + dataClass + " object because classDao not initialized"); } try { classDao.delete(last); } finally { // if we've try to delete it, clear the last marker last = null; } } /** * Removes the last object returned by next() by calling delete on the dao associated with the * object. * * @throws IllegalStateException If there was no previous next() call or if delete() throws a * SQLException (set as the cause). */ public void remove() { try { removeThrow(); } catch (SQLException e) { closeQuietly(); // unfortunately, can't propagate back the SQLException throw new IllegalStateException("Could not delete " + dataClass + " object " + last, e); } } public void close() throws SQLException { if (!closed) { compiledStmt.close(); closed = true; last = null; if (statement != null) { logger.debug("closed iterator @{} after {} rows", hashCode(), rowC); } connectionSource.releaseConnection(connection); } } public void closeQuietly() { try { close(); } catch (SQLException e) { // ignore it } } public DatabaseResults getRawResults() { return results; } public void moveToNext() { last = null; first = false; alreadyMoved = false; } private T getCurrent() throws SQLException { last = rowMapper.mapRow(results); alreadyMoved = false; rowC++; return last; } }
/** * Database connection for Android. * * @author kevingalligan, graywatson */ public class AndroidDatabaseConnection implements DatabaseConnection { private static final String ANDROID_VERSION = "VERSION__4.49-SNAPSHOT__"; private static Logger logger = LoggerFactory.getLogger(AndroidDatabaseConnection.class); private static final String[] NO_STRING_ARGS = new String[0]; private final SQLiteDatabase db; private final boolean readWrite; private final boolean cancelQueriesEnabled; static { VersionUtils.checkCoreVersusAndroidVersions(ANDROID_VERSION); } public AndroidDatabaseConnection(SQLiteDatabase db, boolean readWrite) { this(db, readWrite, false); } public AndroidDatabaseConnection( SQLiteDatabase db, boolean readWrite, boolean cancelQueriesEnabled) { this.db = db; this.readWrite = readWrite; this.cancelQueriesEnabled = cancelQueriesEnabled; logger.trace("{}: db {} opened, read-write = {}", this, db, readWrite); } public boolean isAutoCommitSupported() { return true; } public boolean isAutoCommit() throws SQLException { try { boolean inTransaction = db.inTransaction(); logger.trace("{}: in transaction is {}", this, inTransaction); // You have to explicitly commit your transactions, so this is sort of correct return !inTransaction; } catch (android.database.SQLException e) { throw SqlExceptionUtil.create("problems getting auto-commit from database", e); } } public void setAutoCommit(boolean autoCommit) { /* * Sqlite does not support auto-commit. The various JDBC drivers seem to implement it with the use of a * transaction. That's what we are doing here. */ if (autoCommit) { if (db.inTransaction()) { db.setTransactionSuccessful(); db.endTransaction(); } } else { if (!db.inTransaction()) { db.beginTransaction(); } } } public Savepoint setSavePoint(String name) throws SQLException { try { db.beginTransaction(); logger.trace("{}: save-point set with name {}", this, name); return new OurSavePoint(name); } catch (android.database.SQLException e) { throw SqlExceptionUtil.create("problems beginning transaction " + name, e); } } /** Return whether this connection is read-write or not (real-only). */ public boolean isReadWrite() { return readWrite; } public void commit(Savepoint savepoint) throws SQLException { try { db.setTransactionSuccessful(); db.endTransaction(); if (savepoint == null) { logger.trace("{}: transaction is successfuly ended", this); } else { logger.trace("{}: transaction {} is successfuly ended", this, savepoint.getSavepointName()); } } catch (android.database.SQLException e) { if (savepoint == null) { throw SqlExceptionUtil.create("problems commiting transaction", e); } else { throw SqlExceptionUtil.create( "problems commiting transaction " + savepoint.getSavepointName(), e); } } } public void rollback(Savepoint savepoint) throws SQLException { try { // no setTransactionSuccessful() means it is a rollback db.endTransaction(); if (savepoint == null) { logger.trace("{}: transaction is ended, unsuccessfuly", this); } else { logger.trace( "{}: transaction {} is ended, unsuccessfuly", this, savepoint.getSavepointName()); } } catch (android.database.SQLException e) { if (savepoint == null) { throw SqlExceptionUtil.create("problems rolling back transaction", e); } else { throw SqlExceptionUtil.create( "problems rolling back transaction " + savepoint.getSavepointName(), e); } } } public int executeStatement(String statementStr, int resultFlags) throws SQLException { return AndroidCompiledStatement.execSql(db, statementStr, statementStr, NO_STRING_ARGS); } public CompiledStatement compileStatement( String statement, StatementType type, FieldType[] argFieldTypes, int resultFlags) { // resultFlags argument is not used in Android-land since the {@link Cursor} is bi-directional. CompiledStatement stmt = new AndroidCompiledStatement(statement, db, type, cancelQueriesEnabled); logger.trace("{}: compiled statement got {}: {}", this, stmt, statement); return stmt; } public int insert( String statement, Object[] args, FieldType[] argFieldTypes, GeneratedKeyHolder keyHolder) throws SQLException { SQLiteStatement stmt = null; try { stmt = db.compileStatement(statement); bindArgs(stmt, args, argFieldTypes); long rowId = stmt.executeInsert(); if (keyHolder != null) { keyHolder.addKey(rowId); } /* * I've decided to not do the CHANGES() statement here like we do down below in UPDATE because we know that * it worked (since it didn't throw) so we know that 1 is right. */ int result = 1; logger.trace( "{}: insert statement is compiled and executed, changed {}: {}", this, result, statement); return result; } catch (android.database.SQLException e) { throw SqlExceptionUtil.create("inserting to database failed: " + statement, e); } finally { closeQuietly(stmt); } } public int update(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException { return update(statement, args, argFieldTypes, "updated"); } public int delete(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException { // delete is the same as update return update(statement, args, argFieldTypes, "deleted"); } public <T> Object queryForOne( String statement, Object[] args, FieldType[] argFieldTypes, GenericRowMapper<T> rowMapper, ObjectCache objectCache) throws SQLException { Cursor cursor = null; try { cursor = db.rawQuery(statement, toStrings(args)); AndroidDatabaseResults results = new AndroidDatabaseResults(cursor, objectCache); logger.trace("{}: queried for one result: {}", this, statement); if (!results.first()) { return null; } else { T first = rowMapper.mapRow(results); if (results.next()) { return MORE_THAN_ONE; } else { return first; } } } catch (android.database.SQLException e) { throw SqlExceptionUtil.create("queryForOne from database failed: " + statement, e); } finally { closeQuietly(cursor); } } public long queryForLong(String statement) throws SQLException { SQLiteStatement stmt = null; try { stmt = db.compileStatement(statement); long result = stmt.simpleQueryForLong(); logger.trace("{}: query for long simple query returned {}: {}", this, result, statement); return result; } catch (android.database.SQLException e) { throw SqlExceptionUtil.create("queryForLong from database failed: " + statement, e); } finally { closeQuietly(stmt); } } public long queryForLong(String statement, Object[] args, FieldType[] argFieldTypes) throws SQLException { Cursor cursor = null; AndroidDatabaseResults results = null; try { cursor = db.rawQuery(statement, toStrings(args)); results = new AndroidDatabaseResults(cursor, null); long result; if (results.first()) { result = results.getLong(0); } else { result = 0L; } logger.trace("{}: query for long raw query returned {}: {}", this, result, statement); return result; } catch (android.database.SQLException e) { throw SqlExceptionUtil.create("queryForLong from database failed: " + statement, e); } finally { closeQuietly(cursor); IOUtils.closeQuietly(results); } } public void close() throws IOException { try { db.close(); logger.trace("{}: db {} closed", this, db); } catch (android.database.SQLException e) { throw new IOException("problems closing the database connection", e); } } public void closeQuietly() { IOUtils.closeQuietly(this); } public boolean isClosed() throws SQLException { try { boolean isOpen = db.isOpen(); logger.trace("{}: db {} isOpen returned {}", this, db, isOpen); return !isOpen; } catch (android.database.SQLException e) { throw SqlExceptionUtil.create("problems detecting if the database is closed", e); } } public boolean isTableExists(String tableName) { Cursor cursor = db.rawQuery( "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name = '" + tableName + "'", null); try { boolean result; if (cursor.getCount() > 0) { result = true; } else { result = false; } logger.trace("{}: isTableExists '{}' returned {}", this, tableName, result); return result; } finally { cursor.close(); } } private int update(String statement, Object[] args, FieldType[] argFieldTypes, String label) throws SQLException { SQLiteStatement stmt = null; try { stmt = db.compileStatement(statement); bindArgs(stmt, args, argFieldTypes); stmt.execute(); } catch (android.database.SQLException e) { throw SqlExceptionUtil.create("updating database failed: " + statement, e); } finally { closeQuietly(stmt); stmt = null; } int result; try { stmt = db.compileStatement("SELECT CHANGES()"); result = (int) stmt.simpleQueryForLong(); } catch (android.database.SQLException e) { // ignore the exception and just return 1 result = 1; } finally { closeQuietly(stmt); } logger.trace("{} statement is compiled and executed, changed {}: {}", label, result, statement); return result; } private void bindArgs(SQLiteStatement stmt, Object[] args, FieldType[] argFieldTypes) throws SQLException { if (args == null) { return; } for (int i = 0; i < args.length; i++) { Object arg = args[i]; if (arg == null) { stmt.bindNull(i + 1); } else { SqlType sqlType = argFieldTypes[i].getSqlType(); switch (sqlType) { case STRING: case LONG_STRING: case CHAR: stmt.bindString(i + 1, arg.toString()); break; case BOOLEAN: case BYTE: case SHORT: case INTEGER: case LONG: stmt.bindLong(i + 1, ((Number) arg).longValue()); break; case FLOAT: case DOUBLE: stmt.bindDouble(i + 1, ((Number) arg).doubleValue()); break; case BYTE_ARRAY: case SERIALIZABLE: stmt.bindBlob(i + 1, (byte[]) arg); break; case DATE: // this is mapped to a STRING under Android case BLOB: // this is only for derby serializable case BIG_DECIMAL: // this should be handled as a STRING throw new SQLException("Invalid Android type: " + sqlType); case UNKNOWN: default: throw new SQLException("Unknown sql argument type: " + sqlType); } } } } private String[] toStrings(Object[] args) { if (args == null || args.length == 0) { return null; } String[] strings = new String[args.length]; for (int i = 0; i < args.length; i++) { Object arg = args[i]; if (arg == null) { strings[i] = null; } else { strings[i] = arg.toString(); } } return strings; } @Override public String toString() { return getClass().getSimpleName() + "@" + Integer.toHexString(super.hashCode()); } /** We can't use IOUtils here because older versions didn't implement Closeable. */ private void closeQuietly(Cursor cursor) { if (cursor != null) { cursor.close(); } } /** We can't use IOUtils here because older versions didn't implement Closeable. */ private void closeQuietly(SQLiteStatement statement) { if (statement != null) { statement.close(); } } private static class OurSavePoint implements Savepoint { private String name; public OurSavePoint(String name) { this.name = name; } public int getSavepointId() { return 0; } public String getSavepointName() { return name; } } }
/** * Proxy to a {@link Dao} that wraps each Exception and rethrows it as RuntimeException. You can use * this if your usage pattern is to ignore all exceptions. That's not a pattern that I like so it's * not the default. * * <pre> * RuntimeExceptionDao<Account, String> accountDao = RuntimeExceptionDao.createDao(connectionSource, Account.class); * </pre> * * @author graywatson */ public class RuntimeExceptionDao<T, ID> implements CloseableIterable<T> { /* * We use debug here because we don't want these messages to be logged by default. The user will need to turn on * logging for this class to DEBUG to see the messages. */ private static final Level LOG_LEVEL = Level.DEBUG; private Dao<T, ID> dao; private static final Logger logger = LoggerFactory.getLogger(RuntimeExceptionDao.class); public RuntimeExceptionDao(Dao<T, ID> dao) { this.dao = dao; } /** * Call through to {@link DaoManager#createDao(ConnectionSource, Class)} with the returned DAO * wrapped in a RuntimeExceptionDao. */ public static <T, ID> RuntimeExceptionDao<T, ID> createDao( ConnectionSource connectionSource, Class<T> clazz) throws SQLException { @SuppressWarnings("unchecked") Dao<T, ID> castDao = (Dao<T, ID>) DaoManager.createDao(connectionSource, clazz); return new RuntimeExceptionDao<T, ID>(castDao); } /** * Call through to {@link DaoManager#createDao(ConnectionSource, DatabaseTableConfig)} with the * returned DAO wrapped in a RuntimeExceptionDao. */ public static <T, ID> RuntimeExceptionDao<T, ID> createDao( ConnectionSource connectionSource, DatabaseTableConfig<T> tableConfig) throws SQLException { @SuppressWarnings("unchecked") Dao<T, ID> castDao = (Dao<T, ID>) DaoManager.createDao(connectionSource, tableConfig); return new RuntimeExceptionDao<T, ID>(castDao); } /** @see Dao#queryForId(Object) */ public T queryForId(ID id) { try { return dao.queryForId(id); } catch (SQLException e) { logMessage(e, "queryForId threw exception on: " + id); throw new RuntimeException(e); } } /** @see Dao#queryForFirst(PreparedQuery) */ public T queryForFirst(PreparedQuery<T> preparedQuery) { try { return dao.queryForFirst(preparedQuery); } catch (SQLException e) { logMessage(e, "queryForFirst threw exception on: " + preparedQuery); throw new RuntimeException(e); } } /** @see Dao#queryForAll() */ public List<T> queryForAll() { try { return dao.queryForAll(); } catch (SQLException e) { logMessage(e, "queryForAll threw exception"); throw new RuntimeException(e); } } /** @see Dao#queryForEq(String, Object) */ public List<T> queryForEq(String fieldName, Object value) { try { return dao.queryForEq(fieldName, value); } catch (SQLException e) { logMessage(e, "queryForEq threw exception on: " + fieldName); throw new RuntimeException(e); } } /** @see Dao#queryForMatching(Object) */ public List<T> queryForMatching(T matchObj) { try { return dao.queryForMatching(matchObj); } catch (SQLException e) { logMessage(e, "queryForMatching threw exception on: " + matchObj); throw new RuntimeException(e); } } /** @see Dao#queryForMatchingArgs(Object) */ public List<T> queryForMatchingArgs(T matchObj) { try { return dao.queryForMatchingArgs(matchObj); } catch (SQLException e) { logMessage(e, "queryForMatchingArgs threw exception on: " + matchObj); throw new RuntimeException(e); } } /** @see Dao#queryForFieldValues(Map) */ public List<T> queryForFieldValues(Map<String, Object> fieldValues) { try { return dao.queryForFieldValues(fieldValues); } catch (SQLException e) { logMessage(e, "queryForFieldValues threw exception"); throw new RuntimeException(e); } } /** @see Dao#queryForFieldValuesArgs(Map) */ public List<T> queryForFieldValuesArgs(Map<String, Object> fieldValues) { try { return dao.queryForFieldValuesArgs(fieldValues); } catch (SQLException e) { logMessage(e, "queryForFieldValuesArgs threw exception"); throw new RuntimeException(e); } } /** @see Dao#queryForSameId(Object) */ public T queryForSameId(T data) { try { return dao.queryForSameId(data); } catch (SQLException e) { logMessage(e, "queryForSameId threw exception on: " + data); throw new RuntimeException(e); } } /** @see Dao#queryBuilder() */ public QueryBuilder<T, ID> queryBuilder() { return dao.queryBuilder(); } /** @see Dao#updateBuilder() */ public UpdateBuilder<T, ID> updateBuilder() { return dao.updateBuilder(); } /** @see Dao#deleteBuilder() */ public DeleteBuilder<T, ID> deleteBuilder() { return dao.deleteBuilder(); } /** @see Dao#query(PreparedQuery) */ public List<T> query(PreparedQuery<T> preparedQuery) { try { return dao.query(preparedQuery); } catch (SQLException e) { logMessage(e, "query threw exception on: " + preparedQuery); throw new RuntimeException(e); } } /** @see Dao#create(Object) */ public int create(T data) { try { return dao.create(data); } catch (SQLException e) { logMessage(e, "create threw exception on: " + data); throw new RuntimeException(e); } } /** @see Dao#create(Collection) */ public int create(Collection<T> datas) { try { return dao.create(datas); } catch (SQLException e) { logMessage(e, "create threw exception on: " + datas); throw new RuntimeException(e); } } /** @see Dao#createIfNotExists(Object) */ public T createIfNotExists(T data) { try { return dao.createIfNotExists(data); } catch (SQLException e) { logMessage(e, "createIfNotExists threw exception on: " + data); throw new RuntimeException(e); } } /** @see Dao#createOrUpdate(Object) */ public CreateOrUpdateStatus createOrUpdate(T data) { try { return dao.createOrUpdate(data); } catch (SQLException e) { logMessage(e, "createOrUpdate threw exception on: " + data); throw new RuntimeException(e); } } /** @see Dao#update(Object) */ public int update(T data) { try { return dao.update(data); } catch (SQLException e) { logMessage(e, "update threw exception on: " + data); throw new RuntimeException(e); } } /** @see Dao#updateId(Object, Object) */ public int updateId(T data, ID newId) { try { return dao.updateId(data, newId); } catch (SQLException e) { logMessage(e, "updateId threw exception on: " + data); throw new RuntimeException(e); } } /** @see Dao#update(PreparedUpdate) */ public int update(PreparedUpdate<T> preparedUpdate) { try { return dao.update(preparedUpdate); } catch (SQLException e) { logMessage(e, "update threw exception on: " + preparedUpdate); throw new RuntimeException(e); } } /** @see Dao#refresh(Object) */ public int refresh(T data) { try { return dao.refresh(data); } catch (SQLException e) { logMessage(e, "refresh threw exception on: " + data); throw new RuntimeException(e); } } /** @see Dao#delete(Object) */ public int delete(T data) { try { return dao.delete(data); } catch (SQLException e) { logMessage(e, "delete threw exception on: " + data); throw new RuntimeException(e); } } /** @see Dao#deleteById(Object) */ public int deleteById(ID id) { try { return dao.deleteById(id); } catch (SQLException e) { logMessage(e, "deleteById threw exception on: " + id); throw new RuntimeException(e); } } /** @see Dao#delete(Collection) */ public int delete(Collection<T> datas) { try { return dao.delete(datas); } catch (SQLException e) { logMessage(e, "delete threw exception on: " + datas); throw new RuntimeException(e); } } /** @see Dao#deleteIds(Collection) */ public int deleteIds(Collection<ID> ids) { try { return dao.deleteIds(ids); } catch (SQLException e) { logMessage(e, "deleteIds threw exception on: " + ids); throw new RuntimeException(e); } } /** @see Dao#delete(PreparedDelete) */ public int delete(PreparedDelete<T> preparedDelete) { try { return dao.delete(preparedDelete); } catch (SQLException e) { logMessage(e, "delete threw exception on: " + preparedDelete); throw new RuntimeException(e); } } /** @see Dao#iterator() */ public CloseableIterator<T> iterator() { return dao.iterator(); } public CloseableIterator<T> closeableIterator() { return dao.closeableIterator(); } /** @see Dao#iterator(int) */ public CloseableIterator<T> iterator(int resultFlags) { return dao.iterator(resultFlags); } /** @see Dao#getWrappedIterable() */ public CloseableWrappedIterable<T> getWrappedIterable() { return dao.getWrappedIterable(); } /** @see Dao#getWrappedIterable(PreparedQuery) */ public CloseableWrappedIterable<T> getWrappedIterable(PreparedQuery<T> preparedQuery) { return dao.getWrappedIterable(preparedQuery); } /** @see Dao#closeLastIterator() */ public void closeLastIterator() { try { dao.closeLastIterator(); } catch (IOException e) { logMessage(e, "closeLastIterator threw exception"); throw new RuntimeException(e); } } /** @see Dao#iterator(PreparedQuery) */ public CloseableIterator<T> iterator(PreparedQuery<T> preparedQuery) { try { return dao.iterator(preparedQuery); } catch (SQLException e) { logMessage(e, "iterator threw exception on: " + preparedQuery); throw new RuntimeException(e); } } /** @see Dao#iterator(PreparedQuery, int) */ public CloseableIterator<T> iterator(PreparedQuery<T> preparedQuery, int resultFlags) { try { return dao.iterator(preparedQuery, resultFlags); } catch (SQLException e) { logMessage(e, "iterator threw exception on: " + preparedQuery); throw new RuntimeException(e); } } /** @see Dao#queryRaw(String, String...) */ public GenericRawResults<String[]> queryRaw(String query, String... arguments) { try { return dao.queryRaw(query, arguments); } catch (SQLException e) { logMessage(e, "queryRaw threw exception on: " + query); throw new RuntimeException(e); } } /** @see Dao#queryRawValue(String, String...) */ public long queryRawValue(String query, String... arguments) { try { return dao.queryRawValue(query, arguments); } catch (SQLException e) { logMessage(e, "queryRawValue threw exception on: " + query); throw new RuntimeException(e); } } /** @see Dao#queryRaw(String, RawRowMapper, String...) */ public <UO> GenericRawResults<UO> queryRaw( String query, RawRowMapper<UO> mapper, String... arguments) { try { return dao.queryRaw(query, mapper, arguments); } catch (SQLException e) { logMessage(e, "queryRaw threw exception on: " + query); throw new RuntimeException(e); } } /** @see Dao#queryRaw(String, DataType[], RawRowObjectMapper, String...) */ public <UO> GenericRawResults<UO> queryRaw( String query, DataType[] columnTypes, RawRowObjectMapper<UO> mapper, String... arguments) { try { return dao.queryRaw(query, columnTypes, mapper, arguments); } catch (SQLException e) { logMessage(e, "queryRaw threw exception on: " + query); throw new RuntimeException(e); } } /** @see Dao#queryRaw(String, DataType[], String...) */ public GenericRawResults<Object[]> queryRaw( String query, DataType[] columnTypes, String... arguments) { try { return dao.queryRaw(query, columnTypes, arguments); } catch (SQLException e) { logMessage(e, "queryRaw threw exception on: " + query); throw new RuntimeException(e); } } /** @see Dao#queryRaw(String, DatabaseResultsMapper, String...) */ public <UO> GenericRawResults<UO> queryRaw( String query, DatabaseResultsMapper<UO> mapper, String... arguments) { try { return dao.queryRaw(query, mapper, arguments); } catch (SQLException e) { logMessage(e, "queryRaw threw exception on: " + query); throw new RuntimeException(e); } } /** @see Dao#executeRaw(String, String...) */ public int executeRaw(String statement, String... arguments) { try { return dao.executeRaw(statement, arguments); } catch (SQLException e) { logMessage(e, "executeRaw threw exception on: " + statement); throw new RuntimeException(e); } } /** @see Dao#executeRawNoArgs(String) */ public int executeRawNoArgs(String statement) { try { return dao.executeRawNoArgs(statement); } catch (SQLException e) { logMessage(e, "executeRawNoArgs threw exception on: " + statement); throw new RuntimeException(e); } } /** @see Dao#updateRaw(String, String...) */ public int updateRaw(String statement, String... arguments) { try { return dao.updateRaw(statement, arguments); } catch (SQLException e) { logMessage(e, "updateRaw threw exception on: " + statement); throw new RuntimeException(e); } } /** @see Dao#callBatchTasks(Callable) */ public <CT> CT callBatchTasks(Callable<CT> callable) { try { return dao.callBatchTasks(callable); } catch (Exception e) { logMessage(e, "callBatchTasks threw exception on: " + callable); throw new RuntimeException(e); } } /** @see Dao#objectToString(Object) */ public String objectToString(T data) { return dao.objectToString(data); } /** @see Dao#objectsEqual(Object, Object) */ public boolean objectsEqual(T data1, T data2) { try { return dao.objectsEqual(data1, data2); } catch (SQLException e) { logMessage(e, "objectsEqual threw exception on: " + data1 + " and " + data2); throw new RuntimeException(e); } } /** @see Dao#extractId(Object) */ public ID extractId(T data) { try { return dao.extractId(data); } catch (SQLException e) { logMessage(e, "extractId threw exception on: " + data); throw new RuntimeException(e); } } /** @see Dao#getDataClass() */ public Class<T> getDataClass() { return dao.getDataClass(); } /** @see Dao#findForeignFieldType(Class) */ public FieldType findForeignFieldType(Class<?> clazz) { return dao.findForeignFieldType(clazz); } /** @see Dao#isUpdatable() */ public boolean isUpdatable() { return dao.isUpdatable(); } /** @see Dao#isTableExists() */ public boolean isTableExists() { try { return dao.isTableExists(); } catch (SQLException e) { logMessage(e, "isTableExists threw exception"); throw new RuntimeException(e); } } /** @see Dao#countOf() */ public long countOf() { try { return dao.countOf(); } catch (SQLException e) { logMessage(e, "countOf threw exception"); throw new RuntimeException(e); } } /** @see Dao#countOf(PreparedQuery) */ public long countOf(PreparedQuery<T> preparedQuery) { try { return dao.countOf(preparedQuery); } catch (SQLException e) { logMessage(e, "countOf threw exception on " + preparedQuery); throw new RuntimeException(e); } } /** @see Dao#assignEmptyForeignCollection(Object, String) */ public void assignEmptyForeignCollection(T parent, String fieldName) { try { dao.assignEmptyForeignCollection(parent, fieldName); } catch (SQLException e) { logMessage(e, "assignEmptyForeignCollection threw exception on " + fieldName); throw new RuntimeException(e); } } /** @see Dao#getEmptyForeignCollection(String) */ public <FT> ForeignCollection<FT> getEmptyForeignCollection(String fieldName) { try { return dao.getEmptyForeignCollection(fieldName); } catch (SQLException e) { logMessage(e, "getEmptyForeignCollection threw exception on " + fieldName); throw new RuntimeException(e); } } /** @see Dao#setObjectCache(boolean) */ public void setObjectCache(boolean enabled) { try { dao.setObjectCache(enabled); } catch (SQLException e) { logMessage(e, "setObjectCache(" + enabled + ") threw exception"); throw new RuntimeException(e); } } /** @see Dao#getObjectCache() */ public ObjectCache getObjectCache() { return dao.getObjectCache(); } /** @see Dao#setObjectCache(ObjectCache) */ public void setObjectCache(ObjectCache objectCache) { try { dao.setObjectCache(objectCache); } catch (SQLException e) { logMessage(e, "setObjectCache threw exception on " + objectCache); throw new RuntimeException(e); } } /** @see Dao#clearObjectCache() */ public void clearObjectCache() { dao.clearObjectCache(); } /** @see Dao#mapSelectStarRow(DatabaseResults) */ public T mapSelectStarRow(DatabaseResults results) { try { return dao.mapSelectStarRow(results); } catch (SQLException e) { logMessage(e, "mapSelectStarRow threw exception on results"); throw new RuntimeException(e); } } /** @see Dao#getSelectStarRowMapper() */ public GenericRowMapper<T> getSelectStarRowMapper() { try { return dao.getSelectStarRowMapper(); } catch (SQLException e) { logMessage(e, "getSelectStarRowMapper threw exception"); throw new RuntimeException(e); } } /** @see Dao#idExists(Object) */ public boolean idExists(ID id) { try { return dao.idExists(id); } catch (SQLException e) { logMessage(e, "idExists threw exception on " + id); throw new RuntimeException(e); } } /** @see Dao#startThreadConnection() */ public DatabaseConnection startThreadConnection() { try { return dao.startThreadConnection(); } catch (SQLException e) { logMessage(e, "startThreadConnection() threw exception"); throw new RuntimeException(e); } } /** @see Dao#endThreadConnection(DatabaseConnection) */ public void endThreadConnection(DatabaseConnection connection) { try { dao.endThreadConnection(connection); } catch (SQLException e) { logMessage(e, "endThreadConnection(" + connection + ") threw exception"); throw new RuntimeException(e); } } /** @see Dao#setAutoCommit(boolean) */ @Deprecated public void setAutoCommit(boolean autoCommit) { try { dao.setAutoCommit(autoCommit); } catch (SQLException e) { logMessage(e, "setAutoCommit(" + autoCommit + ") threw exception"); throw new RuntimeException(e); } } /** @see Dao#setAutoCommit(DatabaseConnection, boolean) */ public void setAutoCommit(DatabaseConnection connection, boolean autoCommit) { try { dao.setAutoCommit(connection, autoCommit); } catch (SQLException e) { logMessage(e, "setAutoCommit(" + connection + "," + autoCommit + ") threw exception"); throw new RuntimeException(e); } } /** @see Dao#isAutoCommit() */ @Deprecated public boolean isAutoCommit() { try { return dao.isAutoCommit(); } catch (SQLException e) { logMessage(e, "isAutoCommit() threw exception"); throw new RuntimeException(e); } } /** @see Dao#isAutoCommit(DatabaseConnection) */ public boolean isAutoCommit(DatabaseConnection connection) { try { return dao.isAutoCommit(connection); } catch (SQLException e) { logMessage(e, "isAutoCommit(" + connection + ") threw exception"); throw new RuntimeException(e); } } /** @see Dao#commit(DatabaseConnection) */ public void commit(DatabaseConnection connection) { try { dao.commit(connection); } catch (SQLException e) { logMessage(e, "commit(" + connection + ") threw exception"); throw new RuntimeException(e); } } /** @see Dao#rollBack(DatabaseConnection) */ public void rollBack(DatabaseConnection connection) { try { dao.rollBack(connection); } catch (SQLException e) { logMessage(e, "rollBack(" + connection + ") threw exception"); throw new RuntimeException(e); } } /** @see Dao#setObjectFactory(ObjectFactory) */ public void setObjectFactory(ObjectFactory<T> objectFactory) { dao.setObjectFactory(objectFactory); } /** @see Dao#getRawRowMapper() */ public RawRowMapper<T> getRawRowMapper() { return dao.getRawRowMapper(); } /** @see Dao#getConnectionSource() */ public ConnectionSource getConnectionSource() { return dao.getConnectionSource(); } public void registerObserver(DaoObserver observer) { dao.registerObserver(observer); } public void unregisterObserver(DaoObserver observer) { dao.unregisterObserver(observer); } public void notifyChanges() { dao.notifyChanges(); } private void logMessage(Exception e, String message) { logger.log(LOG_LEVEL, e, message); } }
public class AndroidConnectionSource extends BaseConnectionSource implements ConnectionSource { private static final Logger logger = LoggerFactory.getLogger(com / j256 / ormlite / android / AndroidConnectionSource); private AndroidDatabaseConnection connection; private final DatabaseType databaseType; private final SQLiteOpenHelper helper; private volatile boolean isOpen; private final SQLiteDatabase sqliteDatabase; public AndroidConnectionSource(SQLiteDatabase sqlitedatabase) { connection = null; isOpen = true; databaseType = new SqliteAndroidDatabaseType(); helper = null; sqliteDatabase = sqlitedatabase; } public AndroidConnectionSource(SQLiteOpenHelper sqliteopenhelper) { connection = null; isOpen = true; databaseType = new SqliteAndroidDatabaseType(); helper = sqliteopenhelper; sqliteDatabase = null; } public void clearSpecialConnection(DatabaseConnection databaseconnection) { clearSpecial(databaseconnection, logger); } public void close() { isOpen = false; } public DatabaseType getDatabaseType() { return databaseType; } public DatabaseConnection getReadOnlyConnection() { return getReadWriteConnection(); } public DatabaseConnection getReadWriteConnection() { DatabaseConnection databaseconnection = getSavedConnection(); if (databaseconnection != null) { return databaseconnection; } if (connection == null) { if (sqliteDatabase == null) { connection = new AndroidDatabaseConnection(helper.getWritableDatabase(), true); } else { connection = new AndroidDatabaseConnection(sqliteDatabase, true); } } return connection; } public boolean isOpen() { return isOpen; } public void releaseConnection(DatabaseConnection databaseconnection) {} public boolean saveSpecialConnection(DatabaseConnection databaseconnection) { return saveSpecial(databaseconnection); } }
/** * This helps organize and access database connections to optimize connection sharing. There are * several schemes to manage the database connections in an Android app, but as an app gets more * complicated, there are many potential places where database locks can occur. This class allows * database connection sharing between multiple threads in a single app. * * <p>This gets injected or called with the {@link OrmLiteSqliteOpenHelper} class that is used to * manage the database connection. The helper instance will be kept in a static field and only * released once its internal usage count goes to 0. * * <p>The {@link SQLiteOpenHelper} and database classes maintain one connection under the hood, and * prevent locks in the java code. Creating multiple connections can potentially be a source of * trouble. This class shares the same connection instance between multiple clients, which will * allow multiple activities and services to run at the same time. * * <p>Every time you use the helper, you should call {@link #getHelper(Context)} or {@link * #getHelper(Context, Class)}. When you are done with the helper you should call {@link * #releaseHelper()}. * * @author graywatson, kevingalligan */ public class OpenHelperManager { private static final String HELPER_CLASS_RESOURCE_NAME = "open_helper_classname"; private static Logger logger = LoggerFactory.getLogger(OpenHelperManager.class); private static Class<? extends OrmLiteSqliteOpenHelper> helperClass = null; private static volatile OrmLiteSqliteOpenHelper helper = null; private static boolean wasClosed = false; private static int instanceCount = 0; /** * If you are _not_ using the {@link OrmLiteBaseActivity} type classes then you will need to call * this in a static method in your code. */ public static synchronized void setOpenHelperClass( Class<? extends OrmLiteSqliteOpenHelper> openHelperClass) { if (openHelperClass == null) { helperClass = null; } else { innerSetHelperClass(openHelperClass); } } /** * Set the helper for the manager. This is most likely used for testing purposes and should only * be called if you _really_ know what you are doing. If you do use it then it should be in a * static {} initializing block to make sure you have one helper instance for your application. */ public static synchronized void setHelper(OrmLiteSqliteOpenHelper helper) { OpenHelperManager.helper = helper; } /** * Create a static instance of our open helper from the helper class. This has a usage counter on * it so make sure all calls to this method have an associated call to {@link #releaseHelper()}. * This should be called during an onCreate() type of method when the application or service is * starting. The caller should then keep the helper around until it is shutting down when {@link * #releaseHelper()} should be called. */ public static synchronized <T extends OrmLiteSqliteOpenHelper> T getHelper( Context context, Class<T> openHelperClass) { innerSetHelperClass(openHelperClass); return loadHelper(context, openHelperClass); } /** * Similar to {@link #getHelper(Context, Class)} (which is recommended) except we have to find the * helper class through other means. This method requires that the Context be a class that extends * one of ORMLite's Android base classes such as {@link OrmLiteBaseActivity}. Either that or the * helper class needs to be set in the strings.xml. * * <p>To find the helper class, this does the following: <br> * 1) If the class has been set with a call to {@link #setOpenHelperClass(Class)}, it will be used * to construct a helper. <br> * 2) If the resource class name is configured in the strings.xml file it will be used. <br> * 3) The context class hierarchy is walked looking at the generic parameters for a class * extending OrmLiteSqliteOpenHelper. This is used by the {@link OrmLiteBaseActivity} and other * base classes. <br> * 4) An exception is thrown saying that it was not able to set the helper class. * * @deprecated Should use {@link #getHelper(Context, Class)} */ @Deprecated public static synchronized OrmLiteSqliteOpenHelper getHelper(Context context) { if (helperClass == null) { if (context == null) { throw new IllegalArgumentException("context argument is null"); } Context appContext = context.getApplicationContext(); innerSetHelperClass(lookupHelperClass(appContext, context.getClass())); } return loadHelper(context, helperClass); } /** @deprecated This has been renamed to be {@link #releaseHelper()}. */ @Deprecated public static void release() { releaseHelper(); } /** * Release the helper that was previously returned by a call {@link #getHelper(Context)} or {@link * #getHelper(Context, Class)}. This will decrement the usage counter and close the helper if the * counter is 0. * * <p><b> WARNING: </b> This should be called in an onDestroy() type of method when your * application or service is terminating or if your code is no longer going to use the helper or * derived DAOs in any way. _Don't_ call this method if you expect to call {@link * #getHelper(Context)} again before the application terminates. */ public static synchronized void releaseHelper() { instanceCount--; logger.trace("releasing helper {}, instance count = {}", helper, instanceCount); if (instanceCount <= 0) { if (helper != null) { logger.trace("zero instances, closing helper {}", helper); helper.close(); helper = null; wasClosed = true; } if (instanceCount < 0) { logger.error("too many calls to release helper, instance count = {}", instanceCount); } } } /** Set the helper class and make sure we aren't changing it to another class. */ private static void innerSetHelperClass( Class<? extends OrmLiteSqliteOpenHelper> openHelperClass) { // make sure if that there are not 2 helper classes in an application if (helperClass == null) { helperClass = openHelperClass; } else if (helperClass != openHelperClass) { throw new IllegalStateException( "Helper class was " + helperClass + " but is trying to be reset to " + openHelperClass); } } private static <T extends OrmLiteSqliteOpenHelper> T loadHelper( Context context, Class<T> openHelperClass) { if (helper == null) { if (wasClosed) { // this can happen if you are calling get/release and then get again logger.info("helper was already closed and is being re-opened"); } if (context == null) { throw new IllegalArgumentException("context argument is null"); } Context appContext = context.getApplicationContext(); helper = constructHelper(appContext, helperClass); logger.trace("zero instances, created helper {}", helper); /* * Filipe Leandro and I worked on this bug for like 10 hours straight. It's a doosey. * * Each ForeignCollection has internal DAO objects that are holding a ConnectionSource. Each Android * ConnectionSource is tied to a particular database connection. What Filipe was seeing was that when all of * his views we closed (onDestroy), but his application WAS NOT FULLY KILLED, the first View.onCreate() * method would open a new connection to the database. Fine. But because he application was still in memory, * the static BaseDaoImpl default cache had not been cleared and was containing cached objects with * ForeignCollections. The ForeignCollections still had references to the DAOs that had been opened with old * ConnectionSource objects and therefore the old database connection. Using those cached collections would * cause exceptions saying that you were trying to work with a database that had already been close. * * Now, whenever we create a new helper object, we must make sure that the internal object caches have been * fully cleared. This is a good lesson for anyone that is holding objects around after they have closed * connections to the database or re-created the DAOs on a different connection somehow. */ BaseDaoImpl.clearAllInternalObjectCaches(); /* * Might as well do this also since if the helper changes then the ConnectionSource will change so no one is * going to have a cache hit on the old DAOs anyway. All they are doing is holding memory. * * NOTE: we don't want to clear the config map. */ DaoManager.clearDaoCache(); instanceCount = 0; } instanceCount++; logger.trace("returning helper {}, instance count = {} ", helper, instanceCount); @SuppressWarnings("unchecked") T castHelper = (T) helper; return castHelper; } /** Call the constructor on our helper class. */ private static OrmLiteSqliteOpenHelper constructHelper( Context context, Class<? extends OrmLiteSqliteOpenHelper> openHelperClass) { Constructor<?> constructor; try { constructor = openHelperClass.getConstructor(Context.class); } catch (Exception e) { throw new IllegalStateException( "Could not find constructor that hast just a (Context) argument for helper class " + openHelperClass, e); } try { return (OrmLiteSqliteOpenHelper) constructor.newInstance(context); } catch (Exception e) { throw new IllegalStateException( "Could not construct instance of helper class " + openHelperClass, e); } } /** * Lookup the helper class either from the resource string or by looking for a generic parameter. */ private static Class<? extends OrmLiteSqliteOpenHelper> lookupHelperClass( Context context, Class<?> componentClass) { // see if we have the magic resource class name set Resources resources = context.getResources(); int resourceId = resources.getIdentifier(HELPER_CLASS_RESOURCE_NAME, "string", context.getPackageName()); if (resourceId != 0) { String className = resources.getString(resourceId); try { @SuppressWarnings("unchecked") Class<? extends OrmLiteSqliteOpenHelper> castClass = (Class<? extends OrmLiteSqliteOpenHelper>) Class.forName(className); return castClass; } catch (Exception e) { throw new IllegalStateException( "Could not create helper instance for class " + className, e); } } // try walking the context class to see if we can get the OrmLiteSqliteOpenHelper from a generic // parameter for (Class<?> componentClassWalk = componentClass; componentClassWalk != null; componentClassWalk = componentClassWalk.getSuperclass()) { Type superType = componentClassWalk.getGenericSuperclass(); if (superType == null || !(superType instanceof ParameterizedType)) { continue; } // get the generic type arguments Type[] types = ((ParameterizedType) superType).getActualTypeArguments(); // defense if (types == null || types.length == 0) { continue; } for (Type type : types) { // defense if (!(type instanceof Class)) { continue; } Class<?> clazz = (Class<?>) type; if (OrmLiteSqliteOpenHelper.class.isAssignableFrom(clazz)) { @SuppressWarnings("unchecked") Class<? extends OrmLiteSqliteOpenHelper> castOpenHelperClass = (Class<? extends OrmLiteSqliteOpenHelper>) clazz; return castOpenHelperClass; } } } throw new IllegalStateException( "Could not find OpenHelperClass because none of the generic parameters of class " + componentClass + " extends OrmLiteSqliteOpenHelper. You should use getHelper(Context, Class) instead."); } }