How can I split a long, single SQLiteOpenHelper into several classes, one for each table

别说谁变了你拦得住时间么 提交于 2019-11-27 23:23:23

do you think you could give some hints on how to do it

This has nothing much to do with Android, and even not all that much to do with Java. Decomposing long programming structures (e.g., classes in Java) into smaller structures has standard techniques, called design patterns, with language-specific implementations.

For example, you could go with the composite pattern:

  • Define an interface -- I'll call it TableHelper here -- that has onCreate() and onUpdate() methods that match those on SQLiteOpenHelper

  • Define N classes, one per table, that implement the TableHelper interface and provide the create and upgrade logic for that table (along with whatever other business logic you want to have on those classes)

  • Have your SQLiteOpenHelper define a TableHelper[] containing instances of your TableHelper classes, and have it delegate onCreate() and onUpgrade() to those TableHelper instances by iterating over the array

Using a Content Provider, you can split your long SQLiteOpenHelper in a multiple files : one persistenceContract for each table, one little SQLiteOpenHelper, and long and verbose ContentProvider.

With this solution, you need to write more code. But it's more readable, and easy to maintain.


Avantages :

  • Easy to name a column of a table when you write your query (example : TaskPersistenceContract.TaskEntry.COLUMN_TASK_NAME)
  • Easy to read and maintain
  • Less typo bugs
  • Use cursor and CursorLoader logic

Disadvantage :

  • More verbose

Let's try with your initial code !

1. Create a PersistenceContract for each table

ListPersistenceContract :

public final class ListPersistenceContract {

    public static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID;
    public static final String CONTENT_LIST_TYPE = "vnd.android.cursor.dir/" + CONTENT_AUTHORITY + "/" + ListEntry.TABLE_NAME;
    public static final String CONTENT_LIST_ITEM_TYPE = "vnd.android.cursor.item/" + CONTENT_AUTHORITY + "/" + ListEntry.TABLE_NAME;
    public static final String VND_ANDROID_CURSOR_ITEM_VND = "vnd.android.cursor.item/vnd." + CONTENT_AUTHORITY + ".";
    private static final String CONTENT_SCHEME = "content://";
    public static final Uri BASE_CONTENT_URI = Uri.parse(CONTENT_SCHEME + CONTENT_AUTHORITY);
    private static final String VND_ANDROID_CURSOR_DIR_VND = "vnd.android.cursor.dir/vnd." + CONTENT_AUTHORITY + ".";
    private static final String SEPARATOR = "/";

    // To prevent someone from accidentally instantiating the contract class,
    // give it an empty constructor.
    private ListPersistenceContract() {}

    public static Uri getBaseListUri(String listId) {
        return Uri.parse(CONTENT_SCHEME + CONTENT_LIST_ITEM_TYPE + SEPARATOR + listId);
    }

    /* Inner class that defines the table contents */
    public static abstract class ListEntry implements BaseColumns {

        public static final String TABLE_NAME = "list";
        public static final String COLUMN_LIST_NAME = "list_name";

        public static final Uri CONTENT_LIST_URI = BASE_CONTENT_URI.buildUpon().appendPath(TABLE_NAME).build();
        public static String[] LIST_COLUMNS = new String[]{
                ListPersistenceContract.ListEntry._ID,
                ListEntry.COLUMN_LIST_NAME};

        public static final String LIST_AND_TASK = "listandtask";

        public static Uri buildListUriWith(long id) {
            return ContentUris.withAppendedId(CONTENT_LIST_URI, id);
        }

        public static Uri buildListUriWith(String id) {
            Uri uri = CONTENT_LIST_URI.buildUpon().appendPath(id).build();
            return uri;
        }

        public static Uri buildListUri() {
            return CONTENT_LIST_URI.buildUpon().build();
        }

        public static Uri buildListAndTaskUri() {
            return BASE_CONTENT_URI.buildUpon().appendPath(ListEntry.LIST_AND_TASK).build();
        }

    }
}

TaskPersistenceContract :

public class TaskPersistenceContract {

    public static final String CONTENT_AUTHORITY = BuildConfig.APPLICATION_ID;
    public static final String CONTENT_TASK_TYPE = "vnd.android.cursor.dir/" + CONTENT_AUTHORITY + "/" + TaskEntry.TABLE_NAME;
    public static final String CONTENT_TASK_ITEM_TYPE = "vnd.android.cursor.item/" + CONTENT_AUTHORITY + "/" + TaskEntry.TABLE_NAME;
    public static final String VND_ANDROID_CURSOR_ITEM_VND = "vnd.android.cursor.item/vnd." + CONTENT_AUTHORITY + ".";
    private static final String CONTENT_SCHEME = "content://";
    public static final Uri BASE_CONTENT_URI = Uri.parse(CONTENT_SCHEME + CONTENT_AUTHORITY);
    private static final String VND_ANDROID_CURSOR_DIR_VND = "vnd.android.cursor.dir/vnd." + CONTENT_AUTHORITY + ".";
    private static final String SEPARATOR = "/";

