使用多个表的最佳实践

时间:2010-09-10 12:42:54

标签: android database android-sqlite

我在我的应用程序中使用包含多个表的数据库。我有一个XML解析器,需要在解析时将数据写入两个表。我为这两个表创建了两个数据库适配器,但现在我遇到了问题。当我使用一张桌子时,很容易:

FirstDBAdapter firstTable = new FirstDBAdapter(mycontext);
firstTable.open(); // open and close it every time I need to insert something
                   // may be hundreds of times while parsing
                   // it opens not a table but whole DB     
firstTable.insertItem(Item);        
firstTable.close(); 

因为它是一个SAX解析器,在我看来(也许我错了),这会更好:

FirstDBAdapter firstTable = new FirstDBAdapter(mycontext);

@Override
public void startDocument() throws SAXException 
{
    firstTable.open(); // open and close only once
}

...
firstTable.insertItem(Item);
...

@Override
public void endDocument() throws SAXException 
{
    firstTable.close();
}

但是,如果我需要将数据插入第二个表,我该怎么做?例如,如果我有第二个适配器,我认为这是一个坏主意:

FirstDBAdapter firstTable = new FirstDBAdapter(mycontext);
SecondDBAdapter secondTable = new SecondDBAdapter(mycontext);

@Override
public void startDocument() throws SAXException 
{
    firstTable.open();
    secondTable.open(); 
}

有关如何实现这一目标的任何想法?

4 个答案:

答案 0 :(得分:31)

我成功创建了一个带有数据库名称/ create语句和其他共享信息的抽象基类,然后为每个表扩展它。这样,我可以将所有CRUD方法分开(我更喜欢)。唯一的缺点是DATABASE_CREATE语句必须驻留在父类中,并且必须包含所有表,因为之后不能添加新表,但在我看来,保留CRUD的代价很小每个表的方法分开。

这样做很简单,但这里有一些注意事项:

  • 必须为每个表分解父类中的create语句,因为db.execSQL不能执行多个语句。
  • 我将所有私有变量/方法更改为受保护,以防万一。
  • 如果要将表添加到现有应用程序(不确定这是否特定于模拟器),则必须卸载该应用程序,然后重新安装。

这是我的抽象父类的代码,它基于Notepad Tutorial。孩子们只是扩展这个,调用super的构造函数(随意使用它):

package com.pheide.trainose;

import android.content.Context;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public abstract class AbstractDbAdapter {

    protected static final String TAG = "TrainOseDbAdapter";
    protected DatabaseHelper mDbHelper;
    protected SQLiteDatabase mDb;

    protected static final String TABLE_CREATE_ROUTES =
        "create table routes (_id integer primary key autoincrement, "
        + "source text not null, destination text not null);";
    protected static final String TABLE_CREATE_TIMETABLES =    
        "create table timetables (_id integer primary key autoincrement, "
        + "route_id integer, depart text not null, arrive text not null, "
        + "train text not null);";

    protected static final String DATABASE_NAME = "data";
    protected static final int DATABASE_VERSION = 2;

    protected final Context mCtx;

    protected static class DatabaseHelper extends SQLiteOpenHelper {

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

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(TABLE_CREATE_ROUTES);
            db.execSQL(TABLE_CREATE_TIMETABLES);
        }

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

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

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

    public void close() {
        mDbHelper.close();
    }

}

这里有一个稍微详细的解释:http://pheide.com/page/11/tab/24#post13

答案 1 :(得分:15)

我的数据库适配器。实例始终存储在从Application继承的MyApplication中。想想第二个表我定义了第一个表...目前这只是一个简短的版本,实际上这个适配器处理数据库中的7个表。

public class MyDbAdapter {
    private static final String LOG_TAG = MyDbAdapter.class.getSimpleName();

    private SQLiteDatabase mDb;
    private static MyDatabaseManager mDbManager;

    public MyDbAdapter() {
        mDbManager = new MyDatabaseManager(MyApplication.getApplication());
        mDb = mDbManager.getWritableDatabase();
    }

    public static final class GameColumns implements BaseColumns {
        public static final String TABLE = "game";
        public static final String IMEI = "imei";
        public static final String LAST_UPDATE = "lastupdate";
        public static final String NICKNAME = "nickname";
    }

    public String getImei() {
        checkDbState();
        String retValue = "";
        Cursor c = mDb.rawQuery("SELECT imei FROM " + GameColumns.TABLE, null);
        if (c.moveToFirst()) {
            retValue = c.getString(c.getColumnIndex(GameColumns.IMEI));
        }
        c.close();
        return retValue;
    }

