打造android ORM架構opendroid(七)——資料庫升級方案

來源:互聯網
上載者:User

標籤:orm   架構   opendroid   

在上一篇部落格《打造android ORM架構opendroid(六)——級聯查詢》我們講了OpenDroid最後一塊功能查詢的實現原理。今天我們將進行OpenDroid一個重頭戲,也是本系列部落格的最後一篇——資料庫升級方案。
說道資料庫升級,我可是很頭疼的, 為什麼呢? 因為以前的項目中,根本沒有考慮資料庫升級方案的問題,就直接drop table了,這樣導致的結果就是“以前的資料都消失了”。額。。。 憑空消失確實不是很少的一件事,如果資料不重要還行,重要資料呢? 說消失就消失了? 使用者升級了一下軟體,結果資料全沒了。。。 那是多吊絲的一件事。
OpenDroid則提供了一種資料庫升級的方案,當然這種方案肯定不是完美的。 肯定還有更好的方案,如果你發現你有好的解決方案,請不吝賜教。
好,下面開始進入正題。首先說說我的方案的原理吧:其實很簡單,就是在drop table之前將資料查詢出來,並儲存到集合中,在建立新表後,嘗試去insert資料。原理的思路很簡單,以至於我一直認為這種方案太爛了, 可我沒有想到更好的結果方案,也就只能先這樣了。
大家都知道,android的SQLiteOpenHelper類中提供了一個抽象方法onUpgrade()來讓使用者靈活的定製資料庫升級方案, 最簡單的方法就是我之前提到直接drop table。既然upgrade的權利掌握在我們手中,那我們何不借onUpgrade()大幹一番呢?

先來看看代碼:

@Override  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {      System.out.println("upgrade database");      upgrade(db);  }

在onUpgrade裡除了一句列印,其實真正有用了就一句代碼,當然也是調用了我們自訂的一個方法,那麼我們就從upgrade()這個方法開始說起:

