处理多个表的最佳实践
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 提议。以上是关于处理多个表的最佳实践的主要内容,如果未能解决你的问题,请参考以下文章
处理一个项目的多个版本的 Flash Builder 最佳实践