在Android中使用多表SQLite数据库适配器?

60

我正在阅读 Android SQLite NotePad 教程,其中提到创建一个数据库适配器类来创建和访问数据库表。在处理多表 SQLite 数据库时,是创建每个表的不同适配器类最佳实践,还是为整个 Android 应用程序创建一个单一的 DB 适配器类?

我的应用程序使用多个表格,我希望不必拥有一个单一的庞大适配器类。然而问题在于,我在每个适配器中都有一个嵌套的 SQLiteOpenHelper 子类,就像NotePad示例中一样。当访问第一张表格时,一切正常。但是当我尝试从另一个活动访问第二个表时,我的应用程序会崩溃。

起初,我认为崩溃是由版本问题引起的,但是两个适配器现在具有相同的数据库版本,仍然会崩溃。

这是一个表格的 DB 适配器示例。其他适配器都遵循相同的格式,但实现不同。

public class InfoDBAdapter {
    public static final String ROW_ID = "_id";
    public static final String NAME = "name";

    private static final String TAG = "InfoDbAdapter";
    private static final String DATABASE_NAME = "myappdb";
    private static final String DATABASE_TABLE = "usersinfo";
    private static final int DATABASE_VERSION = 1;


    private static final String DATABASE_CREATE = "create table usersinfo (_id integer primary key autoincrement, "
            + NAME
            + " TEXT," + ");";

    private DatabaseHelper mDbHelper;
    private SQLiteDatabase mDb;

    private final Context mCtx;

    private static class DatabaseHelper extends SQLiteOpenHelper {

        DatabaseHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {

            db.execSQL(DATABASE_CREATE);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(TAG, "Upgrading database from version " + oldVersion + " to " //$NON-NLS-1$//$NON-NLS-2$
                    + newVersion + ", which will destroy all old data"); //$NON-NLS-1$
            //db.execSQL("DROP TABLE IF EXISTS usersinfo"); //$NON-NLS-1$
            onCreate(db);
        }
    }


    public InfoDBAdapter(Context ctx) {
        this.mCtx = ctx;
    }


    public InfoDBAdapter open() throws SQLException {
        this.mDbHelper = new DatabaseHelper(this.mCtx);
        this.mDb = this.mDbHelper.getWritableDatabase();
        return this;
    }

    /**
     * close return type: void
     */
    public void close() {
        this.mDbHelper.close();
    }


    public long createUser(String name) {
        ContentValues initialValues = new ContentValues();
        initialValues.put(NAME, name);
        return this.mDb.insert(DATABASE_TABLE, null, initialValues);
    }


    public boolean deleteUser(long rowId) {

        return this.mDb.delete(DATABASE_TABLE, ROW_ID + "=" + rowId, null) > 0; //$NON-NLS-1$
    }


    public Cursor fetchAllUsers() {

        return this.mDb.query(DATABASE_TABLE, new String[] { ROW_ID,
                NAME}, null, null, null, null, null);
    }


    public Cursor fetchUser(long rowId) throws SQLException {

        Cursor mCursor =

        this.mDb.query(true, DATABASE_TABLE, new String[] { ROW_ID, NAME}, ROW_ID + "=" + rowId, null, //$NON-NLS-1$
                null, null, null, null);
        if (mCursor != null) {
            mCursor.moveToFirst();
        }
        return mCursor;

    }


    public boolean updateUser(long rowId, String name) {
        ContentValues args = new ContentValues();
        args.put(NAME, name);
        return this.mDb
                .update(DATABASE_TABLE, args, ROW_ID + "=" + rowId, null) > 0; //$NON-NLS-1$
    }
}
当第一个适配器(在这种情况下为usersinfo)被访问时,一切都按预期发生。假设我有另一个适配器用于朋友信息,遵循与上述相同的结构,当它由不同的活动访问时,我会认为SQLiteOpenHelper的嵌套子类会尝试再次创建数据库。显然出现了问题,因为在那种情况下,我的应用程序会崩溃。
在Android中的标准做法是创建一个单一的巨大的数据库适配器,而不是每个表都有一个适配器吗?