    // To prevent someone from accidentally instantiating the contract class,
    // give it an empty constructor.
    private TaskPersistenceContract() {}

    public static Uri getBaseTaskUri(String taskId) {
        return Uri.parse(CONTENT_SCHEME + CONTENT_TASK_ITEM_TYPE + SEPARATOR + taskId);
    }

    /* Inner class that defines the table contents */
    public static abstract class TaskEntry implements BaseColumns {

        public static final String TABLE_NAME = "task";
        public static final String COLUMN_TASK_LIST_ID = "list_id";
        public static final String COLUMN_TASK_NAME = "task_name";


        public static final Uri CONTENT_TASK_URI = BASE_CONTENT_URI.buildUpon().appendPath(TABLE_NAME).build();
        public static String[] TASK_COLUMNS = new String[]{
                TaskPersistenceContract.TaskEntry._ID,
                TaskPersistenceContract.TaskEntry.COLUMN_TASK_LIST_ID,
                TaskPersistenceContract.TaskEntry.COLUMN_TASK_NAME};

        public static Uri buildTaskUriWith(long id) {
            return ContentUris.withAppendedId(CONTENT_TASK_URI, id);
        }

        public static Uri buildTaskUriWith(String id) {
            Uri uri = CONTENT_TASK_URI.buildUpon().appendPath(id).build();
            return uri;
        }

        public static Uri buildTaskUri() {
            return CONTENT_TASK_URI.buildUpon().build();
        }

    }
}

2. Create DbHelper

public class LocalDbHelper {

    public static final int DB_VERSION = 1;
    public static final String DB_NAME = "mydatabase.db";
    private static final String TEXT_TYPE = " TEXT";
    private static final String INTEGER_TYPE = " INTEGER";
    private static final String PRIMARY_KEY = " PRIMARY KEY";
    private static final String AUTOINCREMENT = " AUTOINCREMENT";
    private static final String UNIQUE = " UNIQUE";
    private static final String CREATE_TABLE = "CREATE TABLE ";
    private static final String DROP_TABLE_IF_EXISTS = "DROP TABLE IF EXISTS ";
    private static final String OPEN_PARENTHESIS = " (";
    private static final String CLOSE_PARENTHESIS = " )";
    private static final String COMMA_SEP = ",";

    private static final String CREATE_LIST_TABLE =
            CREATE_TABLE + ListPersistenceContract.ListEntry.TABLE_NAME + OPEN_PARENTHESIS +
                    ListPersistenceContract.ListEntry._ID + INTEGER_TYPE + PRIMARY_KEY + AUTOINCREMENT + COMMA_SEP +
                    ListPersistenceContract.ListEntry.COLUMN_LIST_NAME + TEXT_TYPE + UNIQUE +
                    CLOSE_PARENTHESIS;

    private static final String CREATE_TASK_TABLE =
            CREATE_TABLE + TaskPersistenceContract.TaskEntry.TABLE_NAME + OPEN_PARENTHESIS +
                    TaskPersistenceContract.TaskEntry._ID + INTEGER_TYPE + PRIMARY_KEY + AUTOINCREMENT + COMMA_SEP +
                    TaskPersistenceContract.TaskEntry.COLUMN_TASK_LIST_ID + INTEGER_TYPE + COMMA_SEP +
                    TaskPersistenceContract.TaskEntry.COLUMN_TASK_NAME + TEXT_TYPE +
                    CLOSE_PARENTHESIS;

    private static final String DROP_LIST_TABLE =
            DROP_TABLE_IF_EXISTS + ListPersistenceContract.ListEntry.TABLE_NAME;

    private static final String DROP_TASK_TABLE =
            DROP_TABLE_IF_EXISTS + TaskPersistenceContract.TaskEntry.TABLE_NAME;

    public LocalDbHelper(Context context) {
        super(context, DB_NAME, null, DB_VERSION);
    }