/**  * 升級資料庫  * @param db 資料庫連結  */  private <T extends OpenDroid> void upgrade(SQLiteDatabase db) {      try {          XmlPullParser pullParser = Xml.newPullParser();          InputStream inputStream = DroidApplication.sContext.getAssets().open("open_droid.xml");          pullParser.setInput(inputStream, "utf-8");                        int type = pullParser.getEventType();          while(type != XmlPullParser.END_DOCUMENT) {              if(type == XmlPullParser.START_TAG) {                  // 擷取mapping                  if(pullParser.getName().equals(OpenDroidHelper.TAG_MAPPING)) {                      dumpData(db, pullParser);                  }              }              type = pullParser.next();          }      } catch (Exception e) {          e.printStackTrace();      }                // 執行建立資料庫      onCreate(db);  }

7~9行可以看出我們準備去解析open_droid.xml檔案了,和我們平時解析一樣,使用一個while迴圈,觀察while迴圈,我們在15~17行擷取到了有用的資訊,如果當前的tag是mapping的或,我們又去調用了dumpData,這裡面XmlPullParser會作為第二個參數傳遞過去。
方法的最後,我們直接調用了重載的onCreate方法去建立新表,當然還有資料的恢複。這個我們稍後分析,接下來我們來看看dumpData方法。

/**  * 將資料庫中的資料轉儲到程式中  * @param db 資料庫連接  * @param pullParser   * @throws Exception  */  private <T extends OpenDroid> void dumpData(SQLiteDatabase db, XmlPullParser pullParser)          throws Exception {      Class<OpenDroid> klass = (Class<OpenDroid>) Class.forName(pullParser.getAttributeValue(null, "class"));      String tableName = klass.getSimpleName(); // 表名      Cursor cursor = db.rawQuery("select * from " + tableName, null);                T t;      Method m;      String methodName;      String columnName;                // 遍曆遊標      for(cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()) {          t = (T) klass.newInstance();  // 通過反射進行執行個體化          final int columnCount = cursor.getColumnCount();          for(int i=0;i<columnCount;i++) {              columnName = cursor.getColumnName(i); // 擷取欄位名              // try一下,如果沒有該欄位對應的方法,則消化異常,並繼續              try {                  switch (cursor.getType(i)) {                  case Cursor.FIELD_TYPE_INTEGER:                      methodName = columnName.equals("_id") ? "setId" :                           CRUD.getMethodName(cursor.getColumnName(i));                      m = klass.getMethod(methodName, int.class); // 反射出方法                      m.invoke(t, cursor.getInt(i)); // 執行方法                      break;                  case Cursor.FIELD_TYPE_FLOAT:                      methodName = CRUD.getMethodName(cursor.getColumnName(i));                      m = klass.getMethod(methodName, float.class);                      m.invoke(t, cursor.getFloat(i));                      break;                  default:                      methodName = CRUD.getMethodName(cursor.getColumnName(i));                      m = klass.getMethod(methodName, String.class);                      m.invoke(t, cursor.getString(i));                      break;                  }              } catch (Exception e) {                  e.printStackTrace();              }          }          mOldData.add(t);      }      cursor.close();      db.execSQL("drop table if exists " + tableName); // 刪除舊的資料庫  }

這個方法很長,而且也很關鍵,我們的資料庫升級方案將在這裡終結。
第9行,我們通過Class.forName擷取了一個Class, 是根據什麼映射呢?來看一下我們open_droid.xml檔案就一目瞭然。

<open-droid>      <version value="6" />      <name value="school" />      <mapping class="org.loader.opendroid.Student" />      <mapping class="org.loader.opendroid.Grade" />  </open-droid>

這這個xml中,我們就是要通過org.loader.opendroid.Student來映射出一個類。
第10行,我們擷取了該類的類名,當然也就是我們要操作的表名了,唉? 為什麼就一個表呢? 仔細看看這個方法是在哪調用的,我們是在一個迴圈中調用了,也就是在迴圈中去遍曆xml節點,每次擷取到mapping節點,都來調用一下這個方法。
11行,我們執行一段select語句,將現在表中所有的資料查詢出來,那查詢出來的資料我們怎麼處理呢?
要回答這個問題,我們就得去下面的for迴圈中找答案。
在for迴圈中,20行,通過反射執行個體化了上面那個類,為什麼要在迴圈中執行個體化呢?因為每行資料我們都需要用一個對象來儲存。
21行,擷取了當前行所有列的個數。
接下來有一個for迴圈,這個迴圈我們是迴圈的每一行的列,在迴圈中去取每一列的資料。
26行,進入一個switch語句,依照慣例,我們只去分析一個case語句。
在第一個case中,如果改列的欄位是一個integer類型,28行,我們和之前講過的一樣去拼裝一個setter,當然如果是_id的話,我們就直接定義為setId了。
30行,反射出這個方法,等待下面去執行。
當然31行我們就要去執行這個方法了,我們都知道setter方法是需要參數的,參數當然就是我們查詢出來的當前列的資料了。
48行,我們將這個對象的實力放入一個集合中。
當查詢完當前表,這個表就沒用了,因為我們已經把資料都儲存起來了,所以在51行,將該表刪除。


至此,我們就把資料從舊版本的資料庫中全部查詢出來了。接下來我們回到onCreate方法中來看看新表是如果建立的,並且資料是如何恢複的。


@Override  public void onCreate(SQLiteDatabase db) {      for(String sql : OpenDroidHelper.getDBInfoBean().getSqls()) {          db.execSQL(sql);      }                // 還原資料      if(!mOldData.isEmpty()) {          for(OpenDroid item : mOldData) {              item.save(db);          }      }  }

前面幾行代碼,我們在《打造android ORM架構opendroid(二)——自動建立資料庫》 已經講解過,這裡就不重複了,我們重點來看看在那篇部落格中省略的幾行代碼,也正是這幾行代碼,實現了舊資料向新表中的轉移。
8行,先去判斷mOldData是否為空白的集合,因為onCreate方法並不是只有在資料庫升級的時候才去執行。
接下來遍曆整個集合,並且調用每個item的save方法將資料儲存到新表中,當然這裡我們重用了OpenDroid類中的save方法,因為都是insert嘛。從這裡我們也可以看出這個mOldData集合的泛型肯定是OpenDroid。

private ArrayList<OpenDroid> mOldData = new ArrayList<OpenDroid>();

好了,至此,我們opendroid提供的一個簡單的資料庫升級方案就執行完了,而且我們的opendroid也介紹的差不多了,剩下的一點東西都是輔助性的東西。哦,對了,這裡還要提一點:細心的朋友可能已經發現了,opendroid在操作完資料庫並沒有預設的關閉掉資料庫,而是蛋疼的提供了open和release兩個方法,不信可以看代碼:

/**  * 開啟資料庫  */  public static void open() {      if(sSqliteDatabase == null) {          sSqliteDatabase = sOpenHelper.getWritableDatabase();      }  }        /**  * 釋放資料庫  */  public static void release() {      if(sSqliteDatabase != null && sSqliteDatabase.isOpen()) {          sSqliteDatabase.close();      }      sSqliteDatabase = null;  }

這是為什麼呢? 其實剛開始我是做了預設關閉了,可就是在寫到資料庫升級恢複資料的時候,因為save是在一個迴圈中執行了,因此,可能在很短的時間內多次開啟/關閉資料庫,這樣做會消耗很大的效能,所以android會拋出一個異常,在一氣之下,我就將代碼改造成了這種方式,如果有大神有更好的解決方案,請賜教哈。


好了,至此我們opendroid系列部落格也就尾聲了,當然,做出一個orm架構本身並不重要,重要的是學會如何去做一個orm架構,別人能做的事,我們為什麼就不能呢?對吧,作為一個程式員,我們要努力去做一個“創造者”,而不是簡單停留在一個“使用者”上。

最後是opendroid的開源地址:http://git.oschina.net/qibin/OpenDroid

打造android ORM架構opendroid(七)——資料庫升級方案

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.