1. 程式人生 > >greendao3.0以上使用步驟(二):資料庫到底該怎麼升級

greendao3.0以上使用步驟(二):資料庫到底該怎麼升級

這一篇看看資料庫到底該怎麼升級呢?看我升級後的效果

  • 沒有升級前的頁面顯示
    這裡寫圖片描述

  • 沒有升級前的資料庫
    這裡寫圖片描述

  • 升級後的頁面顯示

這裡寫圖片描述

  • 升級後的資料庫

這裡寫圖片描述

看增加了一個NUM欄位 。

最新有小夥伴遇到資料庫升級問題了,說網上都是2.0版本的升級方法,自己使用的是3.0,沒法升級資料庫了….

然後問別人,別人讓他改為2.0版本,讓資料庫裡面有兩個表供應用操作,天啦這還了得,我用的也是3.0,那以後還得了,非把人累死……..

資料庫的升級大家都知道,建立臨時表,進行過渡儲存而已,網上有人提供了一個MigrationHelper類,真好正好可以使用!

but 蹦出了這樣一個大bug,請看:

這裡寫圖片描述

什麼新增欄位不能為空? 我沒有設定呀,到底為什麼呢?

我相信有的小夥伴也遇到過吧!那下面我們一起來看看怎麼回事吧

思路:

建立臨時表-->刪除原表-->建立新表-->複製臨時表資料到新表並刪除臨時表;這樣資料庫表的更新就完成了
  • 首先我們引入MigrationHelper類,內容如下:
package cn.hnshangyu.testgreendao.helper;

import android.database.Cursor;
import android.text.TextUtils;
import android.util.Log;

import
org.greenrobot.greendao.AbstractDao; import org.greenrobot.greendao.database.Database; import org.greenrobot.greendao.internal.DaoConfig; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import cn.hnshangyu.testgreendao.greendao.DaoMaster; public class MigrationHelper
{
private static final String CONVERSION_CLASS_NOT_FOUND_EXCEPTION = "MIGRATION HELPER - CLASS DOESN'T MATCH WITH THE CURRENT PARAMETERS"; private static MigrationHelper instance; public static MigrationHelper getInstance() { if (instance == null) { instance = new MigrationHelper(); } return instance; } public void migrate(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) { generateTempTables(db, daoClasses); DaoMaster.dropAllTables(db, true); DaoMaster.createAllTables(db, false); restoreData(db, daoClasses); } /** * 生成臨時列表 * * @param db * @param daoClasses */ private void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) { for (int i = 0; i < daoClasses.length; i++) { DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]); String divider = ""; String tableName = daoConfig.tablename; String tempTableName = daoConfig.tablename.concat("_TEMP"); ArrayList<String> properties = new ArrayList<>(); StringBuilder createTableStringBuilder = new StringBuilder(); createTableStringBuilder.append("CREATE TABLE ").append(tempTableName).append(" ("); for (int j = 0; j < daoConfig.properties.length; j++) { String columnName = daoConfig.properties[j].columnName; if (getColumns(db, tableName).contains(columnName)) { properties.add(columnName); String type = null; try { type = getTypeByClass(daoConfig.properties[j].type); } catch (Exception exception) { exception.printStackTrace(); } createTableStringBuilder.append(divider).append(columnName).append(" ").append(type); if (daoConfig.properties[j].primaryKey) { createTableStringBuilder.append(" PRIMARY KEY"); } divider = ","; } } createTableStringBuilder.append(");"); db.execSQL(createTableStringBuilder.toString()); StringBuilder insertTableStringBuilder = new StringBuilder(); insertTableStringBuilder.append("INSERT INTO ").append(tempTableName).append(" ("); insertTableStringBuilder.append(TextUtils.join(",", properties)); insertTableStringBuilder.append(") SELECT "); insertTableStringBuilder.append(TextUtils.join(",", properties)); insertTableStringBuilder.append(" FROM ").append(tableName).append(";"); db.execSQL(insertTableStringBuilder.toString()); } } /** * 儲存新的資料庫表 以及資料 * * @param db * @param daoClasses */ private 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"); ArrayList<String> properties = new ArrayList(); for (int j = 0; j < daoConfig.properties.length; j++) { String columnName = daoConfig.properties[j].columnName; if (getColumns(db, tempTableName).contains(columnName)) { properties.add(columnName); } } StringBuilder insertTableStringBuilder = new StringBuilder(); insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" ("); insertTableStringBuilder.append(TextUtils.join(",", properties)); insertTableStringBuilder.append(") SELECT "); insertTableStringBuilder.append(TextUtils.join(",", properties)); insertTableStringBuilder.append(" FROM ").append(tempTableName).append(";"); StringBuilder dropTableStringBuilder = new StringBuilder(); dropTableStringBuilder.append("DROP TABLE ").append(tempTableName); db.execSQL(insertTableStringBuilder.toString()); db.execSQL(dropTableStringBuilder.toString()); } } private String getTypeByClass(Class<?> type) throws Exception { if (type.equals(String.class)) { return "TEXT"; } if (type.equals(Long.class) || type.equals(Integer.class) || type.equals(long.class)) { return "INTEGER"; } if (type.equals(Boolean.class)) { return "BOOLEAN"; } Exception exception = new Exception(CONVERSION_CLASS_NOT_FOUND_EXCEPTION.concat(" - Class: ").concat(type.toString())); exception.printStackTrace(); throw exception; } private List<String> getColumns(Database db, String tableName) { List<String> columns = new ArrayList<>(); Cursor cursor = null; try { cursor = db.rawQuery("SELECT * FROM " + tableName + " limit 1", null); if (cursor != null) { columns = new ArrayList<>(Arrays.asList(cursor.getColumnNames())); } } catch (Exception e) { Log.v(tableName, e.getMessage(), e); e.printStackTrace(); } finally { if (cursor != null) cursor.close(); } return columns; } }
  • 然後需要知道資料庫更新表的方法是DaoMaster類中的onUpgrade方法:

這裡寫圖片描述

注意:

這個類為配置greendao後系統自動生成,不能直接在這裡操作,不然每次執行此類都是重新生成的。
  • 構建MyOpenHelper幫助類,重寫onUpgrade方法進行操作:
package cn.hnshangyu.testgreendao.helper;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import org.greenrobot.greendao.database.Database;

import cn.hnshangyu.testgreendao.greendao.DaoMaster;
import cn.hnshangyu.testgreendao.greendao.StudentDao;

public class MyOpenHelper extends DaoMaster.OpenHelper {

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

    /**
     * 資料庫升級
     * @param db
     * @param oldVersion
     * @param newVersion
     */
    @Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
        //操作資料庫的更新 有幾個表升級都可以傳入到下面
        MigrationHelper.getInstance().migrate(db,StudentDao.class);
    }

}
  • 什麼地方使用MyOpenHelper 類呢?

之前的greendao3.0以上使用步驟(一)中,我建立了一個DbManager管理類來進行對資料庫的統一管理,今天同樣使用這個類:

那麼與之前的有什麼區別呢??

其實就是在獲取DaoMaster時有所改變,開啟資料庫方式不同

1、之前的方法

    /**
     * 獲取DaoMaster
     *
     * @param context
     * @return
     */
    public static DaoMaster getDaoMaster(Context context) {
        if (null == mDaoMaster) {
            synchronized (DbManager.class) {
                if (null == mDaoMaster) {

                    mDaoMaster = new DaoMaster(getWritableDatabase(context));
                }
            }
        }
        return mDaoMaster;
    }

2、現在的

    /**
     * 獲取DaoMaster
     *
     * 判斷是否存在資料庫,如果沒有則建立資料庫
     * @param context
     * @return
     */
    public static DaoMaster getDaoMaster(Context context) {
        if (null == mDaoMaster) {
            synchronized (DbManager.class) {
                if (null == mDaoMaster) {
                    MyOpenHelper helper = new MyOpenHelper(context,DB_NAME,null);
                    mDaoMaster = new DaoMaster(helper.getWritableDatabase());
                }
            }
        }
        return mDaoMaster;
    }