    public void onCreate(SQLiteDatabase db) {
        // create tables
        db.execSQL(CREATE_LIST_TABLE);
        db.execSQL(CREATE_TASK_TABLE);

        // insert lists
        db.execSQL("INSERT INTO " + ListPersistenceContract.ListEntry.TABLE_NAME + " VALUES (1, 'Hobbies')");
        db.execSQL("INSERT INTO " + ListPersistenceContract.ListEntry.TABLE_NAME + " VALUES (2, 'Sports')");

        // insert sample tasks
        db.execSQL("INSERT INTO " + TaskPersistenceContract.TaskEntry.TABLE_NAME + " VALUES (1, 1, 'Play the guitar')");
        db.execSQL("INSERT INTO " + TaskPersistenceContract.TaskEntry.TABLE_NAME + " VALUES (2, 1, 'Play video games')");
    }

    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.d("Task list", "Upgrading db from version "
                + oldVersion + " to " + newVersion);


        db.execSQL(LocalDbHelper.DROP_LIST_TABLE);
        db.execSQL(LocalDbHelper.DROP_TASK_TABLE);
        onCreate(db);
    }

    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // Not required as at version 1
    }
}

3. Create ContentProvider with query, insert, update and delete operations

(in this example, i put a join of tables for a query. You can use the same logic for your insert, update and delete operations.)

public class MyAppContentProvider extends ContentProvider {

    private static final int LIST = 100;
    private static final int LIST_ITEM = 101;
    private static final int LIST_AND_TASK = 102;
    private static final int TASK = 200;
    private static final int TASK_ITEM = 201;

    private static final UriMatcher sUriMatcher = buildUriMatcher();
    private LocalDbHelper mLocalDbHelper;

    private static final SQLiteQueryBuilder sListAndTask;

    static{
        sListAndTask = new SQLiteQueryBuilder();

        sListAndTask.setTables(
                ListPersistenceContract.ListEntry.TABLE_NAME + " INNER JOIN " +
                        TaskPersistenceContract.TaskEntry.TABLE_NAME +
                        " ON " + ListPersistenceContract.ListEntry.TABLE_NAME +
                        "." + ListPersistenceContract.ListEntry._ID +
                        " = " + TaskPersistenceContract.TaskEntry.TABLE_NAME +
                        "." + TaskPersistenceContract.TaskEntry.COLUMN_TASK_LIST_ID);
    }

    private static UriMatcher buildUriMatcher() {
        final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
        final String authority = ListPersistenceContract.CONTENT_AUTHORITY;

        matcher.addURI(authority, ListPersistenceContract.ListEntry.TABLE_NAME, LIST);
        matcher.addURI(authority, ListPersistenceContract.ListEntry.TABLE_NAME + "/*", LIST_ITEM);
        matcher.addURI(authority, ListPersistenceContract.ListEntry.LIST_AND_TASK, LIST_AND_TASK);

        matcher.addURI(authority, TaskPersistenceContract.TaskEntry.TABLE_NAME, TASK);
        matcher.addURI(authority, TaskPersistenceContract.TaskEntry.TABLE_NAME + "/*", TASK_ITEM);

        return matcher;
    }

    @Override
    public boolean onCreate() {
        mLocalDbHelper = new LocalDbHelper(getContext());
        return true;
    }