这里是同样的问题,但有实际的答案:https://dev59.com/UXA65IYBdhLWcg3wsgyA - Joel McBeth
2个回答

80

以下是我最终采用的解决方案。它是从Commonsware书籍中获得的信息,以及一些在网上找到的东西“大杂烩”,如果我能够收藏它们并想给出信用:

对于我需要从数据库中拉取的每种类型的数据,我创建一个“适配器”类(不是从任何类派生的子类)。这些适配器类包含了访问该信息的数据库所需的所有方法。例如,如果我的数据库中有三个表:

  1. Cars
  2. Boats
  3. Motorcycles

我将拥有三个适配器,类似于以下示例(我只放一个作为演示,但每个适配器的想法都相同):

public class CarsDBAdapter {
    public static final String ROW_ID = "_id";
    public static final String NAME = "name";
    public static final String MODEL = "model";
    public static final String YEAR = "year";
    
    private static final String DATABASE_TABLE = "cars";

    private DatabaseHelper mDbHelper;
    private SQLiteDatabase mDb;

    private final Context mCtx;

    private static class DatabaseHelper extends SQLiteOpenHelper {

        DatabaseHelper(Context context) {
            super(context, DBAdapter.DATABASE_NAME, null, DBAdapter.DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) {
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        }
    }

    /**
     * Constructor - takes the context to allow the database to be
     * opened/created
     * 
     * @param ctx
     *            the Context within which to work
     */
    public CarsDBAdapter(Context ctx) {
        this.mCtx = ctx;
    }

    /**
     * Open the cars database. If it cannot be opened, try to create a new
     * instance of the database. If it cannot be created, throw an exception to
     * signal the failure
     * 
     * @return this (self reference, allowing this to be chained in an
     *         initialization call)
     * @throws SQLException
     *             if the database could be neither opened or created
     */
    public CarsDBAdapter open() throws SQLException {
        this.mDbHelper = new DatabaseHelper(this.mCtx);
        this.mDb = this.mDbHelper.getWritableDatabase();
        return this;
    }

    /**
     * close return type: void
     */
    public void close() {
        this.mDbHelper.close();
    }

    /**
     * Create a new car. If the car is successfully created return the new
     * rowId for that car, otherwise return a -1 to indicate failure.
     * 
     * @param name
     * @param model
     * @param year
     * @return rowId or -1 if failed
     */
    public long createCar(String name, String model, String year){
        ContentValues initialValues = new ContentValues();
        initialValues.put(NAME, name);
        initialValues.put(MODEL, model);
        initialValues.put(YEAR, year);
        return this.mDb.insert(DATABASE_TABLE, null, initialValues);
    }

    /**
     * Delete the car with the given rowId
     * 
     * @param rowId
     * @return true if deleted, false otherwise
     */
    public boolean deleteCar(long rowId) {

        return this.mDb.delete(DATABASE_TABLE, ROW_ID + "=" + rowId, null) > 0; //$NON-NLS-1$
    }

    /**
     * Return a Cursor over the list of all cars in the database
     * 
     * @return Cursor over all cars
     */
    public Cursor getAllCars() {

        return this.mDb.query(DATABASE_TABLE, new String[] { ROW_ID,
                NAME, MODEL, YEAR }, null, null, null, null, null);
    }

    /**
     * Return a Cursor positioned at the car that matches the given rowId
     * @param rowId
     * @return Cursor positioned to matching car, if found
     * @throws SQLException if car could not be found/retrieved
     */
    public Cursor getCar(long rowId) throws SQLException {

        Cursor mCursor =

        this.mDb.query(true, DATABASE_TABLE, new String[] { ROW_ID, NAME,
                MODEL, YEAR}, ROW_ID + "=" + rowId, null, null, null, null, null);
        if (mCursor != null) {
            mCursor.moveToFirst();
        }
        return mCursor;
    }

