編輯:關於Android編程
Android開發涉及到的數據庫采用的是輕量級的SQLite3,而在實際開發中,在存儲一些簡單的數據,使用SharedPreferences就足夠了,只有在存儲數據結構稍微復雜的時候,才會使用數據庫來存儲。而數據庫表的設計往往不是一開始就非常完美,可能在應用版本開發迭代中,表的結構也需要調整,這時候就涉及到數據庫升級的問題了。
數據庫升級,主要有以下這幾種情況:
增加表 刪除表 修改表增加表和刪除表問題不大,因為它們都沒有涉及到數據的遷移問題,增加表只是在原來的基礎上CRTATE TABLE,而刪除表就是對歷史數據不需要了,那只要DROP TABLE即可。那麼修改表呢?
其實,很多時候,程序員為了圖個方便,最簡單最暴力的方法就是,將原來的表刪除了然後重新創建新的表,這樣就不用考慮其他因素了。但這樣對於用戶來說,體驗是非常不好的,比如:用戶當前下載列表正在下載文件,此時進行更新,而新版本有個更新點是升級了下載列表的數據庫表,那麼用戶更新完之後發現下載列表變空了,那麼用戶看到辛辛苦苦下載的99%文件.avi沒來,那不崩潰了,這種體驗是非常不好的,分分鐘就卸載你的應用。
那麼數據庫表升級時,數據遷移就顯得非常重要了,那麼如何實現呢?
現在開發,為了效率,都會使用第三方,本文數據庫方面是基於ORMLite的,所以接下來討論的都是基於此。
上表的意思是:版本升級從版本號1升級到2再升級到3,1->2->3,期間表ABC的變化,‘+’表示該表增加了字段,‘-’表示該表刪除了字段,例如1升級到2,表A增加了字段,表B刪除了字段,表C沒有發生變化。
首先,我們要先理解SQLiteOpenHelper中
/**
* Called when the database is created for the first time. This is where the
* creation of tables and the initial population of the tables should happen.
*
* @param db The database.
*/
public abstract void onCreate(SQLiteDatabase db);
和
/**
* Called when the database needs to be upgraded. The implementation
* should use this method to drop tables, add tables, or do anything else it
* needs to upgrade to the new schema version.
* @param db The database.
* @param oldVersion The old database version.
* @param newVersion The new database version.
*/
public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
什麼時候調用。文檔說得很清楚了,onCreate()是數據庫第一次創建的時候調用,而onUpgrade()是當數據庫版本升級的時候調用。
首先,先簡單的創建A、B、C三個類,並使用OrmLite注解來創建表
A.class
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
@DatabaseTable(tableName = tb_a)
public class A {
@DatabaseField(generatedId = true)
public int id;
@DatabaseField
public String name;
}
B.class
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
@DatabaseTable(tableName = tb_b)
public class B {
@DatabaseField(generatedId = true)
public int id;
@DatabaseField
public String name;
@DatabaseField
public String age;
}
C.class
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
@DatabaseTable(tableName = tb_c)
public class C {
@DatabaseField(generatedId = true)
public int id;
@DatabaseField
public String name;
}
創建自己的Helper的MySqliteHelper.class
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import java.sql.SQLException;
public class MySqliteHelper extends OrmLiteSqliteOpenHelper{
private final static String DATABASE_NAME=test.db;
private final static int DATABASE_VERSION = 1;
private static MySqliteHelper mInstance;
public MySqliteHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
public static MySqliteHelper getInstance(Context context) {
if (mInstance == null) {
mInstance= new MySqliteHelper(context);
}
return mInstance;
}
@Override
public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
try {
TableUtils.createTableIfNotExists(connectionSource,A.class);
TableUtils.createTableIfNotExists(connectionSource,B.class);
TableUtils.createTableIfNotExists(connectionSource,C.class);
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {
}
}
創建數據操作的Dao
import android.content.Context;
import com.j256.ormlite.dao.Dao;
import java.sql.SQLException;
public class ADao {
private Dao dao;
public ADao(Context context){
try {
dao = MySqliteHelper.getInstance(context).getDao(A.class);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
BDao、CDao,也類似。
運行程序,進行Dao操作,此時就創建數據庫test.db,進而執行onCreate()創建表。
import android.app.Application;
import android.test.ApplicationTestCase;
import android.test.suitebuilder.annotation.MediumTest;
import com.helen.andbase.demolist.db.A;
import com.helen.andbase.demolist.db.ADao;
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
@MediumTest
public void testDao(){
ADao aDao = new ADao(getContext());
A a = new A();
a.name=a;
aDao.add(a);
BDao bDao = new BDao(getContext());
B b = new B();
b.name=a;
b.age =18;
bDao.add(b);
}
}
將其拷出來,查看數據庫。這裡使用SQLiteExpertPers進行查看<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="這裡寫圖片描述" src="/uploadfile/Collfiles/20150730/20150730085207122.jpg" title="\" />
如上圖表已創建。接著我們進行數據庫升級,將版本號DATABASE_VERSION變為2,表A新增字段age,表B刪除字段age,C不變
@DatabaseTable(tableName = tb_a)
public class A {
@DatabaseField(generatedId = true)
public int id;
@DatabaseField
public String name;
@DatabaseField
public String age;
}
@DatabaseTable(tableName = tb_b)
public class B {
@DatabaseField(generatedId = true)
public int id;
@DatabaseField
public String name;
}
@DatabaseTable(tableName = tb_c)
public class C {
@DatabaseField(generatedId = true)
public int id;
@DatabaseField
public String name;
}
簡單暴力的解決方法是:
@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
if(oldVersion < 2){//暫不說明為何要這麼判斷
try {
TableUtils.dropTable(connectionSource,A.class,true);
TableUtils.dropTable(connectionSource,B.class,true);
} catch (SQLException e) {
e.printStackTrace();
}
}
onCreate(db,connectionSource);
}
先將舊的表刪除再創建新的表,這是最簡單暴力的,但前面提過這不是我們想要的結果。
將代碼改下,
@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
if(oldVersion < 2){//暫不說明為何要這麼判斷
DatabaseUtil.upgradeTable(db,connectionSource,A.class,DatabaseUtil.OPERATION_TYPE.ADD);
}
onCreate(db,connectionSource);
}
主要的代碼就是封裝的DatabaseUtil.class這個類
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import com.j256.ormlite.misc.JavaxPersistence;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.DatabaseTable;
import com.j256.ormlite.table.TableUtils;
import java.util.Arrays;
public class DatabaseUtil {
public static final String TAG = DatabaseUtil.java;
/**數據庫表操作類型*/
public enum OPERATION_TYPE{
/**表新增字段*/
ADD,
/**表刪除字段*/
DELETE
}
/**
* 升級表,增加字段
* @param db
* @param clazz
*/
public static void upgradeTable(SQLiteDatabase db,ConnectionSource cs,Class clazz,OPERATION_TYPE type){
String tableName = extractTableName(clazz);
db.beginTransaction();
try {
//Rename table
String tempTableName = tableName + _temp;
String sql = ALTER TABLE +tableName+ RENAME TO +tempTableName;
db.execSQL(sql);
//Create table
try {
sql = TableUtils.getCreateTableStatements(cs, clazz).get(0);
db.execSQL(sql);
} catch (Exception e) {
e.printStackTrace();
TableUtils.createTable(cs, clazz);
}
//Load data
String columns;
if(type == OPERATION_TYPE.ADD){
columns = Arrays.toString(getColumnNames(db,tempTableName)).replace([,).replace(],);
}else if(type == OPERATION_TYPE.DELETE){
columns = Arrays.toString(getColumnNames(db,tableName)).replace([,).replace(], );
}else {
throw new IllegalArgumentException(OPERATION_TYPE error);
}
sql = INSERT INTO +tableName +
(+ columns+) +
SELECT + columns+ FROM +tempTableName;
db.execSQL(sql);
//Drop temp table
sql = DROP TABLE IF EXISTS +tempTableName;
db.execSQL(sql);
db.setTransactionSuccessful();
}catch (Exception e){
e.printStackTrace();
}finally {
db.endTransaction();
}
}
/**
* 獲取表名(ormlite DatabaseTableConfig.java)
* @param clazz
* @param
* @return
*/
private static String extractTableName(Class clazz) {
DatabaseTable databaseTable = clazz.getAnnotation(DatabaseTable.class);
String name ;
if (databaseTable != null && databaseTable.tableName() != null && databaseTable.tableName().length() > 0) {
name = databaseTable.tableName();
} else {
/*
* NOTE: to remove javax.persistence usage, comment the following line out
*/
name = JavaxPersistence.getEntityName(clazz);
if (name == null) {
// if the name isn't specified, it is the class name lowercased
name = clazz.getSimpleName().toLowerCase();
}
}
return name;
}
/**
* 獲取表的列名
* @param db
* @param tableName
* @return
*/
private static String[] getColumnNames(SQLiteDatabase db,String tableName){
String[] columnNames = null;
Cursor cursor = null;
try {
cursor = db.rawQuery(PRAGMA table_info(+tableName+),null);
if(cursor != null){
int columnIndex = cursor.getColumnIndex(name);
if(columnIndex == -1){
return null;
}
int index = 0;
columnNames = new String[cursor.getCount()];
for(cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()){
columnNames[index] = cursor.getString(columnIndex);
index++;
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
if(cursor != null) {
cursor.close();
}
}
return columnNames;
}
}
upgradeTable方法裡采用的是數據庫事務,利用事務的原子特性,保證所有的SQL能全部執行完成。主要思路是:首先將原來的表進行改名稱rename table(臨時表),接著創建新的表create table,再者將舊表內的數據遷移到新表內,最後drop table刪除臨時表。
表的增加字段和刪除字段,在遷移數據的時候,主要區別在於字段的來源不同。比如:A表新增了age字段,這時候columns變量的獲取是根據舊表來的,這是構造的sql語句是
sql = INSERT INTO tb_a (id,name) SELECT id,name FROM tb_a_temp;
而B表是刪除age字段的,columns變量的獲取是根據新表來的,其構造的sql語句是
sql = INSERT INTO tb_b (id) SELECT id FROM tb_b_temp;
再次執行ApplicationTest->testDao
@MediumTest
public void testDao(){
ADao aDao = new ADao(getContext());
A a = new A();
a.name=a;
a.age = 20;
aDao.add(a);
BDao bDao = new BDao(getContext());
B b = new B();
b.name=b;
bDao.add(b);
}
再查看下數據
可以看到表A、表B的歷史數據還是存在的。
然後我們再將數據庫升級到版本號為3,這時候要考慮到用戶的多種情況,從1->3,從2->3這兩種情況,但不是每次升級都重復之前的操作的,比如用戶1之前已經從1升級到2了,這次是要從2升級到3,而用戶2,一直用的是老版本1,他覺得,嗯,這次這個版本升級的內容不錯,決定升級了,那麼他是從1直接升級到3的,所以他們兩者經歷的版本不一樣,數據庫升級的策略也會有所不用,那就要區分開來考慮了。
這次的升級是將表C添加了sex字段
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
@DatabaseTable(tableName = tb_c)
public class C {
@DatabaseField(generatedId = true)
public int id;
@DatabaseField
public String name;
@DatabaseField
public String sex;
}
然後在onUpgrade進行邏輯判斷
@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
if(oldVersion < 2){
DatabaseUtil.upgradeTable(db,connectionSource,A.class,DatabaseUtil.OPERATION_TYPE.ADD);
DatabaseUtil.upgradeTable(db,connectionSource,B.class,DatabaseUtil.OPERATION_TYPE.DELETE);
}
if(oldVersion < 3){
DatabaseUtil.upgradeTable(db,connectionSource,C.class,DatabaseUtil.OPERATION_TYPE.ADD);
}
onCreate(db,connectionSource);
}
這樣,如果你是從1升級到3,那麼兩個if語句都會執行,而如果是從2升級到3,那麼只有if(oldVersion < 3)這個分支會執行。最後,如果只是新增全新的表D,那麼只要在onCreate內多寫句TableUtils.createTableIfNotExists(connectionSource, D.class);就可以啦,不要忘記版本號要+1~
本文討論的數據遷移,是基於新舊兩個表之間邏輯性不強,不牽涉到業務情景的情況下。比如,表A新增的字段user_id為用戶id,這個字段是用來標記數據來源於哪個用戶的,檢索的時候,user_id是用於檢索條件的,那麼由於舊數據轉移到新表中user_id默認是空的,這時候舊數據可能相當於不起作用了,雖然可以通過設置默認值,但其需要根據具體業務場景進行設置,因此就失去其靈活性了。
在Android開發中,我們經常會用到對商家或者商品的評價,運用星星進行打分。然而在Android系統中自帶的打分控件,RatingBar特別不好用,間距和大小無法改變。
SQLite是Android平台軟件開發中會經常用到的數據庫產品,作為一款輕型數據庫,SQLite的設計目標就是是嵌入式的,而且目前已經在很多嵌入式產品中使用了它,它占用
其實可以理解Handler為主線程和另外的線程之間進行數據更新的東東,並且Handler在主線程中,並在Handler直接調用線程的run方法package com.Ha
1. 功能介紹1.1 Android Universal Image LoaderAndroid Universal Image Loader 是一個強大的、可高度定制的