Back to project page Android-Lib-Database.
The source code is released under:
Apache License
If you think the Android project Android-Lib-Database listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
package android.lib.database; /* w w w . jav a2 s. c om*/ import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.lib.database.predicate.Predicate; import android.lib.database.query.Delete; import android.lib.database.query.Insert; import android.lib.database.query.Query; import android.lib.database.query.Select; import android.lib.database.query.Update; import android.text.TextUtils; /** * Provides database abstraction layer APIs to handle SQLite operations: <code>SELECT</code>, * <code>UPDATE</code>, <code>INSERT</code>, <code>DELETE</code>. */ public class Database { private final SQLiteDatabase database; protected Database(final SQLiteDatabase database) { this.database = database; } /** * Creates a new instance of {@link Database} with the given <code>context</code>, * <code>name</code>, <code>version</code>. * @param context the context to use to open or create the database * @param name the file name of the database file, or null for an in-memory database * @param version the version number of the database (starting at 1); if the database is older, * {@link #onUpgrade(SQLiteDatabase, int, int)} will be used to upgrade the database; * if the database is newer, {@link #onDowngrade(SQLiteDatabase, int, int)} will be used to downgrade the database * @param tables the tables of the database to create in when the application runs for the first time. * For subsequent call, <code>tables</code> can be <code>null</code>. */ protected Database(final Context context, final String name, final int version, final Class<?>... tables) { this(DatabaseOpenHelper.newInstance(context, name, version, tables).getWritableDatabase()); } /** * Creates a new instance of {@link Database} with the given <code>context</code>, <code>name</code>. * @param context the context to use to open or create the database * @param name the file name of the database file, or null for an in-memory database * @param version the version number of the database (starting at 1); if the database is older, * {@link #onUpgrade(SQLiteDatabase, int, int)} will be used to upgrade the database; * if the database is newer, {@link #onDowngrade(SQLiteDatabase, int, int)} will be used to downgrade the database * @param tables the tables of the database to create in when the application runs for the first time. * For subsequent call, <code>tables</code> can be <code>null</code>. */ protected Database(final Context context, final String name, final Class<?>... tables) { this(DatabaseOpenHelper.newInstance(context, name, tables).getWritableDatabase()); } /** * Creates a new instance of {@link Database} with the given <code>context</code>, <code>version</code>. * @param context the context to use to open or create the database * @param name the file name of the database file, or null for an in-memory database * @param version the version number of the database (starting at 1); if the database is older, * {@link #onUpgrade(SQLiteDatabase, int, int)} will be used to upgrade the database; * if the database is newer, {@link #onDowngrade(SQLiteDatabase, int, int)} will be used to downgrade the database * @param tables the tables of the database to create in when the application runs for the first time. * For subsequent call, <code>tables</code> can be <code>null</code>. */ protected Database(final Context context, final int version, final Class<?>... tables) { this(DatabaseOpenHelper.newInstance(context, version, tables).getWritableDatabase()); } /** * Creates a new instance of {@link Database} with the given <code>context</code>. * @param context the context to use to open or create the database * @param name the file name of the database file, or null for an in-memory database * @param version the version number of the database (starting at 1); if the database is older, * {@link #onUpgrade(SQLiteDatabase, int, int)} will be used to upgrade the database; * if the database is newer, {@link #onDowngrade(SQLiteDatabase, int, int)} will be used to downgrade the database * @param tables the tables of the database to create in when the application runs for the first time. * For subsequent call, <code>tables</code> can be <code>null</code>. */ protected Database(final Context context, final Class<?>... tables) { this(DatabaseOpenHelper.newInstance(context, tables).getWritableDatabase()); } /** * Creates a new instance of {@link Database} with the given <code>context</code>, * <code>name</code>, <code>version</code>. * @param context the context to use to open or create the database * @param name the file name of the database file, or null for an in-memory database * @param version the version number of the database (starting at 1); if the database is older, * {@link #onUpgrade(SQLiteDatabase, int, int)} will be used to upgrade the database; * if the database is newer, {@link #onDowngrade(SQLiteDatabase, int, int)} will be used to downgrade the database * @param tables the tables of the database to create in when the application runs for the first time. * For subsequent call, <code>tables</code> can be <code>null</code>. */ public static Database newInstance(final Context context, final String name, final int version, final Class<?>... tables) { return new Database(context, name, version, tables); } /** * Creates a new instance of {@link Database} with the given <code>context</code>, <code>name</code>. * @param context the context to use to open or create the database * @param name the file name of the database file, or null for an in-memory database * @param version the version number of the database (starting at 1); if the database is older, * {@link #onUpgrade(SQLiteDatabase, int, int)} will be used to upgrade the database; * if the database is newer, {@link #onDowngrade(SQLiteDatabase, int, int)} will be used to downgrade the database * @param tables the tables of the database to create in when the application runs for the first time. * For subsequent call, <code>tables</code> can be <code>null</code>. */ public static Database newInstance(final Context context, final String name, final Class<?>... tables) { return new Database(context, name, tables); } /** * Creates a new instance of {@link Database} with the given <code>context</code>, <code>version</code>. * @param context the context to use to open or create the database * @param name the file name of the database file, or null for an in-memory database * @param version the version number of the database (starting at 1); if the database is older, * {@link #onUpgrade(SQLiteDatabase, int, int)} will be used to upgrade the database; * if the database is newer, {@link #onDowngrade(SQLiteDatabase, int, int)} will be used to downgrade the database * @param tables the tables of the database to create in when the application runs for the first time. * For subsequent call, <code>tables</code> can be <code>null</code>. */ public static Database newInstance(final Context context, final int version, final Class<?>... tables) { return new Database(context, version, tables); } /** * Creates a new instance of {@link Database} with the given <code>context</code>. * @param context the context to use to open or create the database * @param name the file name of the database file, or null for an in-memory database * @param version the version number of the database (starting at 1); if the database is older, * {@link #onUpgrade(SQLiteDatabase, int, int)} will be used to upgrade the database; * if the database is newer, {@link #onDowngrade(SQLiteDatabase, int, int)} will be used to downgrade the database * @param tables the tables of the database to create in when the application runs for the first time. * For subsequent call, <code>tables</code> can be <code>null</code>. */ public static Database newInstance(final Context context, final Class<?>... tables) { return new Database(context, tables); } /** * Creates database tables/columns/indices. * <p>Call this method on application first-run after installation.</p> * <p>This method should be called once per application installation.</p> * @param context the context to use to open the database. * @param the tables of the database to create. */ public static void createTables(final Context context, final Class<?>... tables) { Database.newInstance(context, tables).close(); } /** * Closes the database, and abort any uncommitted changes. * <p>This is a no-op if the database has already been closed before.</p> */ public void close() { if (this.database.isOpen()) { if (this.database.inTransaction()) { this.database.endTransaction(); } this.database.close(); } } /** * Persists any uncommitted changes to the database. * <p>The current transaction, if any, will be closed when this method returns. * If necessary, call {@link #beginTransaction()} to start a new transaction.</p> * <p>This is no-op if there is no uncommitted changes</p> * @throws IllegalStateException if the database has been closed. */ public void commit() throws IllegalStateException { if (this.database.isOpen()) { if (this.database.inTransaction()) { this.database.setTransactionSuccessful(); this.database.endTransaction(); } } else { throw new IllegalStateException("Database has been closed"); //$NON-NLS-1$ } } /** * Rolls back any uncommitted changes of the database. * <p>The current transaction, if any, will be closed when this method returns. * If necessary, call {@link #beginTransaction()} to start a new transaction.</p> * <p>This is a no-op if there is no uncommitted changes.</p> * @throws IllegalStateException if the database has been closed. */ public void rollback() throws IllegalStateException { if (this.database.isOpen()) { if (this.database.inTransaction()) { this.database.endTransaction(); } } else { throw new IllegalStateException("Database has been closed"); //$NON-NLS-1$ } } /** * Starts a new transaction. * <p>If a transaction has already been started and not closed yet, * a nested transaction will be started.</p> * @throws IllegalStateException if the database has been closed. */ public void beginTransaction() { if (this.database.isOpen()) { this.database.beginTransaction(); } else { throw new IllegalStateException("Database has been closed"); //$NON-NLS-1$ } } /** * Executes a {@link Query} of type {@link Select} against the database and returns a list of object * of type <code>T</code> using the given {@link RowMapper}. * @param query the {@link Query} object containing * the raw SQL statement to be executed. * @param mapper the mapper to map {@link android.database.Cursor} to <code>T</code>. * @param <T> the type of the table object to cast the result set to. * @return the result set returned from the query execution. * @throws IllegalArgumentException if the column name is not found from the resulting <code>cursor</code>. * @throws UnsupportedTypeException if the type <code>T</code> contains fields of unsupported types. * @throws IllegalAccessException if the if the default constructor of <code>T</code> is not visible, * or the default constructor of the {@link TypeConverter} associated with any field of <code>T</code> is not visible. * @throws InstantiationException if the instance of <code>T</code> can not be created, * or the instance of the {@link TypeConverter} associated with any field of <code>T</code> can not be created. * @throws SQLException if the SQL string is invalid. */ public <T> List<T> execute(final Query query, final RowMapper<T> mapper) throws IllegalArgumentException, IllegalAccessException, InstantiationException, UnsupportedTypeException, SQLException { final List<T> rows = new ArrayList<T>(); final Cursor cursor = this.database.rawQuery(query.getRawSQL(), Database.getParameters(query)); while (cursor.moveToNext()) { rows.add(mapper.mapRow(cursor)); } return rows; } /** * Execute SQL <code>SELECT</code> statement against the database and returns the row matching * the given <code>id</code>, or <code>null</code> if there is no matching row. * @param table the table containing the data to retrieve from. * @param idColumn the name of the id column. * @param idValue an unique value to match the id column of a row. * @return the result set returned from the query execution, * or <code>null</code> if there is no matching row. * @throws IllegalArgumentException if the column name is not found from the resulting <code>cursor</code>. * @throws UnsupportedTypeException if the type <code>T</code> contains fields of unsupported types. * @throws IllegalAccessException if the if the default constructor of <code>T</code> is not visible, * or the default constructor of the {@link TypeConverter} associated with any field of <code>T</code> is not visible. * @throws InstantiationException if the instance of <code>T</code> can not be created, * or the instance of the {@link TypeConverter} associated with any field of <code>T</code> can not be created. * @throws SQLException if the SQL string is invalid. */ public <T> T selectById(final Class<T> table, final String idColumn, final int idValue) throws IllegalArgumentException, IllegalAccessException, InstantiationException, UnsupportedTypeException, SQLException { final List<T> rows = this.execute(Query.select().columns().from(table).where(Predicate.equalTo(idColumn, Integer.valueOf(idValue), false, true)).build(), new RowMapper<T>(table)); if (rows == null) { return null; } return rows.get(0); } /** * Executes SQL <code>INSERT</code> statement against the database. * @param row the data to insert into the database. * @throws UnsupportedTypeException if the type <code>T</code> contains fields of unsupported types. * @throws IllegalAccessException if the if the default constructor of <code>T</code> is not visible, * or the default constructor of the {@link TypeConverter} associated with any field of <code>T</code> is not visible. * @throws InstantiationException if the instance of <code>T</code> can not be created, * or the instance of the {@link TypeConverter} associated with any field of <code>T</code> can not be created. * @throws SQLException if the SQL string is invalid. */ public <T> void insert(final T row) throws IllegalAccessException, InstantiationException, UnsupportedTypeException, SQLException { final Class<T> clazz = (Class<T>)row.getClass(); final Insert insert = Query.insert().into(clazz); final Set<Field> fields = new HashSet<Field>(); for (final Field field : clazz.getFields()) { fields.add(field); } for (final Field field : clazz.getDeclaredFields()) { fields.add(field); } for (final Field field : fields) { field.setAccessible(true); final Column column = field.getAnnotation(Column.class); if (column != null && !column.autoIncrement()) { insert.set(TextUtils.isEmpty(column.value()) ? field.getName() : column.value(), Database.getColumnValue(row, field)); } } this.execute(insert.build()); } /** * Execute SQL <code>DELETE</code> statement against the database and deletes any rows with * the matching id value. * @param table the table to delete rows from. * @param idColumn the name of the id column. * @param idValue an unique value to match the id column of a row. * @throws SQLException if the SQL string is invalid . */ public <T> void deleteById(final Class<T> table, final String idColumn, final int idValue) throws SQLException { this.execute(Query.delete().from(table).where(Predicate.equalTo(idColumn, Integer.valueOf(idValue), false, true)).build()); } /** * Executes a {@link Query} of type {@link Update}, {@link Insert} or {@link Delete} against the database. * @param query the {@link Query} object to execute. * @throws SQLException if the SQL string is invalid . */ public void execute(final Query query) throws SQLException { final String[] parameters = Database.getParameters(query); if (parameters == null) { this.database.execSQL(query.getRawSQL()); } else { this.database.execSQL(query.getRawSQL(), parameters); } } private static String[] getParameters(final Query query) { final String[] parameters = query.getParameters().size() > 0 ? new String[query.getParameters().size()] : null; if (parameters != null) { for (int i = query.getParameters().size(); --i >= 0;) { final Object parameter = query.getParameters().get(i); parameters[i] = parameter == null ? null : query.getParameters().get(i).toString(); } } return parameters; } private static <T> Object getColumnValue(final T row, final Field field) throws UnsupportedTypeException, IllegalAccessException, InstantiationException { final Class<?> type = field.getType(); final UseConverter converter = field.getAnnotation(UseConverter.class); if (converter == null) { if (type.equals(boolean.class) || type.equals(Boolean.class)) { return field.get(row); } else if (type.equals(byte.class) || type.equals(Byte.class)) { return field.get(row); } else if (type.equals(byte[].class) || type.equals(Byte[].class)) { return field.get(row); } else if (type.equals(double.class) || type.equals(Double.class)) { return field.get(row); } else if (type.equals(float.class) || type.equals(Float.class)) { return field.get(row); } else if (type.equals(int.class) || type.equals(Integer.class)) { return field.get(row); } else if (type.equals(long.class) || type.equals(Long.class)) { return field.get(row); } else if (type.equals(short.class) || type.equals(Short.class)) { return field.get(row); } else if (type.equals(String.class)) { return field.get(row); } else { throw new UnsupportedTypeException(String.format("%s is not of SQLite-compatible type", field.getName())); //$NON-NLS-1$ } } final Class<?> fromType = converter.type(); if (fromType.equals(boolean.class) || fromType.equals(Boolean.class)) { return ((TypeConverter<?, T>)converter.value().newInstance()).toDatabase((T)field.get(row)); } else if (fromType.equals(byte.class) || fromType.equals(Byte.class)) { return ((TypeConverter<?, T>)converter.value().newInstance()).toDatabase((T)field.get(row)); } else if (fromType.equals(byte[].class)) { return ((TypeConverter<?, T>)converter.value().newInstance()).toDatabase((T)field.get(row)); } else if (fromType.equals(Byte[].class)) { return ((TypeConverter<?, T>)converter.value().newInstance()).toDatabase((T)field.get(row)); } else if (fromType.equals(double.class) || fromType.equals(Double.class)) { return ((TypeConverter<?, T>)converter.value().newInstance()).toDatabase((T)field.get(row)); } else if (fromType.equals(float.class) || fromType.equals(Float.class)) { return ((TypeConverter<?, T>)converter.value().newInstance()).toDatabase((T)field.get(row)); } else if (fromType.equals(int.class) || fromType.equals(Integer.class)) { return ((TypeConverter<?, T>)converter.value().newInstance()).toDatabase((T)field.get(row)); } else if (fromType.equals(long.class) || fromType.equals(Long.class)) { return ((TypeConverter<?, T>)converter.value().newInstance()).toDatabase((T)field.get(row)); } else if (fromType.equals(short.class) || fromType.equals(Short.class)) { return ((TypeConverter<?, T>)converter.value().newInstance()).toDatabase((T)field.get(row)); } else if (fromType.equals(String.class)) { return ((TypeConverter<?, T>)converter.value().newInstance()).toDatabase((T)field.get(row)); } else { throw new UnsupportedTypeException(String.format("%s is not of SQLite-compatible type", field.getName())); //$NON-NLS-1$ } } }