3、整個類的類容:

package cn.hnshangyu.testgreendao.db;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;

import cn.hnshangyu.testgreendao.greendao.DaoMaster;
import cn.hnshangyu.testgreendao.greendao.DaoSession;
import cn.hnshangyu.testgreendao.helper.MyOpenHelper;

public class DbManager {

    // 是否加密
    public static final boolean ENCRYPTED = true;

    private static final String DB_NAME = "test.db";
    private static DbManager mDbManager;
    private static DaoMaster.DevOpenHelper mDevOpenHelper;
    private static DaoMaster mDaoMaster;
    private static DaoSession mDaoSession;

    private Context mContext;

    private DbManager(Context context) {
        this.mContext = context;
        // 初始化資料庫資訊
        mDevOpenHelper = new DaoMaster.DevOpenHelper(context, DB_NAME);
        getDaoMaster(context);
        getDaoSession(context);
    }

    public static DbManager getInstance(Context context) {
        if (null == mDbManager) {
            synchronized (DbManager.class) {
                if (null == mDbManager) {
                    mDbManager = new DbManager(context);
                }
            }
        }
        return mDbManager;
    }

    /**
     * 獲取可讀資料庫
     *
     * @param context
     * @return
     */
    public static SQLiteDatabase getReadableDatabase(Context context) {
        if (null == mDevOpenHelper) {
            getInstance(context);
        }
        return mDevOpenHelper.getReadableDatabase();
    }

    /**
     * 獲取可寫資料庫
     *
     * @param context
     * @return
     */
    public static SQLiteDatabase getWritableDatabase(Context context) {
        if (null == mDevOpenHelper) {
            getInstance(context);
        }
        return mDevOpenHelper.getWritableDatabase();
    }

    /**
     * 獲取DaoMaster
     *
     * 判斷是否存在資料庫,如果沒有則建立資料庫
     * @param context
     * @return
     */
    public static DaoMaster getDaoMaster(Context context) {
        if (null == mDaoMaster) {
            synchronized (DbManager.class) {
                if (null == mDaoMaster) {
                    MyOpenHelper helper = new MyOpenHelper(context,DB_NAME,null);
                    mDaoMaster = new DaoMaster(helper.getWritableDatabase());
                }
            }
        }
        return mDaoMaster;
    }

    /**
     * 獲取DaoMaster
     *
     * @param context
     * @return
     */
//    public static DaoMaster getDaoMaster(Context context) {
//        if (null == mDaoMaster) {
//            synchronized (DbManager.class) {
//                if (null == mDaoMaster) {
//
//                    mDaoMaster = new DaoMaster(getWritableDatabase(context));
//                }
//            }
//        }
//        return mDaoMaster;
//    }

    /**
     * 獲取DaoSession
     *
     * @param context
     * @return
     */
    public static DaoSession getDaoSession(Context context) {
        if (null == mDaoSession) {
            synchronized (DbManager.class) {
                mDaoSession = getDaoMaster(context).newSession();
            }
        }

        return mDaoSession;
    }
}
  • 現在我們在bean物件裡面加一個num欄位,注意型別:
package cn.hnshangyu.testgreendao.bean;

import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Keep;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.NotNull;

@Entity(generateConstructors = false)
public class Student {
    @Id
    private Long id;
    private String name;
    private int age;
    private int num;

    public int getNum() {
        return num;
    }

    public void setNum(int num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", num=" + num +
                '}';
    }

    public Student() {
    }


    @Keep
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }


    public Student(Long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;

    }

    @Keep
    public Long getId() {
        return id;
    }

    @Keep
    public void setId(Long id) {
        this.id = id;
    }

    @Keep
    public String getName() {
        return name;
    }


    @Keep
    public void setName(String name) {
        this.name = name;
    }

    @Keep
    public int getAge() {
        return age;
    }

    @Keep
    public void setAge(int age) {
        this.age = age;
    }

    @Keep
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;

        Student student = (Student) o;

        return name.equals(student.name);

    }

    @Keep
    @Override
    public int hashCode() {
        return (int) (id ^ (id >>> 32));
    }

}

