GreenDao設(shè)置數(shù)據(jù)庫路徑以及數(shù)據(jù)庫升級

1. APP開發(fā)期間的數(shù)據(jù)庫

上一篇文章中封裝了GreenDaoManager中有個(gè)構(gòu)造方法如下:

private GreenDaoManager(){
        if (mInstance == null) {
//            DaoMaster.DevOpenHelper devOpenHelper = new
//                    DaoMaster.DevOpenHelper(MyApplication.getContext(), "database_name", null);//此處openhelper為自動生成開發(fā)所使用窟感,發(fā)布版本需自定義
            MySQLiteOpenHelper devOpenHelper = new
                    MySQLiteOpenHelper(new GreenDaoContext(), "database_name.db", null);//GreenDaoContext為創(chuàng)建數(shù)據(jù)庫路徑使用
            mDaoMaster = new DaoMaster(devOpenHelper.getWritableDatabase());
            mDaoSession = mDaoMaster.newSession();
        }
    }

其中注釋掉的兩行代碼中:

DaoMaster.DevOpenHelper devOpenHelper = new
DaoMaster.DevOpenHelper(MyApplication.getContext(), "database_name", null);//此處DevOpenHelper 為自動生成開發(fā)所使用,發(fā)布版本需自定義

其中的MyApplication.getContext()上下文表示了數(shù)據(jù)庫存儲路徑為手機(jī)內(nèi)存较锡。這里的DevOpenHelper即為DaoMaster中自動生成代碼打肝,完整代碼如下:

/** * WARNING: Drops all table on Upgrade! Use only during development. */
public static class DevOpenHelper extends OpenHelper {    
  public DevOpenHelper(Context context, String name, CursorFactory factory) {
             super(context, name, factory);    
  }    
  @Override    
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {            
        Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");        
        dropAllTables(db, true);        
        onCreate(db);    
  }
}

注意看第一行注釋:WARNING: Drops all table on Upgrade! Use only during development.
數(shù)據(jù)庫升級的話弄痹,會刪除所有表,然后重新創(chuàng)建园细。這種方式在開發(fā)期間耗式,APP還沒有上線之前是可以的。

當(dāng)APP上線后五芝,我們不能使用這種方式痘儡,因?yàn)檫@樣會導(dǎo)致已經(jīng)存在的數(shù)據(jù)會被刪除。

2. APP上線后,數(shù)據(jù)庫升級

我們需要重寫一個(gè)類MySQLiteOpenHelper實(shí)現(xiàn)OpenHelper.

  1. 需要自己實(shí)現(xiàn)了onUpgrade方法來自定義升級過程与柑。
  2. 當(dāng)然升級過程中也要修改DaoMaster.SCHEMA_VERSION
  3. 當(dāng)DaoMaster.SCHEMA_VERSION跟你當(dāng)前數(shù)據(jù)庫的版本比較后,會根據(jù)你當(dāng)前數(shù)據(jù)庫的版本谤辜,然后進(jìn)行升級。
  4. 關(guān)鍵代碼onUpgrade方法,會比較新數(shù)據(jù)庫和舊數(shù)據(jù)庫的版本,然后執(zhí)行相應(yīng)的sql升級:
public class MySQLiteOpenHelper extends DaoMaster.OpenHelper  {
    public MySQLiteOpenHelper(Context context, String name) {
        super(context, name);
    }

    public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
        super(context, name, factory);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //數(shù)據(jù)遷移模塊
        MigrationHelper.migrate(db,
                UserDao.class,
                ProfessionDao.class,
                LTestDao.class);
    }
}

數(shù)據(jù)庫升級不外乎兩種情況:
  1.修改表結(jié)構(gòu)价捧。
  2.添加新表。