    public void setImei(String imei) {
        checkDbState();
        ContentValues cv = new ContentValues();
        cv.put(GameColumns.IMEI, imei);
        mDb.update(GameColumns.TABLE, cv, null, null);
    }

    public boolean isOpen() {
        return mDb != null && mDb.isOpen();
    }

    public void open() {
        mDbManager = new MyDatabaseManager(MyApplication.getApplication());
        if (!isOpen()) {
            mDb = mDbManager.getWritableDatabase();
        }
    }

    public void close() {
        if (isOpen()) {
            mDb.close();
            mDb = null;
            if (mDbManager != null) {
                mDbManager.close();
                mDbManager = null;
            }
        }
    }

    private void checkDbState() {
        if (mDb == null || !mDb.isOpen()) {
            throw new IllegalStateException("The database has not been opened");
        }
    }

    private static class MyDatabaseManager extends SQLiteOpenHelper {
        private static final String DATABASE_NAME = "dbname";
        private static final int DATABASE_VERSION = 7;

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

        @Override
        public void onCreate(SQLiteDatabase db) {
            createGameTable(db);
        }

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            Log.w(LOG_TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + "!");
        }

        private void dropDatabase(SQLiteDatabase db) {
            db.execSQL("DROP TABLE IF EXISTS " + GameColumns.TABLE);
        }

        private void createGameTable(SQLiteDatabase db) {
            db.execSQL("CREATE TABLE " + GameColumns.TABLE + " ("
                    + GameColumns._ID + " INTEGER PRIMARY KEY,"
                    + GameColumns.IMEI + " TEXT,"
                    + GameColumns.LAST_UPDATE + " TEXT,"
                    + GameColumns.NICKNAME + " TEXT);");
            ContentValues cv = new ContentValues();
            cv.put(GameColumns.IMEI, "123456789012345");
            cv.put(GameColumns.LAST_UPDATE, 0);
            cv.put(GameColumns.NICKNAME, (String) null);
            db.insert(GameColumns.TABLE, null, cv);
        }
    }
}

答案 2 :(得分:10)

phoxicle的解决方案是一个很好的起点,但根据Kevin Galligan的notes on Android's SQLite serialization,这个实现不是线程安全的,当多个数据库连接(例如来自不同的线程)尝试编写数据库时,它将无声地失败:

  

如果您尝试同时从实际的不同连接写入数据库,则会失败。它不会等到第一个完成然后写。它根本不会写你的改变。更糟糕的是,如果您没有在SQLiteDatabase上调用正确版本的插入/更新,则不会出现异常。您只需在LogCat中收到一条消息即可。

     

那么,多线程?使用一个帮手。


以下是使用静态SQLiteOpenHelper实例的phoxicle数据库适配器的修改实现,因此仅限于单个数据库连接:

public class DBBaseAdapter {

    private static final String TAG = "DBBaseAdapter";

    protected static final String DATABASE_NAME = "db.sqlite";
    protected static final int DATABASE_VERSION = 1;

    protected Context mContext;
    protected static DatabaseHelper mDbHelper;

    private static final String TABLE_CREATE_FOO = 
        "create table foo (_id integer primary key autoincrement, " +
        "bar text not null)");

    public DBBaseAdapter(Context context) {
        mContext = context.getApplicationContext();
    }

    public SQLiteDatabase openDb() {
        if (mDbHelper == null) {
            mDbHelper = new DatabaseHelper(mContext);
        }
        return mDbHelper.getWritableDatabase();
    }

    public void closeDb() {
        mDbHelper.close();
    }

    protected static class DatabaseHelper extends SQLiteOpenHelper {

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

        @Override
        public void onCreate(SQLiteDatabase db) {
            db.execSQL(TABLE_CREATE_FOO);
        }

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

为每个表扩展DBBaseAdapter以实现您的CRUD方法:

public class DBFooTable extends DBBaseAdapter {

    public DBFooTable(Context context) {
        super(context);
    }

    public void getBar() {

        SQLiteDatabase db = openDb();
        // ...
        closeDb();
}

答案 3 :(得分:1)

我有点迟了但是我总是打开我的数据库,而不是我的桌子。所以这对我来说毫无意义。

    firstTable.open();
    secondTable.open(); 

而是这样做。

    dataBase.getWritableDatabase();

然后如果你想更新juste,请选择表格:

public int updateTotal (int id, Jours jour){
    ContentValues values = new ContentValues();

    values.put(COL_TOTAL,Total );

    //update the table you want
    return bdd.update(TABLE_NAME, values, COL_JOUR + " = " + id, null);
}

就是这样。希望它可以帮助其他人