    /**
     * Update the car.
     * 
     * @param rowId
     * @param name
     * @param model
     * @param year
     * @return true if the note was successfully updated, false otherwise
     */
    public boolean updateCar(long rowId, String name, String model,
            String year){
        ContentValues args = new ContentValues();
        args.put(NAME, name);
        args.put(MODEL, model);
        args.put(YEAR, year);

        return this.mDb.update(DATABASE_TABLE, args, ROW_ID + "=" + rowId, null) >0; 
    }

}

所以,如果您想象我有每个表一个这样的“适配器”类。

当我的应用程序启动画面开始时,我使用了在Android For Beginners: Creating multiple SQLite Tables for Android中介绍的技术。

因此,我的主要DBAdapter(负责在单个数据库中创建所有表格)看起来像这样:

public class DBAdapter {
    
    public static final String DATABASE_NAME = "stuffIOwn"; //$NON-NLS-1$
    
    public static final int DATABASE_VERSION = 1;
    
    private static final String CREATE_TABLE_CARS =
       "create table cars (_id integer primary key autoincrement, " //$NON-NLS-1$
    + CarsDBAdapter.NAME+ " TEXT," //$NON-NLS-1$
    + CarsDBAdapter.MODEL+ " TEXT," //$NON-NLS-1$
    + CarsDBAdapter.YEAR+ " TEXT" + ");"; //$NON-NLS-1$ //$NON-NLS-2$
            
    private static final String CREATE_TABLE_BOATS = "create table boats (_id integer primary key autoincrement, " //$NON-NLS-1$
    +BoatsDBAdapter.NAME+" TEXT," //$NON-NLS-1$
    +BoatsDBAdapter.MODEL+" TEXT," //$NON-NLS-1$
    +BoatsDBAdapter.YEAR+" TEXT"+ ");"; //$NON-NLS-1$  //$NON-NLS-2$

        private static final String CREATE_TABLE_CYCLES = "create table cycles (_id integer primary key autoincrement, " //$NON-NLS-1$
    +CyclesDBAdapter.NAME+" TEXT," //$NON-NLS-1$
    +CyclesDBAdapter.MODEL+" TEXT," //$NON-NLS-1$
    +CyclesDBAdapter.YEAR+" TEXT"+ ");"; //$NON-NLS-1$  //$NON-NLS-2$

    
    private final Context context; 
    private DatabaseHelper DBHelper;
    private SQLiteDatabase db;
    
    /**
     * Constructor
     * @param ctx
     */
    public DBAdapter(Context ctx)
    {
        this.context = ctx;
        this.DBHelper = new DatabaseHelper(this.context);
    }
    
    private static class DatabaseHelper extends SQLiteOpenHelper 
    {
        DatabaseHelper(Context context) 
        {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }

        @Override
        public void onCreate(SQLiteDatabase db) 
        {
            db.execSQL(CREATE_TABLE_CARS);
            db.execSQL(CREATE_TABLE_BOATS);
            db.execSQL(CREATE_TABLE_CYCLES);           
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, 
        int newVersion) 
        {               
            // Adding any table mods to this guy here
        }
    } 
    
   /**
     * open the db
     * @return this
     * @throws SQLException
     * return type: DBAdapter
     */
    public DBAdapter open() throws SQLException 
    {
        this.db = this.DBHelper.getWritableDatabase();
        return this;
    }
    
    /**
     * close the db 
     * return type: void
     */
    public void close() 
    {
        this.DBHelper.close();
    }
}

DBAdapter类只在应用程序首次启动时被调用,其唯一责任是创建/升级表。对数据的所有其他访问都通过各个“适配器”类进行。我发现这很完美地运作,并且不会产生我之前提到的版本控制问题。


3
如果你需要合并表格,你能做到吗? - STT LCU
1
我猜你从每个适配器都可以访问全局数据库,所以联接表不应该是一个问题。但我仍然在想,每个适配器中有多个SqliteOpenHelper子类是否是一个好的方法...? - Paranoid Android
19
这是一个很好的解决方案,但如果我能提出一个建议以进一步改进它:我会让我的各个表适配器扩展(extend)DBAdapter类,从而强制数据库在您打开任何表适配器时检查是否需要升级。 这还消除了在应用程序首次启动时实例化DBAdapter的需要。 - Matt
我觉得之前的评论非常有用和合理。+1 - Konstantin Milyutin
使用@Shawn解释的概念,如何实现外键约束的触发器? - Rushabh Shah
2
我遵循了Matt的建议,让表适配器扩展了DBAdapter类。我将DBAdapterdb字段设置为受保护的,以便在表适配器中可见,并从表适配器中删除了内部的DatabaseHelper类。一切正常! - Stephen Hosking

9

我曾经遇到过同样的问题,尝试了很多解决方案,最终我创建了一个抽象方法来构建数据库结构,并为表类创建了扩展类。

这是我的数据库构造器抽象类:

public abstract class dbAdapter {
    public static String DATABASE_NAME = "";
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_TABLE1 = "ContactName";
    public static final String DATABASE_TABLE2 = "PhoneNumber";