針對onUpgrade中的實(shí)現(xiàn)方法可以采用兩種不同策略:(稍后都會提供兩種策略的代碼實(shí)現(xiàn)涡戳,下面主要講述第二種數(shù)據(jù)遷移實(shí)現(xiàn)方式)
 1.逐級版本迭代升級结蟋,比如當(dāng)前版本為1,最新版本為3渔彰,此方案就是先從1-->2-->3.
 2.把數(shù)據(jù)庫中的數(shù)據(jù)備份嵌屎,然后全刪數(shù)據(jù)庫推正,再重新創(chuàng)建新的數(shù)據(jù)庫,把備份數(shù)據(jù)導(dǎo)入宝惰。此方案直接從1-->3植榕。

3. 數(shù)據(jù)遷移(數(shù)據(jù)庫備份)的具體實(shí)現(xiàn)

  1. android上面對的數(shù)據(jù)庫基本上就是輕量級數(shù)據(jù)庫sqlite,首先我們需要了解一些后面需要用到的數(shù)據(jù)庫操作語句:
  • 判斷sqlite表是否存在
    SELECT * FROM sqlite_master WHERE type='table' AND name='tempTableName';
  • sqlite判斷臨時(shí)表是否存在
    SELECT * FROM sqlite_temp_master WHERE type='table' AND name='tempTableName';
  • sqlite創(chuàng)建表tableName的臨時(shí)表
    create temporary table tempTableName as select * from tableName;
  • 刪除臨時(shí)表
    drop table tempTableName;
  1. sqlite的系統(tǒng)表sqlite_master、臨時(shí)表sqlite_temp_master
    SQLite數(shù)據(jù)庫中一個(gè)特殊的名叫 SQLITE_MASTER上執(zhí)行一個(gè)SELECT查詢以獲得所有表的索引尼夺。每一個(gè) SQLite 數(shù)據(jù)庫都有一個(gè)叫 SQLITE_MASTER的表尊残, 它定義數(shù)據(jù)庫的模式。 SQLITE_MASTER表看起來如下:
CREATE TABLE sqlite_master ( 
type TEXT, 
name TEXT, 
tbl_name TEXT, 
rootpage INTEGER, 
sql TEXT 
); 

type 字段永遠(yuǎn)是 ‘table’淤堵,name 字段永遠(yuǎn)是對應(yīng)表的名字寝衫。所以,要獲得數(shù)據(jù)庫中所有表的列表拐邪, 使用下列SELECT語句:

SELECT name FROM sqlite_master 
WHERE type=’table’ 
ORDER BY name; 

對于索引慰毅,type 等于 ‘index’, name 則是索引的名字,tbl_name 是該索引所屬的表的名字扎阶。 不管是表還是索引汹胃,sql 字段是原先用 CREATE TABLE 或 CREATE INDEX 語句創(chuàng)建它們時(shí)的命令文本。對于自動創(chuàng)建的索引(用來實(shí)現(xiàn) PRIMARY KEY 或 UNIQUE 約束)东臀,sql字段為NULL着饥。
SQLITE_MASTER 表是只讀的。不能對它使用 UPDATE啡邑、INSERT 或 DELETE贱勃。 它會被 CREATE TABLE、CREATE INDEX谤逼、DROP TABLE 和 DROP INDEX 命令自動更新贵扰。
臨時(shí)表及其索引和觸發(fā)器存放在另外一個(gè)叫 SQLITE_TEMP_MASTER 的表中。SQLITE_TEMP_MASTER 跟 SQLITE_MASTER 差不多流部, 但它只是對于創(chuàng)建那些臨時(shí)表的應(yīng)用可見戚绕。如果要獲得所有表的列表, 不管是永久的還是臨時(shí)的枝冀,可以使用類似下面的命令:

SELECT name FROM 
(SELECT * FROM sqlite_master UNION ALL 
SELECT * FROM sqlite_temp_master) 
WHERE type=’table’ 
ORDER BY name
  1. 數(shù)據(jù)遷移的工具類MigrationHelper舞丛。
    public final class MigrationHelper {
    public static boolean DEBUG = true;
    private static String TAG = "MigrationHelper";
    private static final String SQLITE_MASTER = "sqlite_master";
    private static final String SQLITE_TEMP_MASTER = "sqlite_temp_master";
    public static void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
    Database database = new StandardDatabase(db);

       printLog("【The Old Database Version】" + db.getVersion());
       printLog("【Generate temp table】start");
       generateTempTables(database, daoClasses);
       printLog("【Generate temp table】complete");
    
       dropAllTables(database, true, daoClasses);
       createAllTables(database, false, daoClasses);
    
       printLog("【Restore data】start");
       restoreData(database, daoClasses);
       printLog("【Restore data】complete");
     }
     private static void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses)   {
       for (int i = 0; i < daoClasses.length; i++) {
         String tempTableName = null;
    
         DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
         String tableName = daoConfig.tablename;
         if (!isTableExists(db, false, tableName)) {//不存在系統(tǒng)表中,表明是新增表果漾,不需要?jiǎng)?chuàng)建臨時(shí)表
             printLog("【New Table】" + tableName);
             continue;
         }
         try {
             tempTableName = daoConfig.tablename.concat("_TEMP");
             StringBuilder dropTableStringBuilder = new StringBuilder();
             dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName).append(";");
             db.execSQL(dropTableStringBuilder.toString());
    
             StringBuilder insertTableStringBuilder = new StringBuilder();
             insertTableStringBuilder.append("CREATE TEMPORARY TABLE ").append(tempTableName);
             insertTableStringBuilder.append(" AS SELECT * FROM ").append(tableName).append(";");
             db.execSQL(insertTableStringBuilder.toString());
             printLog("【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig));
             printLog("【Generate temp table】" + tempTableName);
         } catch (SQLException e) {
             Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e);
         }
      }
     }
    
    private static boolean isTableExists(Database db, boolean isTemp, String tableName) {
     if (db == null || TextUtils.isEmpty(tableName)) {
         return false;
     }
     String dbName = isTemp ? SQLITE_TEMP_MASTER : SQLITE_MASTER;
     String sql = "SELECT COUNT(*) FROM " + dbName + " WHERE type = ? AND name = ?";
     Cursor cursor=null;
     int count = 0;
     try {
         cursor = db.rawQuery(sql, new String[]{"table", tableName});
         if (cursor == null || !cursor.moveToFirst()) {
             return false;
         }
         count = cursor.getInt(0);
     } catch (Exception e) {
         e.printStackTrace();
     } finally {
         if (cursor != null)
             cursor.close();
     }
     return count > 0;
    }
    private static String getColumnsStr(DaoConfig daoConfig) {
     if (daoConfig == null) {
         return "no columns";
     }
     StringBuilder builder = new StringBuilder();
     for (int i = 0; i < daoConfig.allColumns.length; i++) {
         builder.append(daoConfig.allColumns[i]);
         builder.append(",");
     }
     if (builder.length() > 0) {
         builder.deleteCharAt(builder.length() - 1);
     }
     return builder.toString();
    }
     private static void dropAllTables(Database db, boolean ifExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
     reflectMethod(db, "dropTable", ifExists, daoClasses);
     printLog("【Drop all table】");
      }
      private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
     reflectMethod(db, "createTable", ifNotExists, daoClasses);
     printLog("【Create all table】");
     }
     /**
      * dao class already define the sql exec method, so just invoke it
      */
     private static void reflectMethod(Database db, String methodName, boolean isExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {
     if (daoClasses.length < 1) {
         return;
     }
     try {
         for (Class cls : daoClasses) {
             Method method = cls.getDeclaredMethod(methodName, Database.class, boolean.class);
             method.invoke(null, db, isExists);
         }
     } catch (NoSuchMethodException e) {
         e.printStackTrace();
     } catch (InvocationTargetException e) {
         e.printStackTrace();
     } catch (IllegalAccessException e) {
         e.printStackTrace();
     }
    }
    private static void restoreData(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {
     for (int i = 0; i < daoClasses.length; i++) {
         DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);
         String tableName = daoConfig.tablename;
         String tempTableName = daoConfig.tablename.concat("_TEMP");
    
         if (!isTableExists(db, true, tempTableName)) {
             continue;
         }
    
         try {
             // get all columns from tempTable, take careful to use the columns list
             List<String> columns = getColumns(db, tempTableName);
             ArrayList<String> properties = new ArrayList<>(columns.size());
             for (int j = 0; j < daoConfig.properties.length; j++) {
                 String columnName = daoConfig.properties[j].columnName;
                 if (columns.contains(columnName)) {
                     properties.add(columnName);
                 }
             }
             if (properties.size() > 0) {
                 final String columnSQL = TextUtils.join(",", properties);
    
                 StringBuilder insertTableStringBuilder = new StringBuilder();
                 insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" (");
                 insertTableStringBuilder.append(columnSQL);
                 insertTableStringBuilder.append(") SELECT ");
                 insertTableStringBuilder.append(columnSQL);
                 insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";");
                 db.execSQL(insertTableStringBuilder.toString());
                 printLog("【Restore data】 to " + tableName);
             }
             StringBuilder dropTableStringBuilder = new StringBuilder();
             dropTableStringBuilder.append("DROP TABLE ").append(tempTableName);
             db.execSQL(dropTableStringBuilder.toString());
             printLog("【Drop temp table】" + tempTableName);
         } catch (SQLException e) {
             Log.e(TAG, "【Failed to restore data from temp table 】" + tempTableName, e);
         }
     }
    }
    private static List<String> getColumns(Database 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;
    }
    private static void printLog(String info){
     if(DEBUG){
         Log.d(TAG, info);
     }
    }
    }
    