    @Nullable
    @Override
    public String getType(Uri uri) {
        final int match = sUriMatcher.match(uri);
        switch (match) {
            case LIST:
                return ListPersistenceContract.CONTENT_LIST_TYPE;
            case LIST_ITEM:
                return ListPersistenceContract.CONTENT_LIST_ITEM_TYPE;
            case TASK:
                return TaskPersistenceContract.CONTENT_TASK_TYPE;
            case TASK_ITEM:
                return TaskPersistenceContract.CONTENT_TASK_ITEM_TYPE;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        Cursor retCursor;
        switch (sUriMatcher.match(uri)) {
            case LIST:
                retCursor = mLocalDbHelper.getReadableDatabase().query(
                        ListPersistenceContract.ListEntry.TABLE_NAME,
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null,
                        sortOrder
                );
                break;
            case LIST_ITEM:
                String[] where_list = {uri.getLastPathSegment()};
                retCursor = mLocalDbHelper.getReadableDatabase().query(
                        ListPersistenceContract.ListEntry.TABLE_NAME,
                        projection,
                        ListPersistenceContract.ListEntry._ID + " = ?",
                        where_list,
                        null,
                        null,
                        sortOrder
                );
                break;
            case LIST_AND_TASK:
                retCursor = sListAndTask.query(mLocalDbHelper.getReadableDatabase(),
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null,
                        sortOrder
                );
                break;
            case TASK:
                retCursor = mLocalDbHelper.getReadableDatabase().query(
                        TaskPersistenceContract.TaskEntry.TABLE_NAME,
                        projection,
                        selection,
                        selectionArgs,
                        null,
                        null,
                        sortOrder
                );
                break;
            case TASK_ITEM:
                String[] where_task = {uri.getLastPathSegment()};
                retCursor = mLocalDbHelper.getReadableDatabase().query(
                        TaskPersistenceContract.TaskEntry.TABLE_NAME,
                        projection,
                        TaskPersistenceContract.TaskEntry._ID + " = ?",
                        where_task,
                        null,
                        null,
                        sortOrder
                );
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
        retCursor.setNotificationUri(getContext().getContentResolver(), uri);
        return retCursor;
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues values) {
        final SQLiteDatabase db = mLocalDbHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        Uri returnUri;
        Cursor exists;

        switch (match) {
            case LIST:
                exists = db.query(
                        ListPersistenceContract.ListEntry.TABLE_NAME,
                        new String[]{ListPersistenceContract.ListEntry._ID},
                        ListPersistenceContract.ListEntry._ID + " = ?",
                        new String[]{values.getAsString(ListPersistenceContract.ListEntry._ID)},
                        null,
                        null,
                        null
                );
                if (exists.moveToLast()) {
                    long _id = db.update(
                            ListPersistenceContract.ListEntry.TABLE_NAME, values,
                            ListPersistenceContract.ListEntry._ID + " = ?",
                            new String[]{values.getAsString(ListPersistenceContract.ListEntry._ID)}
                    );
                    if (_id > 0) {
                        returnUri = ListPersistenceContract.ListEntry.buildListUriWith(_id);
                    } else {
                        throw new android.database.SQLException("Failed to insert row into " + uri);
                    }
                } else {
                    long _id = db.insert(ListPersistenceContract.ListEntry.TABLE_NAME, null, values);
                    if (_id > 0) {
                        returnUri = ListPersistenceContract.ListEntry.buildListUriWith(_id);
                    } else {
                        throw new android.database.SQLException("Failed to insert row into " + uri);
                    }
                }
                exists.close();
                break;
            case TASK:
                exists = db.query(
                        TaskPersistenceContract.TaskEntry.TABLE_NAME,
                        new String[]{TaskPersistenceContract.TaskEntry._ID},
                        TaskPersistenceContract.TaskEntry._ID + " = ?",
                        new String[]{values.getAsString(TaskPersistenceContract.TaskEntry._ID)},
                        null,
                        null,
                        null
                );
                if (exists.moveToLast()) {
                    long _id = db.update(
                            TaskPersistenceContract.TaskEntry.TABLE_NAME, values,
                            TaskPersistenceContract.TaskEntry._ID + " = ?",
                            new String[]{values.getAsString(TaskPersistenceContract.TaskEntry._ID)}
                    );
                    if (_id > 0) {
                        returnUri = TaskPersistenceContract.TaskEntry.buildTaskUriWith(_id);
                    } else {
                        throw new android.database.SQLException("Failed to insert row into " + uri);
                    }
                } else {
                    long _id = db.insert(TaskPersistenceContract.TaskEntry.TABLE_NAME, null, values);
                    if (_id > 0) {
                        returnUri = TaskPersistenceContract.TaskEntry.buildTaskUriWith(_id);
                    } else {
                        throw new android.database.SQLException("Failed to insert row into " + uri);
                    }
                }
                exists.close();
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return returnUri;

    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        final SQLiteDatabase db = mLocalDbHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        int rowsDeleted;

        switch (match) {
            case LIST:
                rowsDeleted = db.delete(
                        ListPersistenceContract.ListEntry.TABLE_NAME, selection, selectionArgs);
                break;
            case TASK:
                rowsDeleted = db.delete(
                        TaskPersistenceContract.TaskEntry.TABLE_NAME, selection, selectionArgs);
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
        if (selection == null || rowsDeleted != 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return rowsDeleted;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        final SQLiteDatabase db = mLocalDbHelper.getWritableDatabase();
        final int match = sUriMatcher.match(uri);
        int rowsUpdated;

        switch (match) {
            case LIST:
                rowsUpdated = db.update(ListPersistenceContract.ListEntry.TABLE_NAME, values, selection,
                        selectionArgs
                );
                break;
            case TASK:
                rowsUpdated = db.update(TaskPersistenceContract.TaskEntry.TABLE_NAME, values, selection,
                        selectionArgs
                );
                break;
            default:
                throw new UnsupportedOperationException("Unknown uri: " + uri);
        }
        if (rowsUpdated != 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
        return rowsUpdated;
    }
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!