处理多个表的最佳实践

Posted

技术标签:

【中文标题】处理多个表的最佳实践【英文标题】:Best practices for working with multiple tables 【发布时间】:2011-04-10 17:24:26 【问题描述】:

我在我的应用程序中使用包含多个表的数据库。我有一个 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(); 

关于如何实现这一点的任何想法?

【问题讨论】:

既然你已经有了代码,那就试试吧。我总是以全局方式保存数据库对象引用,只有在超出范围时才重新打开它。 我会试一试,但我担心我可能把整个概念弄错了,我应该以某种完全不同的方式来做 每次读写操作后无需关闭数据库连接。另外,为什么不能在一个适配器中同时拥有两张表?或者,就像我被打败的那样,有一个共享的数据库对象? 【参考方案1】:

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

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

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

这是我的抽象父类的代码,它基于记事本教程。孩子们只需扩展 this,调用 super 的构造函数(随意使用 this):

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

【讨论】:

我会尝试这种方法。也无法支持如此庞大的混乱类。我将采用一种方法,我将拥有一个包含所有静态结构的 Sigle MetaDataClass,而不是简化您帖子中的内部类。 我不擅长编写干净的代码,但我当然喜欢你的适配器的结构方式。非常感谢! 原来onUpgrade中绝对可以建表。只是为了节省大家的时间,我刚刚创建了一个空的测试应用程序,它创建一个表并填充它。然后在另一次运行中,增加数据库版本,创建另一个表并填充它。最终,AbstractDbAdapter派生的两个db适配器都可以同时访问,而且这两个表不是由同一个APK版本创建的。 SO +1,很好的实现。【参考方案2】:

我的数据库适配器。一个实例总是存储在 MyApplication 中,它继承自 Application。想想我定义第一个表的第二个表......目前这只是一个简短的版本,实际上这个适配器处理数据库中的 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);
        
    

【讨论】:

好的,感谢分享,我曾考虑使用一个适配器,但之后我认为它可能会很乱。只是为了确保我正确理解您的方法,您是否将所有数据库方法都包含在一个此类中?例如在你的 MyDatabaseManager 你 dropDatabase 类似于这样:private void dropDatabase(SQLiteDatabase db) db.execSQL("DROP TABLE IF EXISTS " + GameColumns.TABLE); db.execSQL("DROP TABLE IF EXISTS " + OtherColumns.TABLE); ... ? 我不想说我不喜欢它什么的,只是想理解。 没错。我的适配器目前有 1000 多行,但其中大部分是因为如果数据库为空,我会用动态内容填充一些表。为了防止“凌乱”的感觉,你应该有一个特定的命名规则:例如findTABLENAMEById(int id) 您也可以尝试使用更通用的方法:例如findById(String tableName, int id) 可以缩短你的代码和你使用的方法的数量【参考方案3】:

phoxicle 的解决方案是一个很好的起点,但根据 Kevin Galligan 的 notes on Android's SQLite serialization,此实现不是线程安全的,并且当多个数据库连接(例如来自不同线程)尝试写入数据库时​​会静默失败:

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

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


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

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();

【讨论】:

【参考方案4】:

我可能有点晚了,但我总是打开我的数据库,而不是我的表。所以这让我觉得毫无意义。

    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);

仅此而已。希望它可以帮助其他人

【讨论】:

t'as pas publiqué ton helper class pour qu'on puisse comprendre exactement ce que tu 提议。

以上是关于处理多个表的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章

使用Android中的内容提供程序公开多个表的最佳实践

Vue:处理多个 API 调用的最佳实践

表的 ASP.NET 最佳实践

处理一个项目的多个版本的 Flash Builder 最佳实践

Django/Python:处理多个多向外部 API 的外部 ID 的最佳实践/建议

Invoke-AzVMRunCommand - 错误处理的最佳实践?