4. 設(shè)置數(shù)據(jù)庫路徑

在第一模塊中有句代碼:
MySQLiteOpenHelper devOpenHelper = new MySQLiteOpenHelper(new GreenDaoContext(), "database_name.db", null);//GreenDaoContext為創(chuàng)建數(shù)據(jù)庫路徑使用
里面有個(gè)上下文GreenDaoContext繼承了ContextWrapper,里面設(shè)置了數(shù)據(jù)庫路徑球切,代碼如下:

public class GreenDaoContext  extends ContextWrapper {

    private String currentUserId = "greendao";//一般用來針對一個(gè)用戶一個(gè)數(shù)據(jù)庫,以免數(shù)據(jù)混亂問題
    private Context mContext;

    public GreenDaoContext() {
        super(MyApplication.getContext());
        this.mContext = MyApplication.getContext();
//        this.currentUserId = "greendao";//初始化
    }

    /**
     * 獲得數(shù)據(jù)庫路徑绒障,如果不存在吨凑,則創(chuàng)建對象
     *
     * @param dbName
     */
    @Override
    public File getDatabasePath(String dbName) {
        String dbDir = CommonUtils.getDBPath();
        if (TextUtils.isEmpty(dbDir)){
            Log.e("SD卡管理:", "SD卡不存在,請加載SD卡");
            return null;
        }
        File baseFile = new File(dbDir);
        // 目錄不存在則自動創(chuàng)建目錄
        if (!baseFile.exists()){
            baseFile.mkdirs();
        }
        StringBuffer buffer = new StringBuffer();
        buffer.append(baseFile.getPath());
        buffer.append(File.separator);
        buffer.append(currentUserId);
        dbDir = buffer.toString();// 數(shù)據(jù)庫所在目錄
        buffer.append(File.separator);
//        buffer.append(dbName+"_"+currentUserId);//也可以采用此種方式,將用戶id與表名聯(lián)系到一塊命名
        buffer.append(dbName);
        String dbPath = buffer.toString();// 數(shù)據(jù)庫路徑
        // 判斷目錄是否存在鸵钝,不存在則創(chuàng)建該目錄
        File dirFile = new File(dbDir);
        if (!dirFile.exists()){
            dirFile.mkdirs();
        }
        // 數(shù)據(jù)庫文件是否創(chuàng)建成功
        boolean isFileCreateSuccess = false;
        // 判斷文件是否存在糙臼,不存在則創(chuàng)建該文件
        File dbFile = new File(dbPath);
        if (!dbFile.exists()) {
            try {
                isFileCreateSuccess = dbFile.createNewFile();// 創(chuàng)建文件
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else
            isFileCreateSuccess = true;
        // 返回?cái)?shù)據(jù)庫文件對象
        if (isFileCreateSuccess)
            return dbFile;
        else
            return super.getDatabasePath(dbName);
    }

    /**
     * 重載這個(gè)方法,是用來打開SD卡上的數(shù)據(jù)庫的恩商,android 2.3及以下會調(diào)用這個(gè)方法变逃。
     *
     * @param name
     * @param mode
     * @param factory
     */
    @Override
    public SQLiteDatabase openOrCreateDatabase(String name, int mode,
                                               SQLiteDatabase.CursorFactory factory) {
        SQLiteDatabase result = SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), factory);
        return result;
    }

