安卓數據庫爲SQLite輕量級數據庫,是遵守ACID的關係型數據庫管理系統,它包含在一個相對小的C庫中。它的設計目標是嵌入式的,佔用資源非常的低,在嵌入式設備中,可能只需要幾百K的內存就夠了。
在安卓SDK中,會有SQLiteOpenHelper類提供SQLite的基本操作。
在開發中,我們會重新定義SQLiteOpenHelper,並重寫onCreate和onUpgrade方法來處理數據庫表的創建和更新操作。
對於數據庫的更新,我們還是要在onUpgrade方法中去自己處理。而對於數據庫表添加新的字段或者修改字段類型,SQLite並沒有提供方法。那麼我們就只能在數據庫需要升級的時候,創建新表、複製舊數據到新表中來進行升級。
簡單介紹一下升級步驟:
1.將現有的所有表重命名,創建臨時表;
2.使用新的創建表SQL創建新表;
3.講臨時表的數據寫入新表,並刪除臨時表。
一、表對象繼承BaseTable.java
/**
* Created by WangFeng on 2017/4/12 0012 11:38.
*
* @desc 數據表的基類,所有表都需要繼承此抽象類
*/
public abstract class BaseTable {
/**
* 獲取表名
* @return
*/
public abstract String getTableName();
/**
* 獲取創建表語句
* @return
*/
public abstract String getCreateSQL();
/**
* 獲取刪除表語句
* @return
*/
public abstract String getDropSQL();
}
/**
* Created by WangFeng on 2017/3/29 0029.
* 圖片數據表
*/
public class ImageTable extends BaseTable{
static final String TABLE_NAME = "images";
static final String _ID = "id";
static final String IMAGE_URL = "image_url";
static final String IMAGE_NAME = "image_name";
static final String IMAGE_DESC = "image_desc";
static final String CREATE_TIME = "create_time";
static final String UPDATE_TIME = "update_time";
static final String REMOVED = "removed";
static final String SQL_CREATE = new StringBuilder()
.append("CREATE TABLE ").append(TABLE_NAME)
.append("(")
.append(_ID).append(" VARCHAR(36) PRIMARY KEY NOT NULL,")
.append(IMAGE_URL).append(" VARCHAR(100),")
.append(IMAGE_NAME).append(" VARCHAR(16),")
.append(IMAGE_DESC).append(" TEXT,")
.append(CREATE_TIME).append(" CHAR(20),")
.append(UPDATE_TIME).append(" CHAR(20),")
.append(REMOVED).append(" BOOLEAN DEFAULT FALSE")
.append(")")
.toString();
static final String SQL_DROP = new StringBuilder()
.append("DROP TABLE ").append(TABLE_NAME)
.toString();
@Override
public String getTableName() {
return TABLE_NAME;
}
@Override
public String getCreateSQL() {
return SQL_CREATE;
}
@Override
public String getDropSQL() {
return SQL_DROP;
}
}
二、導入DBUpgradeHelper.java
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Created by WangFeng on 2017/4/12 0012 11:38.
*
* @desc 數據庫表的升級
*/
public class DBUpgradeHelper {
private volatile static DBUpgradeHelper mInstance;
private DBUpgradeHelper() {
}
public static DBUpgradeHelper getInstance() {
if (mInstance == null) {
synchronized (DBUpgradeHelper.class) {
if (mInstance == null) {
mInstance = new DBUpgradeHelper();
}
}
}
return mInstance;
}
/**
* 升級數據庫表
*
* @param db 數據庫
* @param tables 要升級的表
*/
public void upgrade(SQLiteDatabase db, BaseTable... tables) {
createTempTables(db, tables); // 創建臨時表
dropTables(db, tables); // 刪除舊錶
createNewTables(db, tables); // 創建新表
restoreData(db, tables); // 臨時表數據寫入新表,刪除臨時表
}
/**
* 創建臨時表,存儲舊的表數據
*
* @param db
* @param tables
*/
private void createTempTables(SQLiteDatabase db, BaseTable... tables) {
for (int i = 0; i < tables.length; i++) {
String tableName = tables[i].getTableName();
if (!checkTable(db, tableName))
continue;
String tempTableName = tableName.concat("_TEMP");
String SQL = new StringBuilder()
.append("ALTER TABLE ")
.append(tableName)
.append(" RENAME TO ")
.append(tempTableName)
.toString();
db.execSQL(SQL);
}
}
/**
* 檢查表是否存在
*
* @param db
* @param tableName
* @return
*/
private Boolean checkTable(SQLiteDatabase db, String tableName) {
String SQL = new StringBuilder()
.append("SELECT count(*) FROM sqlite_master WHERE type='table' AND name='")
.append(tableName)
.append("'")
.toString();
Cursor c = db.rawQuery(SQL, null);
if (c.moveToNext()) {
int count = c.getInt(0);
if (count > 0) {
return true;
}
return false;
}
return false;
}
/**
* 刪除舊錶
*
* @param db
* @param tables
*/
private void dropTables(SQLiteDatabase db, @NonNull BaseTable... tables) {
for (int i = 0; i < tables.length; i++) {
if(!checkTable(db, tables[i].getTableName()))
continue;
try {
db.execSQL(tables[i].getDropSQL());
} catch (SQLiteException e) {
e.printStackTrace();
}
}
}
/**
* 創建新的表結構
*
* @param db
* @param tables
*/
private void createNewTables(SQLiteDatabase db, @NonNull BaseTable... tables) {
for (int i = 0; i < tables.length; i++) {
if(checkTable(db, tables[i].getTableName()))
continue;
try {
db.execSQL(tables[i].getCreateSQL());
} catch (SQLiteException e) {
e.printStackTrace();
}
}
}
/**
* 將臨時表的數據寫入新表
*
* @param db
* @param tables
*/
private void restoreData(SQLiteDatabase db, BaseTable... tables) {
for (int i = 0; i < tables.length; i++) {
String tableName = tables[i].getTableName();
String tempTableName = tableName.concat("_TEMP");
if (!checkTable(db, tempTableName))
continue;
List<String> columns_temp = getColumns(db, tempTableName);
List<String> columns_new = getColumns(db, tempTableName);
ArrayList<String> properties = new ArrayList<>(columns_temp.size());
for (int j = 0; j < columns_new.size(); j++) {
String columnName = columns_new.get(j);
if (columns_temp.contains(columnName)) {
properties.add(columnName);
}
}
// 從臨時表複製表數據到相同的字段
if (properties.size() > 0) {
String columnSQL = TextUtils.join(",", properties);
String SQL = new StringBuilder()
.append("INSERT INTO ").append(tableName)
.append(" (").append(columnSQL).append(") SELECT ").append(columnSQL)
.append(" FROM ").append(tempTableName)
.toString();
try {
db.execSQL(SQL);
} catch (SQLiteException e) {
e.printStackTrace();
}
}
// 刪除臨時表
String SQL_DROP = new StringBuilder()
.append("DROP TABLE ").append(tempTableName)
.toString();
try {
db.execSQL(SQL_DROP);
} catch (SQLiteException e) {
e.printStackTrace();
}
}
}
/**
* 獲取表字段列表
*
* @param db
* @param tableName
* @return
*/
private List<String> getColumns(SQLiteDatabase db, String tableName) {
List<String> columns = null;
Cursor cursor = null;
try {
cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 0", null);
if (null != cursor && cursor.getColumnCount() > 0) {
columns = Arrays.asList(cursor.getColumnNames());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
if (null == columns)
columns = new ArrayList<>();
}
return columns;
}
}
三、構建SQLiteOpenHelper,進行創建和升級數據庫
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* Created by WangFeng on 2017/3/27 0027.
* <p>
* 本地數據庫
*/
public class OfflineDB {
public static final String DB_NAME = "myDB";
public static final int DB_VER = 4; // 數據庫版本號,每次更改表結構或添加新表時,需要將版本號++
private SQLiteDatabase mDB;
private MyDBHelper mHelper;
public OfflineDB(Context context) {
mHelper = new MyDBHelper(context);
mDB = mHelper.getReadableDatabase();
mDB = mHelper.getWritableDatabase();
}
public SQLiteDatabase getDB() {
return mDB;
}
class MyDBHelper extends SQLiteOpenHelper {
public MyDBHelper(Context context) {
super(context, DB_NAME, null, DB_VER);
}
@Override
public void onCreate(SQLiteDatabase db) {
// 創建新數據庫時調用,進行數據表創建
db.execSQL(ImageTable.SQL_CREATE);
db.execSQL(VideoTable.SQL_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 數據庫版本改變時調用,執行數據庫表升級,將需要升級的表傳入upgrade方法
DBUpgradeHelper.getInstance().upgrade(db, new ImageTable(), new VideoTable());
}
}
}
簡單的數據庫升級方法就介紹完了。
本人經驗尚淺,若有不妥之處,還望大神多多指教,謝謝!