【问题标题】:Best practices for working with multiple tables处理多个表的最佳实践
【发布时间】:2023-04-08 11:42:01
【问题描述】:

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

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

【问题讨论】:

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

标签: android database android-sqlite


【解决方案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 提议。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-11
    • 2019-04-25
    • 1970-01-01
    • 2013-08-25
    • 2010-11-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多