    /**
     * Android 4.0會調(diào)用此方法獲取數(shù)據(jù)庫。
     *
     * @param name
     * @param mode
     * @param factory
     * @param errorHandler
     * @see android.content.ContextWrapper#openOrCreateDatabase(java.lang.String, int,
     * android.database.sqlite.SQLiteDatabase.CursorFactory,
     * android.database.DatabaseErrorHandler)
     */
    @Override
    public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory,
                                               DatabaseErrorHandler errorHandler) {
        SQLiteDatabase result = SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), factory);

        return result;
    }

}

OK怠堪,先到這了揽乱。。研叫。
源碼奉上锤窑,自行參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嚷炉,隨后出現(xiàn)的幾起案子渊啰,更是在濱河造成了極大的恐慌,老刑警劉巖申屹,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绘证,死亡現(xiàn)場離奇詭異,居然都是意外死亡哗讥,警方通過查閱死者的電腦和手機(jī)嚷那,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來杆煞,“玉大人魏宽,你說我怎么就攤上這事【龊酰” “怎么了队询?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長构诚。 經(jīng)常有香客問我蚌斩,道長,這世上最難降的妖魔是什么范嘱? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任送膳,我火速辦了婚禮,結(jié)果婚禮上丑蛤,老公的妹妹穿的比我還像新娘叠聋。我一直安慰自己,他們只是感情好受裹,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布晒奕。 她就那樣靜靜地躺著,像睡著了一般名斟。 火紅的嫁衣襯著肌膚如雪脑慧。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天砰盐,我揣著相機(jī)與錄音闷袒,去河邊找鬼。 笑死岩梳,一個(gè)胖子當(dāng)著我的面吹牛囊骤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冀值,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼也物,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了列疗?” 一聲冷哼從身側(cè)響起滑蚯,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎抵栈,沒想到半個(gè)月后告材,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡古劲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年斥赋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片产艾。...
    茶點(diǎn)故事閱讀 40,680評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡疤剑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出闷堡,到底是詐尸還是另有隱情隘膘,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布缚窿,位于F島的核電站棘幸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏倦零。R本人自食惡果不足惜误续,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扫茅。 院中可真熱鬧蹋嵌,春花似錦、人聲如沸葫隙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至腺办,卻和暖如春焰手,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背怀喉。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工书妻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人躬拢。 一個(gè)月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓躲履,卻偏偏與公主長得像,于是被迫代替她去往敵國和親聊闯。 傳聞我的和親對象是個(gè)殘疾皇子工猜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容