[英]How to drop previous records on Android SQLite database only after inserting some new ones
我正在構建一個Android應用程序,它可以查看HTML頁面的內容(學校的網站沒有給我們官方應用程序,也沒有公共API)。
我無法確定數據是否完全是新的,因此目前所有獲取數據並將其插入數據庫都是在AsyncTask中完成的,基本上是這樣的:
然后在AsyncTask結果中:
問題是:如果用戶進入應用程序中從recreateDatabase()和insertSubjectList()之間讀取數據庫中的數據的區域,則數據庫中可能幾乎沒有數據,因為它當前正在重新填充。
它不會使應用程序崩潰,但這是一種不受歡迎的行為。 我的問題是:是否有可能告訴SQLite只有在新數據可用於查詢后才清理舊數據? 由於他們的信息可能會崩潰,因此無法同時讀取新舊內容。
希望有人可以幫我解決這個問題!
聽起來像而不是: -
你要
ALTER TABLE original TO original_renamed
因此,在加載數據時原始表仍然存在,然后僅在相對較短的時間段內將沒有數據可用。
這也可以在事務中完成,這可能會提高性能。
因此,這將回答“是”
是否有可能告訴SQLite只有在新數據可用於查詢后才清理舊數據?
至於
由於他們的信息可能會崩潰,因此無法同時讀取新舊內容。
它們不會同時被閱讀。
例如,考慮以下演示: -
DROP TABLE IF EXISTS master;
CREATE TABLE IF NOT EXISTS master (mydata TEXT);
-- Load the original table with some data
WITH RECURSIVE cte1(x) AS (SELECT 1 UNION ALL SELECT x+1 FROM cte1 where x < 1000)
INSERT INTO master SELECT * FROM cte1;
SELECT * FROM master LIMIT 10;
BEGIN TRANSACTION;
-- Load the new data (original table still available)
CREATE TABLE IF NOT EXISTS transitional (mydata TEXT);
WITH RECURSIVE cte1(x) AS (SELECT 2000 UNION ALL SELECT x+1 FROM cte1 where x < 3000)
INSERT INTO transitional SELECT * FROM cte1;
SELECT * FROM transitional LIMIT 10;
-- Switch the tables - original not available
ALTER TABLE master RENAME TO master_old;
ALTER TABLE transitional RENAME TO master;
-- original now available
-- Clean-up old
DROP TABLE master_old;
END TRANSACTION;
SELECT * FROM master LIMIT 10;
以下是與上述類似的有效Android示例的代碼。
調用活動和數據庫助手是2段代碼
public class MainActivity extends AppCompatActivity {
DBHelper mDBHlpr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Instantiate the helper noting that data will be loaded
mDBHlpr = new DBHelper(this);
// Get the current data
Cursor csr = mDBHlpr.getFirst10Rows();
// Write the current data to the log
DatabaseUtils.dumpCursor(csr);
//<<<<<<<<<< do the switch >>>>>>>>>>
mDBHlpr.switchData(DBHelper.TABLE_MASTER);
//Again get the data and write to the log
csr = mDBHlpr.getFirst10Rows();
DatabaseUtils.dumpCursor(csr);
//Clean up
csr.close();
}
}
public class DBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "mydb";
private static final int DBVERSION = 1;
public static final String TABLE_MASTER = "master";
public static final String COl_MASTER_ID = BaseColumns._ID;
public static final String COL_MASTER_MYDATA = "mydata";
SQLiteDatabase mDB;
//Construct the helper NOTE will create the db if needed
public DBHelper(Context context) {
super(context, DBNAME,null, DBVERSION);
mDB = this.getWritableDatabase(); // force db open
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(create_table_sql(TABLE_MASTER));
mDB = db;
addLotsOfData(TABLE_MASTER,1000,1); // Load some data
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
// Generate the table SQL according to the table name passed
private String create_table_sql(String table) {
return "CREATE TABLE IF NOT EXISTS " + table + "(" +
COl_MASTER_ID + " INTEGER PRIMARY KEY," +
COL_MASTER_MYDATA + " TEXT" +
")";
}
// Switch the original table for a newly created version
public void switchData(String table) {
String transitional_table = "transitional";
String renamed_master = table + "_old";
boolean already_in_transaction = mDB.inTransaction();
if (!already_in_transaction) {
mDB.beginTransaction();
}
//<<<<<<<<<< create and load of new data could be done elsewhere
mDB.execSQL(create_table_sql(transitional_table));
addLotsOfData(transitional_table,1000,3001); //<<<<<<<< load new data here
//>>>>>>>>>>
mDB.execSQL("ALTER TABLE " + TABLE_MASTER + " RENAME TO " + renamed_master);
mDB.execSQL("ALTER TABLE " + transitional_table + " RENAME TO " + TABLE_MASTER);
mDB.execSQL("DROP TABLE IF EXISTS " + renamed_master);
if (!already_in_transaction) {
mDB.setTransactionSuccessful();
mDB.endTransaction();
}
}
// Add some data
private void addLotsOfData(String table, int rows, int value_offset) {
boolean already_in_transaction = mDB.inTransaction();
if (!already_in_transaction) {
mDB.beginTransaction();
}
for (int i = 0; i < rows; i++) {
addRow(table,String.valueOf(value_offset + i));
}
if (!already_in_transaction) {
mDB.setTransactionSuccessful();
mDB.endTransaction();
}
}
// Insert a single row
public long addRow(String table, String value) {
ContentValues cv = new ContentValues();
cv.put(COL_MASTER_MYDATA,value);
return mDB.insert(table,null,cv);
}
public Cursor getFirst10Rows() {
return mDB.query(TABLE_MASTER,null,null,null,null,null,null,"10");
}
}
日志顯示(如預期): -
04-26 08:42:43.556I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@11697ce
04-26 08:42:43.557I/System.out: 0 {
04-26 08:42:43.557I/System.out: _id=1
04-26 08:42:43.557I/System.out: mydata=1
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 1 {
04-26 08:42:43.557I/System.out: _id=2
04-26 08:42:43.557I/System.out: mydata=2
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 2 {
04-26 08:42:43.557I/System.out: _id=3
04-26 08:42:43.557I/System.out: mydata=3
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 3 {
04-26 08:42:43.557I/System.out: _id=4
04-26 08:42:43.557I/System.out: mydata=4
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 4 {
04-26 08:42:43.557I/System.out: _id=5
04-26 08:42:43.557I/System.out: mydata=5
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 5 {
04-26 08:42:43.557I/System.out: _id=6
04-26 08:42:43.557I/System.out: mydata=6
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 6 {
04-26 08:42:43.557I/System.out: _id=7
04-26 08:42:43.557I/System.out: mydata=7
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 7 {
04-26 08:42:43.557I/System.out: _id=8
04-26 08:42:43.557I/System.out: mydata=8
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 8 {
04-26 08:42:43.557I/System.out: _id=9
04-26 08:42:43.557I/System.out: mydata=9
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: 9 {
04-26 08:42:43.557I/System.out: _id=10
04-26 08:42:43.557I/System.out: mydata=10
04-26 08:42:43.557I/System.out: }
04-26 08:42:43.557I/System.out: <<<<<
04-26 08:42:43.652I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@3396ef
04-26 08:42:43.652I/System.out: 0 {
04-26 08:42:43.652I/System.out: _id=1
04-26 08:42:43.652I/System.out: mydata=3001
04-26 08:42:43.652I/System.out: }
04-26 08:42:43.652I/System.out: 1 {
04-26 08:42:43.652I/System.out: _id=2
04-26 08:42:43.652I/System.out: mydata=3002
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 2 {
04-26 08:42:43.653I/System.out: _id=3
04-26 08:42:43.653I/System.out: mydata=3003
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 3 {
04-26 08:42:43.653I/System.out: _id=4
04-26 08:42:43.653I/System.out: mydata=3004
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 4 {
04-26 08:42:43.653I/System.out: _id=5
04-26 08:42:43.653I/System.out: mydata=3005
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 5 {
04-26 08:42:43.653I/System.out: _id=6
04-26 08:42:43.653I/System.out: mydata=3006
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 6 {
04-26 08:42:43.653I/System.out: _id=7
04-26 08:42:43.653I/System.out: mydata=3007
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 7 {
04-26 08:42:43.653I/System.out: _id=8
04-26 08:42:43.653I/System.out: mydata=3008
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.653I/System.out: 8 {
04-26 08:42:43.653I/System.out: _id=9
04-26 08:42:43.653I/System.out: mydata=3009
04-26 08:42:43.653I/System.out: }
04-26 08:42:43.654I/System.out: 9 {
04-26 08:42:43.654I/System.out: _id=10
04-26 08:42:43.654I/System.out: mydata=3010
04-26 08:42:43.654I/System.out: }
04-26 08:42:43.654I/System.out: <<<<<
這是一種更通用的方法,可以處理多個表,禁止顯式的App / table特定加載新數據到轉換表。
已添加日志記錄,這可以啟用計時。
例如,運行此行並加載10,000行(使用mDBHlpr.addLotsOfData(DBHelper.TABLE_MASTER + DBHelper.TRANSITION_SUFFIX,100000,10000);
) 。 可以看出: -
即使表加載數據(10,000行)需要0.659秒,表實際上只有0.007秒不可用(如果DROP在切換后完成,則為0.001秒(丟棄表可能是昂貴的時間,同時重命名)一張桌子花很少的時間))。
修訂后的活動是: -
public class MainActivity extends AppCompatActivity {
DBHelper mDBHlpr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Instantiate the helper noting that data will be loaded
mDBHlpr = new DBHelper(this);
// Get the current data
Cursor csr = mDBHlpr.getFirst10Rows();
// Write the current data to the log
DatabaseUtils.dumpCursor(csr);
//<<<<<<<<<< do the switch >>>>>>>>>>
mDBHlpr.switchData(DBHelper.TABLE_MASTER);
//Again get the data and write to the log
csr = mDBHlpr.getFirst10Rows();
DatabaseUtils.dumpCursor(csr);
//<<<<<<<<<<<<<<< AutoSwitch example >>>>>>>>>>
//Create the transition tables
mDBHlpr.prepareSwitchAllAppTables();
Log.d("NEWDATALOADSTART","Loading of new data has started");
// Prepare the new data by loading the data into the transition table
// (only the 1 table for demo)
// but perhaps 1 load per table according to requirements
mDBHlpr.addLotsOfData(DBHelper.TABLE_MASTER + DBHelper.TRANSITION_SUFFIX,100000,10000);
Log.d("TABLESNOTAVAILABLE","Tables will now be unavailable whil switching");
// Switch all of the tables
mDBHlpr.doSwitchAllAppTables();
Log.d("TABLESAVAILABLE","Switch completed, Tables are now available with new data");
//Again get the data and write to the log
csr = mDBHlpr.getFirst10Rows();
DatabaseUtils.dumpCursor(csr);
//Clean up
csr.close();
}
}
//<<<<<<<<<<<<<<< AutoSwitch example >>>>>>>>>>
已添加,但csr.close()
仍然是最后一行。 和修改后的DBHelpr.java
public class DBHelper extends SQLiteOpenHelper {
public static final String DBNAME = "mydb";
private static final int DBVERSION = 1;
public static final String TABLE_MASTER = "master";
public static final String COl_MASTER_ID = BaseColumns._ID;
public static final String COL_MASTER_MYDATA = "mydata";
public static final String TRANSITION_SUFFIX = "_trn";
public static final String RENAME_SUFFIX = "rename";
private static final String SQLITE_MASTER = "sqlite_master";
private static final String SQLITE_MASTER_TYPECOLUMN = "type";
private static final String SQLITE_MASTER_NAMECOLUMN = "name";
private static final String SQLITE_MASTER_SQLCOLUMN = "sql";
private static final String[] SQLITE_MATSER_COLUMNS = new String[]{SQLITE_MASTER_NAMECOLUMN,SQLITE_MASTER_SQLCOLUMN};
private static final String APPTABLES_WHERECLAUSE =
"(" +
SQLITE_MASTER_NAMECOLUMN + " NOT LIKE 'sqlite%' " +
" AND " + SQLITE_MASTER_NAMECOLUMN + " NOT LIKE 'android%' " +
" AND " + SQLITE_MASTER_NAMECOLUMN + " NOT LIKE '%" + TRANSITION_SUFFIX + "'" +
")" +
" AND " + SQLITE_MASTER_TYPECOLUMN + "= '" + "table" + "'";
SQLiteDatabase mDB;
//Construct the helper NOTE will create the db if needed
public DBHelper(Context context) {
super(context, DBNAME,null, DBVERSION);
mDB = this.getWritableDatabase(); // force db open
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(create_table_sql(TABLE_MASTER));
mDB = db;
addLotsOfData(TABLE_MASTER,1000,1); // Load some data
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
// Generate the table SQL according to the table name passed
private String create_table_sql(String table) {
return "CREATE TABLE IF NOT EXISTS " + table + "(" +
COl_MASTER_ID + " INTEGER PRIMARY KEY," +
COL_MASTER_MYDATA + " TEXT" +
")";
}
// Switch the original table for a newly created version
public void switchData(String table) {
String transitional_table = "transitional";
String renamed_master = table + "_old";
boolean already_in_transaction = mDB.inTransaction();
if (!already_in_transaction) {
mDB.beginTransaction();
}
//<<<<<<<<<< create and load of new data could be done elsewhere
mDB.execSQL(create_table_sql(transitional_table));
addLotsOfData(transitional_table,1000,3001); //<<<<<<<< load new data here
//>>>>>>>>>>
mDB.execSQL("ALTER TABLE " + TABLE_MASTER + " RENAME TO " + renamed_master);
mDB.execSQL("ALTER TABLE " + transitional_table + " RENAME TO " + TABLE_MASTER);
mDB.execSQL("DROP TABLE IF EXISTS " + renamed_master);
if (!already_in_transaction) {
mDB.setTransactionSuccessful();
mDB.endTransaction();
}
}
// Add some data
public void addLotsOfData(String table, int rows, int value_offset) {
boolean already_in_transaction = mDB.inTransaction();
if (!already_in_transaction) {
mDB.beginTransaction();
}
for (int i = 0; i < rows; i++) {
addRow(table,String.valueOf(value_offset + i));
}
if (!already_in_transaction) {
mDB.setTransactionSuccessful();
mDB.endTransaction();
}
}
// Insert a single row
public long addRow(String table, String value) {
ContentValues cv = new ContentValues();
cv.put(COL_MASTER_MYDATA,value);
return mDB.insert(table,null,cv);
}
public Cursor getFirst10Rows() {
return mDB.query(TABLE_MASTER,null,null,null,null,null,null,"10");
}
/**
* Create transition copy of all App tables (not sqlite..... tables or android.... tables)
*/
public void prepareSwitchAllAppTables() {
boolean already_in_transaction = mDB.inTransaction();
if (!already_in_transaction) {
mDB.beginTransaction();
}
Cursor csr = mDB.query(SQLITE_MASTER,SQLITE_MATSER_COLUMNS,APPTABLES_WHERECLAUSE,null,null, null,null);
while (csr.moveToNext()) {
String original_tablename = csr.getString(csr.getColumnIndex(SQLITE_MASTER_NAMECOLUMN));
String original_sql = csr.getString(csr.getColumnIndex(SQLITE_MASTER_SQLCOLUMN));
String transition_tablename = original_tablename + TRANSITION_SUFFIX;
String transition_sql = original_sql.replace(original_tablename,transition_tablename).replace("CREATE TABLE","CREATE TABLE IF NOT EXISTS");
Log.d("PREAPRE4SWITCH","Executing the SQL (create transition table for table " + original_sql +
") \n\t" + transition_sql);
mDB.execSQL(transition_sql);
mDB.delete(transition_tablename,null,null); // just to make sure that all transition tables are empty
}
if (!already_in_transaction) {
mDB.setTransactionSuccessful();
mDB.endTransaction();
}
}
public void doSwitchAllAppTables() {
boolean already_in_transaction = mDB.inTransaction();
if (!already_in_transaction) {
mDB.beginTransaction();
}
Cursor csr = mDB.query(SQLITE_MASTER,SQLITE_MATSER_COLUMNS,APPTABLES_WHERECLAUSE,null,null, null,null);
ArrayList<String> tables_to_delete = new ArrayList<>();
while (csr.moveToNext()) {
String original_name = csr.getString(csr.getColumnIndex(SQLITE_MASTER_NAMECOLUMN));
String transition_name = original_name + TRANSITION_SUFFIX;
String rename_name = RENAME_SUFFIX;
tables_to_delete.add(rename_name);
Log.d("SWITCHRENAMEORIG","Executing the SQL to rename(original) " + original_name + " table to " + rename_name);
mDB.execSQL("ALTER TABLE " + original_name + " RENAME TO " + rename_name);
Log.d("SWITCHRENAMETRNS","Executing the SQL to rename(transition) from " + transition_name +
" to (original)" + original_name);
mDB.execSQL("ALTER TABLE " + transition_name + " RENAME TO " + original_name);
}
csr.close();
for (String table_to_delete: tables_to_delete) {
Log.d("SWITCHDROPRENAMED","Dropping renamed original table " + table_to_delete);
mDB.execSQL("DROP TABLE If EXISTS " + table_to_delete);
}
if (!already_in_transaction) {
mDB.setTransactionSuccessful();
mDB.endTransaction();
}
}
}
這假設您從不創建以android或sqlite開頭的App表
(sqlite_受到保護
以“sqlite_”開頭的表名保留供內部使用。 嘗試創建名稱以“sqlite_”開頭的表是錯誤的。 )。
所以SQLITE_MASTER_NAMECOLUMN + " NOT LIKE 'sqlite%' "
SQLITE_MASTER_NAMECOLUMN + " NOT LIKE 'sqlite_%' "
應該是SQLITE_MASTER_NAMECOLUMN + " NOT LIKE 'sqlite_%' "
。
sqlite_master用於驅動進程,它是模式並包含表的列表(以及其他實體,如索引,視圖和觸發器)。
請注意,如果不使用FOREIGN KEYS且未使用ON CASCADE DELETE,則無法依賴上述內容。 因為子表必須在父母之前丟棄。
在日志中運行上述結果包括: -
...........
04-26 13:41:36.000 I/System.out: mydata=3010
04-26 13:41:36.000 I/System.out: }
04-26 13:41:36.000 I/System.out: <<<<<
04-26 13:41:36.000 D/PREAPRE4SWITCH: Executing the SQL (create transition table for table CREATE TABLE "master"(_id INTEGER PRIMARY KEY,mydata TEXT))
CREATE TABLE IF NOT EXISTS "master_trn"(_id INTEGER PRIMARY KEY,mydata TEXT)
04-26 13:41:36.007 D/NEWDATALOADSTART: Loading of new data has started
04-26 13:41:43.666 D/TABLESNOTAVAILABLE: Tables will now be unavailable whil switching
04-26 13:41:43.666 D/SWITCHRENAMEORIG: Executing the SQL to rename(original) master table to rename
04-26 13:41:43.667 D/SWITCHRENAMETRNS: Executing the SQL to rename(transition) from master_trn to (original)master
04-26 13:41:43.667 D/SWITCHDROPRENAMED: Dropping renamed original table rename
04-26 13:41:43.673 D/TABLESAVAILABLE: Switch completed, Tables are now available with new data
04-26 13:41:43.673 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@9ce45fc
04-26 13:41:43.673 I/System.out: 0 {
04-26 13:41:43.673 I/System.out: _id=1
04-26 13:41:43.673 I/System.out: mydata=10000
04-26 13:41:43.673 I/System.out: }
04-26 13:41:43.673 I/System.out: 1 {
04-26 13:41:43.673 I/System.out: _id=2
04-26 13:41:43.673 I/System.out: mydata=10001
04-26 13:41:43.673 I/System.out:
..........
您可能感興趣: -
在這種情況下,您可以通過使用enableWriteAheadLogging (使用onConfigure()方法來啟用 WAL)(Android 9+,默認情況下打開它)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.