大家看到了我加了一個int型別的欄位

  • 最後就是升級版本了,在build升級版本

這裡寫圖片描述

版本升為2了,同步一下

然後執行程式………

哎呀!你會發現報了一個重大bug,就是開始我提到的新增欄位不能為空,這是為什麼呢?bean物件中我沒有限定不能為空啊!

好吧我們看看怎麼回事吧!

  • 首先他報錯的部位為MigrationHelper的restoreData方法,把新臨時表資料拷貝到新表中

這裡寫圖片描述

  • 既然欄位不能為空,大部分是因為建立表的時候造成的

MigrationHelper建立表的時候呼叫的是DaoMaster的createAllTables方法:


    /** Creates underlying database table using DAOs. */
    public static void createAllTables(Database db, boolean ifNotExists) {
        StudentDao.createTable(db, ifNotExists);
    }

從greendao生成的原始碼中可以看到呼叫的是StudentDao的createTable方法

那麼在StudentDao中:

    /** Creates the underlying database table. */
    public static void createTable(Database db, boolean ifNotExists) {
        String constraint = ifNotExists? "IF NOT EXISTS ": "";
        db.execSQL("CREATE TABLE " + constraint + "\"STUDENT\" (" + //
                "\"_id\" INTEGER PRIMARY KEY ," + // 0: id
                "\"NAME\" TEXT," + // 1: name
                "\"AGE\" INTEGER NOT NULL ," + // 2: age
                "\"NUM\" INTEGER NOT NULL);"); // 3: num
    }

哎呀!發現了,INTEGER NOT NULL (integer)不能為空?這下知道原因了,int型別的是不能為空的怎麼辦呢??

我們又看到了NAME這個欄位他是TEXT型別,也就是String型別的可以為空,

因此,我們先把NUM欄位改為String型別

package cn.hnshangyu.testgreendao.bean;

import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Id;
import org.greenrobot.greendao.annotation.Keep;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.NotNull;

@Entity(generateConstructors = false)
public class Student {
    @Id
    private Long id;
    private String name;
    private int age;
    private String num;

    public String getNum() {
        return num;
    }

    public void setNum(String num) {
        this.num = num;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", num=" + num +
                '}';
    }

    public Student() {
    }


    @Keep
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }


    public Student(Long id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;

    }

    @Keep
    public Long getId() {
        return id;
    }

    @Keep
    public void setId(Long id) {
        this.id = id;
    }

    @Keep
    public String getName() {
        return name;
    }


    @Keep
    public void setName(String name) {
        this.name = name;
    }

    @Keep
    public int getAge() {
        return age;
    }

    @Keep
    public void setAge(int age) {
        this.age = age;
    }

    @Keep
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Student)) return false;

        Student student = (Student) o;

        return name.equals(student.name);

    }

    @Keep
    @Override
    public int hashCode() {
        return (int) (id ^ (id >>> 32));
    }

}

重新進行上述資料庫更新操作,把bean物件中的num改為了String型別,

執行後結果正常,資料庫更新了!

  • 看看StudentDao新建立表
    /** Creates the underlying database table. */
    public static void createTable(Database db, boolean ifNotExists) {
        String constraint = ifNotExists? "IF NOT EXISTS ": "";
        db.execSQL("CREATE TABLE " + constraint + "\"STUDENT\" (" + //
                "\"_id\" INTEGER PRIMARY KEY ," + // 0: id
                "\"NAME\" TEXT," + // 1: name
                "\"AGE\" INTEGER NOT NULL ," + // 2: age
                "\"NUM\" TEXT);"); // 3: num
    }

新的欄位NUM為TEXT型別,一切正常!

總結:在greendao3.0升級資料庫時

我們基本思路為:

建立臨時表-->刪除原表-->建立新表-->複製臨時表資料到新表並刪除臨時表
並且我們新增加的和修改的欄位做好為String型別,避免欄位不能為null的情況發生

到這裡greendao3.0資料庫更新說明完畢……

有什麼不妥,歡迎指出,謝謝啦………