    public static DbHelper ourHelper;
    public static Context ourContext;
    public static SQLiteDatabase ourDatabase;

    boolean ourConstructorBool = false;
    boolean ourDB = false;

    public static final String ContactNameTable = "CREATE TABLE "+DATABASE_TABLE1+" (" +
        ContactNameAdapter.KEY_ROWID+" INTEGER PRIMARY KEY AUTOINCREMENT, " +
        ContactNameAdapter.KEY_NAME+" TEXT, " +
        ContactNameAdapter.KEY_BIRTH_DATE+" TEXT);";

    public static final String PhoneNumberTable = "CREATE TABLE "+DATABASE_TABLE2+" (" + 
        PhoneNumberAdapter.KEY_NUMBER+" TEXT , " +
        PhoneNumberAdapter.KEY_DESCRIPTION+" TEXT, " +
        PhoneNumberAdapter.KEY_CONTACTID+" TEXT, " +
        "FOREIGN KEY(" + PhoneNumberAdapter.KEY_CONTACTID +") REFERENCES " +
        (ContactNameAdapter.DATABASE_TABLE)+"("+ContactNameAdapter.KEY_ROWID+") ON DELETE CASCADE"+
    ");";

    static class DbHelper extends SQLiteOpenHelper{
        public DbHelper(Context context) {
            super(context, DATABASE_NAME, null, DATABASE_VERSION);
        }
        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(ContactNameTable);
            db.execSQL(PhoneNumberTable);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int arg1, int arg2) {
            db.execSQL("DROP TABLE IF EXISTS " + ContactNameAdapter.DATABASE_TABLE);
            db.execSQL("DROP TABLE IF EXISTS " + PhoneNumberAdapter.DATABASE_TABLE);
            onCreate(db);
        }
    }

    public dbAdapter(Activity a){   
        if(!ourConstructorBool == true){
            ourContext = a;
            DATABASE_NAME = a.getString(Asaf.com.contactsEX.R.string.DB_NAME);
            ourConstructorBool = true;
        }
    }

    public dbAdapter open() throws SQLException{
        if(!ourDB == true){
            ourHelper = new DbHelper(ourContext);
            ourDB = true;
        }
        ourDatabase = ourHelper.getWritableDatabase();
        return this;
    }

    public void close(){
        if(ourDatabase.isOpen())
            ourHelper.close();
    }
}

这是我其中一个表格类,其他的类也是同样实现的,您可以根据需要添加:

public class PhoneNumberAdapter extends dbAdapter{

    public static final String KEY_NUMBER = "PhoneNumber";
    public static final String KEY_DESCRIPTION = "Description";
    public static final String KEY_CONTACTID = "ContactName_id";

    public static final String DATABASE_TABLE = "PhoneNumber";

    public PhoneNumberAdapter(Activity a){
        super(a);
    }

    public long createEntry(String number, String description,long id){
        // TODO Auto-generated method stub
        ContentValues cv = new ContentValues();
        cv.put(KEY_NUMBER, number);
        cv.put(KEY_DESCRIPTION, description);
        cv.put(KEY_CONTACTID, id);
        return ourDatabase.insert(DATABASE_TABLE, null,cv);
    }
}

希望我有所帮助。

2
为什么要将Activity实例传递给构造函数?不应该是Context吗? - Paranoid Android
如果ourConstructorBool没有被声明为“static”,那么它怎么可能是true呢? - Ohmnibus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接