第三方數(shù)據(jù)庫框架 - GreenDao簡介

1. 概述


在android開發(fā)中,可能或多或少的都會接觸 Sqlite數(shù)據(jù)庫,然而我們在使用它的時候通常需要做很多的額外工作,比如就像編寫 Sqlite語句、解析查詢結(jié)果等等,所以適用于Android的 ORM框架 橫空出世,現(xiàn)在市面上邊主流的框架有 Sqlite、LitePal、GreenDao、Realm、OrmLite、SugarORM、Active Android,而 GreenDao號稱是速度最快的 ORM框架,在使用之前需要配置一些地方,那么接下來我們就來看下它的具體配置及時如何使用的。

ORM還不是很清楚的,可以看下我之前的文章 第三方數(shù)據(jù)庫框架 - LitePal簡介

2. 需要配置的地方


2.1>:project下的build.gradle
圖片.png
2.2>:app下的 build.gradle,這里需要配置3個地方
圖片.png

圖片.png
需要注意下邊配置的地方:
/*targetGenDirTest:設(shè)置生成單元測試目錄
 generateTests:設(shè)置自動生成單元測試用例*/

schemaVersion 1  // 數(shù)據(jù)庫schema版本,也就是數(shù)據(jù)庫的版本號
daoPackage 'cn.novate.greendao.greendao' // DaoMaster、DaoSession、UserDao所在的包名
targetGenDir 'src/main/java'    // DaoMaster、DaoSession、UserDao所在的目錄

以上就已經(jīng)配置好了,然后點(diǎn)擊 Sync Now,就是立即構(gòu)建,就會在 cn.novate.greendao包下邊生成 DaoMaster、DaoSession、UserDao這3個類,注意這3個類都是 在 上邊自己配置的cn.novate.greendao.greendao包下邊,接下來就是具體使用了。


圖片.png

3. 具體使用


3.1>: 寫一個JavaBean,也就是我們的 User 實(shí)體類對象,就是我們數(shù)據(jù)庫中的表;
/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/11 9:07
 * Version 1.0
 * Params:
 * Description:
*/
/**
 * @Entity: 將我們普通的java類變?yōu)橐粋€能夠被 greendao 識別的數(shù)據(jù)庫類型的實(shí)體類
 * @Id: 通過 @Id 注解 標(biāo)記的字段必須是 Long類型的,注意是包裝類型的,這個字段在數(shù)據(jù)庫中表示它就是主鍵,并且默認(rèn)是自增的
 * @NotNul: 數(shù)據(jù)庫的表當(dāng)前的列不能為空
 */
@Entity
public class User {
    @Id
    private Long id ;
    private String name ;
    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Long getId() {
        return this.id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    @Generated(hash = 873297011)
    public User(Long id, String name) {
        this.id = id;
        this.name = name;
    }
    @Generated(hash = 586692638)
    public User() {
    }
}
3.2>:然后點(diǎn)擊 build下邊的 Make Project,然后就會發(fā)現(xiàn)自己的 User實(shí)體類中多了好多代碼,沒錯,這個就是 GreenDao給我們自動生成的
圖片.png
需要注意:

第一:如果你想再次添加實(shí)體類Age,可以直接寫一個實(shí)體類Age,然后點(diǎn)擊 Build下的 Make Project會重新為你生成AgeDao;
第二:不要手動修改DaoMaster、DaoSession、UseDao和User中的代碼,因?yàn)槊恳淮尉幾g項(xiàng)目的時候,都會重新生成一次DaoMaster、DaoSession、UserDao和User,所以說如果修改了的話就會被覆蓋;

3.3>:為了便于數(shù)據(jù)的讀取和添加,這里新建GreenDaoHelper,用于獲取DaoMaster、DaoSession,代碼如下:
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/11 13:18
 * Version 1.0
 * Params:
 * Description:  便于數(shù)據(jù)的讀取和添加,新建GreenDaoHelper輔助類
*/
public class GreenDaoHelper  extends Application {
    private GreenDaoHelper Instance;
    private static DaoMaster daoMaster;
    private static DaoSession daoSession;

    public GreenDaoHelper getInstance() {
        if (Instance == null) {
            Instance = this;
        }
        return Instance;
    }

    /**
     * 獲取DaoMaster
     *
     * @param context
     * @return
     */
    public static DaoMaster getDaoMaster(Context context) {
        if (daoMaster == null) {
            try{
                DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(context,"test.db",null);
                daoMaster = new DaoMaster(helper.getWritableDatabase()); //獲取未加密的數(shù)據(jù)庫
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return daoMaster;
    }

    /**
     * 獲取DaoSession對象
     *
     * @param context
     * @return
     */
    public static DaoSession getDaoSession(Context context) {

        if (daoSession == null) {
            if (daoMaster == null) {
                getDaoMaster(context);
            }
            daoSession = daoMaster.newSession();
        }
        return daoSession;
    }
}
需要注意:

到這里就已經(jīng)創(chuàng)建好User實(shí)體類,并且也可以直接獲取 DaoMaster、DaoSession對象,接下來就可以進(jìn)行增刪改查操作了。

4. 添加數(shù)據(jù)和查詢


4.1>:添加數(shù)據(jù)
第一:創(chuàng)建User對象,然后設(shè)置數(shù)據(jù),參數(shù)一是id,Long包裝類型的,參數(shù)二是name,傳遞時候傳遞的是null目的就是在插入的過程中,id會自增長,
第二:調(diào)用 UserDao的 insert方法,用于添加數(shù)據(jù);
具體代碼如下:
public class MainActivity extends AppCompatActivity {

    private DaoSession daoSession;
    private TextView textview;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //初始化權(quán)限
        initPermission();

        textview = (TextView) findViewById(R.id.textview);

        daoSession = GreenDaoHelper.getDaoSession(this);
        daoSession.getUserDao().deleteAll(); // 清空所有記錄

        // 添加數(shù)據(jù)
        // 參數(shù)1:id 傳遞null表示
        User user = new User(null , "王子文") ;
        User user1 = new User(null , "北京-Novate") ;
        daoSession.getUserDao().insert(user) ;
        daoSession.getUserDao().insert(user1) ;

        // 查詢數(shù)據(jù)
        StringBuilder sb = new StringBuilder() ;
        List<User> users = daoSession.getUserDao().loadAll() ;
        for (int i = 0; i < users.size(); i++) {
            sb.append("id: ").append(users.get(i).getId()).
                    append(", name: ").append(users.get(i).getName()).append("\n") ;

        }

        textview.setText(sb);

    }


    /**
     * 初始化權(quán)限事件
     */
    private void initPermission() {
        //檢查權(quán)限
        String[] permissions = CheckPermissionUtils.checkPermission(this);
        if (permissions.length == 0) {
            //權(quán)限都申請了
            //是否登錄
        } else {
            //申請權(quán)限
            ActivityCompat.requestPermissions(this, permissions, 100);
        }
    }
}
運(yùn)行結(jié)果如下:
圖片.png

5. 修改存放數(shù)據(jù)庫路徑


一般情況下,新建的數(shù)據(jù)庫默認(rèn)位置是存放在 data/data/包名/database下邊的,手機(jī)如果不root的話,根本就無法查看 test.db數(shù)據(jù)庫文件,更別提想要去操作該 test.db數(shù)據(jù)庫文件。而在實(shí)際的開發(fā)過程中,可能需要copy數(shù)據(jù)庫,或者使用第三方工具打開 該 test.db數(shù)據(jù)庫文件來查看里邊的數(shù)據(jù),此時可以通過重寫 Context的 getDatabasePath()、openOrCreateDatabase()、openOrCreateDatabase()這3個方法來修改 test.db的數(shù)據(jù)庫文件的存放路徑。

// 方法一
getDatabasePath(String name)
// 方法二
openOrCreateDatabase(String name, int mode, CursorFactory factory)
// 方法三
openOrCreateDatabase(String name, int mode, CursorFactory factory, DatabaseErrorHandler errorHandler)

上邊已經(jīng)說了,DaoMaster中的代碼是不能修改的,所以我們可以把重寫的方法 放到 GreenDaoHelper中即可,具體代碼如下:

    public class GreenDaoHelper extends Application {  
        private GreenDaoHelper Instance;  
        private static DaoMaster daoMaster;  
        private static DaoSession daoSession;  
      
        public GreenDaoHelper getInstance() {  
            if (Instance == null) {  
                Instance = this;  
            }  
            return Instance;  
        }  
      
        /** 
        * 獲取DaoMaster 
        * 
        * @param context 
        * @return 
        */  
        public static DaoMaster getDaoMaster(Context context) {  
      
            if (daoMaster == null) {  
      
                try{  
                    ContextWrapper wrapper = new ContextWrapper(context) {  
                    /** 
                    * 獲得數(shù)據(jù)庫路徑,如果不存在,則創(chuàng)建對象對象 
                    * 
                    * @param name 
                    */  
                    @Override  
                    public File getDatabasePath(String name) {  
                        // 判斷是否存在sd卡  
                        boolean sdExist = android.os.Environment.MEDIA_MOUNTED.equals(android.os.Environment.getExternalStorageState());  
                        if (!sdExist) {// 如果不存在,  
                            Log.e("SD卡管理:", "SD卡不存在,請加載SD卡");  
                            return null;  
                        } else {// 如果存在  
                            // 獲取sd卡路徑  
                            String dbDir = android.os.Environment.getExternalStorageDirectory().getAbsolutePath();  
                            dbDir += "/Android";// 數(shù)據(jù)庫所在目錄  
                            String dbPath = dbDir + "/" + name;// 數(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;  
                            // 返回數(shù)據(jù)庫文件對象  
                            if (isFileCreateSuccess)  
                                return dbFile;  
                            else  
                                return super.getDatabasePath(name);  
                        }  
                    }  
      
                    /** 
                    * 重載這個方法,是用來打開SD卡上的數(shù)據(jù)庫的,android 2.3及以下會調(diào)用這個方法。 
                    * 
                    * @param name 
                    * @param mode 
                    * @param factory 
                    */  
                    @Override  
                    public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) {  
                        return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);  
                    }  
      
                    /** 
                    * Android 4.0會調(diào)用此方法獲取數(shù)據(jù)庫。 
                    * 
                    * @see android.content.ContextWrapper#openOrCreateDatabase(java.lang.String, 
                    *      int, 
                    *      android.database.sqlite.SQLiteDatabase.CursorFactory, 
                    *      android.database.DatabaseErrorHandler) 
                    * @param name 
                    * @param mode 
                    * @param factory 
                    * @param errorHandler 
                    */  
                    @Override  
                    public SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) {  
                        return SQLiteDatabase.openOrCreateDatabase(getDatabasePath(name), null);  
                    }  
                    };  
                    DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(wrapper,"test.db",null);  
                    daoMaster = new DaoMaster(helper.getWritableDatabase()); //獲取未加密的數(shù)據(jù)庫  
                }catch (Exception e){  
                    e.printStackTrace();  
                }  
            }  
            return daoMaster;  
        }  
      
        /** 
        * 獲取DaoSession對象 
        * 
        * @param context 
        * @return 
        */  
        public static DaoSession getDaoSession(Context context) {  
      
            if (daoSession == null) {  
                if (daoMaster == null) {  
                    getDaoMaster(context);  
                }  
                daoSession = daoMaster.newSession();  
            }  
      
            return daoSession;  
        }  
    }  
通過上邊修改后的 GreenDaoHelper的代碼后,我們可以在 手機(jī)存儲 --> Android --> 里邊就會有 test.db數(shù)據(jù)庫文件,不清楚自己創(chuàng)建的 test.db數(shù)據(jù)庫文件存放路徑在哪,可以看下邊我的手機(jī)截圖:
圖片.png

圖片.png

圖片.png
然后可以通過 qq或者微信 發(fā)送到電腦桌面,通過第三方工具打開該 test.db數(shù)據(jù)庫文件,就可以看到自己在代碼中寫的User對象實(shí)體類對應(yīng)的 --> USER表及該表中的字段如下圖所示:
當(dāng)然也可以使用 手機(jī)查看,都是可以的。

6. 數(shù)據(jù)庫加密


可以直接調(diào)用 DaoMaster.OpenHelper()的getEncryptedWritableDb(password)或者getEncryptedReadableDb(password)方法即可,就可以對獲取一個加密的數(shù)據(jù)庫;

public static DaoMaster getDaoMaster(Context context) {  
  
    if (daoMaster == null) {  
  
        try{  
            ContextWrapper wrapper = new ContextWrapper(context) {  
                 ...  
            };  
            DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(wrapper,"test.db",null);  
            daoMaster = new DaoMaster(helper.getEncryptedWritableDb("1234"));//獲取加密的數(shù)據(jù)庫  
            //daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234"));//獲取加密的數(shù)據(jù)庫  
            //daoMaster = new DaoMaster(helper.getWritableDatabase()); //獲取未加密的數(shù)據(jù)庫  
        }catch (Exception e){  
            e.printStackTrace();  
        }  
    }  
    return daoMaster;  
}

若要解密或重新加密數(shù)據(jù)庫,可參考博客《利用SQLCipher加解密數(shù)據(jù)庫(包括加解密已有的數(shù)據(jù)庫)》。

7. 數(shù)據(jù)庫升級但又不刪除數(shù)據(jù)


使用DevOpenHelper升級數(shù)據(jù)庫時,表都會刪除重建。因此,在實(shí)際開發(fā)過程中都是在 GreenDaoHelper中自己寫一個類繼承 DaoMaster.OpenHelper實(shí)現(xiàn) onUpdate()方法,使得數(shù)據(jù)庫升級。我們示例代碼中是這樣做的:

7.1>:復(fù)制 MigrationHelper類到項(xiàng)目中;
7.2>:然后在 GreenDaoHelper中自定義MySQLiteOpenHelper繼承 DaoMaster.OpenHelper,實(shí)現(xiàn) onUpdate()方法;代碼如下:
public class GreenDaoHelper extends Application {  
    private GreenDaoHelper Instance;  
    private static DaoMaster daoMaster;  
    private static DaoSession daoSession;  
  
    public GreenDaoHelper getInstance() {  
        if (Instance == null) {  
            Instance = this;  
        }  
        return Instance;  
    }  
  
    /** 
    * 獲取DaoMaster 
    * 
    * @param context 
    * @return 
    */  
    public static DaoMaster getDaoMaster(Context context) {  
  
        if (daoMaster == null) {  
  
            try{  
                ContextWrapper wrapper = new ContextWrapper(context) {  
                          ...  
                };  
                DaoMaster.OpenHelper helper = new MySQLiteOpenHelper(wrapper,"test.db",null);  
                //daoMaster = new DaoMaster(helper.getEncryptedWritableDb("1234"));//獲取加密的數(shù)據(jù)庫  
                //daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234"));//獲取加密的數(shù)據(jù)庫  
                daoMaster = new DaoMaster(helper.getWritableDatabase()); //獲取未加密的數(shù)據(jù)庫  
            }catch (Exception e){  
                e.printStackTrace();  
            }  
        }  
        return daoMaster;  
    }  
  
    /** 
    * 獲取DaoSession對象 
    * 
    * @param context 
    * @return 
    */  
    public static DaoSession getDaoSession(Context context) {  
  
        if (daoSession == null) {  
            if (daoMaster == null) {  
                getDaoMaster(context);  
            }  
            daoSession = daoMaster.newSession();  
        }  
  
        return daoSession;  
    }  
  
    private static class MySQLiteOpenHelper extends DaoMaster.OpenHelper {  
  
        public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {  
            super(context, name, factory);  
        }  
  
        private static final String UPGRADE="upgrade";  
  
        @Override  
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
            MigrationHelper.migrate(db,AreaDao.class);  
            Log.e(UPGRADE,"upgrade run success");  
        }  
    }  
} 
7.3>:然后新建一個 People實(shí)體類類,自己只需要寫下邊代碼即可,然后直接 build --> Make Model app就會生成下邊的代碼;

@Entity
public class People {
@Id
private Long id ;
private String Name ;
private String Sex ;
}

@Entity
public class People {
    @Id
    private Long id ;
    private String Name ;
    private String Sex ;
    public String getSex() {
        return this.Sex;
    }
    public void setSex(String Sex) {
        this.Sex = Sex;
    }
    public String getName() {
        return this.Name;
    }
    public void setName(String Name) {
        this.Name = Name;
    }
    public Long getId() {
        return this.id;
    }
    public void setId(Long id) {
        this.id = id;
    }
    @Generated(hash = 1284135911)
    public People(Long id, String Name, String Sex) {
        this.id = id;
        this.Name = Name;
        this.Sex = Sex;
    }
    @Generated(hash = 1406030881)
    public People() {
    }
}
7.4>:修改 schemaVersion 版本號為更高的;
7.5>:然后修改 onUpdate()方法如下:
@Override  
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {  
    MigrationHelper.migrate(db,AreaDao.class, PeopleDao.class);  
    Log.e(UPGRADE,"upgrade run success");  
} 

然后運(yùn)行程序,發(fā)現(xiàn)會報如下的錯,意思就是找不到People這張表:


圖片.png
通過閱讀源碼發(fā)現(xiàn),程序會根據(jù)傳入的 beanDao會對所有的 JavaBean創(chuàng)建臨時表, 然后把 該 JavaBean表中的數(shù)據(jù) 復(fù)制到 bean_temp臨時表中,但是這個時候 People實(shí)體類是新創(chuàng)建的,數(shù)據(jù)庫中并沒有這個表,所以會報上邊的錯誤,所以我們只需要對源碼稍作修改,讓它只對數(shù)據(jù)庫中已有的表創(chuàng)建臨時表并且保存數(shù)據(jù),還有,源碼中是按照字段恢復(fù)數(shù)據(jù),我們把它修改為全表查詢恢復(fù);

代碼如下:

    public final class MigrationHelper {  
        public static boolean DEBUG = false;  
        private static String TAG = "MigrationHelper";  
      
        private static List<String> tablenames = new ArrayList<>();  
      
        public static List<String> getTables(SQLiteDatabase db){  
            List<String> tables = new ArrayList<>();  
      
            Cursor cursor = db.rawQuery("select name from sqlite_master where type='table' order by name", null);  
            while(cursor.moveToNext()){  
                //遍歷出表名  
                tables.add(cursor.getString(0));  
            }  
            cursor.close();  
            return tables;  
        }  
      
        public static void migrate(SQLiteDatabase db, Class<? extends AbstractDao<?, ?>>... daoClasses) {  
            Database database = new StandardDatabase(db);  
            if (DEBUG) {  
                Log.d(TAG, "【Database Version】" + db.getVersion());  
                Log.d(TAG, "【Generate temp table】start");  
            }  
      
            tablenames=getTables(db);  
      
            generateTempTables(database, daoClasses);  
            if (DEBUG) {  
                Log.d(TAG, "【Generate temp table】complete");  
            }  
            dropAllTables(database, true, daoClasses);  
            createAllTables(database, false, daoClasses);  
      
            if (DEBUG) {  
                Log.d(TAG, "【Restore data】start");  
            }  
            restoreData(database, daoClasses);  
            if (DEBUG) {  
                Log.d(TAG, "【Restore data】complete");  
            }  
        }  
      
        private static void generateTempTables(Database db, Class<? extends AbstractDao<?, ?>>... daoClasses) {  
            for (int i = 0; i < daoClasses.length; i++) {  
                String tempTableName = null;  
      
                try {  
                    DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);  
                    if(!tablenames.contains(daoConfig.tablename)){//如果數(shù)據(jù)庫中沒有該表,則繼續(xù)下次循環(huán)  
                        continue;  
                    }  
                    String tableName = daoConfig.tablename;  
                    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());  
                    if (DEBUG) {  
                        Log.d(TAG, "【Table】" + tableName +"\n ---Columns-->"+getColumnsStr(daoConfig));  
                        Log.d(TAG, "【Generate temp table】" + tempTableName);  
                    }  
                } catch (SQLException e) {  
                    Log.e(TAG, "【Failed to generate temp table】" + tempTableName, e);  
                }  
            }  
        }  
      
        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);  
            if (DEBUG) {  
                Log.d(TAG, "【Drop all table】");  
            }  
        }  
      
        private static void createAllTables(Database db, boolean ifNotExists, @NonNull Class<? extends AbstractDao<?, ?>>... daoClasses) {  
            reflectMethod(db, "createTable", ifNotExists, daoClasses);  
            if (DEBUG) {  
                Log.d(TAG, "【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++) {  
                String tempTableName = null;  
      
                try {  
                    DaoConfig daoConfig = new DaoConfig(db, daoClasses[i]);  
                    String tableName = daoConfig.tablename;  
      
                    if(!tablenames.contains(tableName)){  
                        continue;  
                    }  
      
                    tempTableName = daoConfig.tablename.concat("_TEMP");  
                    StringBuilder insertTableStringBuilder = new StringBuilder();  
                    insertTableStringBuilder.append("INSERT INTO ").append(tableName).append(" SELECT * FROM ").append(tempTableName).append(";");  
                    db.execSQL(insertTableStringBuilder.toString());  
                    if (DEBUG) {  
                        Log.d(TAG, "【Restore data】 to " + tableName);  
                    }  
      
                    StringBuilder dropTableStringBuilder = new StringBuilder();  
                    dropTableStringBuilder.append("DROP TABLE IF EXISTS ").append(tempTableName);  
                    db.execSQL(dropTableStringBuilder.toString());  
                    if (DEBUG) {  
                        Log.d(TAG, "【Drop temp table】" + tempTableName);  
                    }  
                } catch (SQLException e) {  
                    Log.e(TAG, "【Failed to restore data from temp table (probably new table)】" + tempTableName, e);  
                }  
            }  
        }  
    }  
這個時候,再去新建一個 Product實(shí)體類,修改版本號,同時修改 onUpdate()方法;

Product代碼如下:

@Entity
public class Product {
    @Id
    private Long Id ;
    private String Name ;
    public String getName() {
        return this.Name;
    }
    public void setName(String Name) {
        this.Name = Name;
    }
    public Long getId() {
        return this.Id;
    }
    public void setId(Long Id) {
        this.Id = Id;
    }
    @Generated(hash = 2099832872)
    public Product(Long Id, String Name) {
        this.Id = Id;
        this.Name = Name;
    }
    @Generated(hash = 1890278724)
    public Product() {
    }

}

onUpdate()方法及運(yùn)行結(jié)果如下:


圖片.png

注意:

1>:MigrationHelper.migrate()暫時只接收 SQLiteDatabase,不接收 DataBase;
2>:對加密的數(shù)據(jù)庫更新是無效的;

我們在開發(fā)過程中,由于要保證數(shù)據(jù)的安全性,所以一般都是需要對 數(shù)據(jù)庫加密的,那么對于 加密的數(shù)據(jù)庫,該如何更新呢?我們采用的思想就是 —— 逆推,也就是說首先分析MigrationHelper.migrate()為什么不支持 對 加密數(shù)據(jù)庫的更新,然后再找出對應(yīng)的解決方法。

8. 分析MigrationHelper.migrate()為什么不支持對 加密數(shù)據(jù)庫的 更新?


圖片.png

由上圖可知,MigrationHelper.migrate()更新數(shù)據(jù)庫時調(diào)用的是 DatabaseOpenHelper中內(nèi)部類 EncrytedHelper類中的 onUpdate()方法,而該方法調(diào)用的是 DatabaseOpenHelper中的 onUpdate()方法,點(diǎn)擊進(jìn)去后發(fā)現(xiàn) 該onUpdate()方法沒有做任何操作,如下圖所示;


圖片.png

所以 MigrationHelper.migrate()方法 不支持 加密數(shù)據(jù)庫的 更新。

9. 對加密數(shù)據(jù)庫的更新 的 解決方案


9.1>:在 GreenDaoHelper 中 新建一個類 MyEncryptedSQLiteOpenHelper 繼承 DaoMaster.OpenHelper,然后實(shí)現(xiàn) onUpdate()、getEncryptedWritableDb(String password)方法;
9.2>:然后在 MyEncryptedSQLiteOpenHelper 內(nèi)部中 再去 寫一個MyEncryptedHelper類 繼承 net.sqlcipher.database.SQLiteOpenHelper,目的就是代替 DatabaseOpenHelper中的 EncryptedHelper內(nèi)部類
1>:首先需要添加對 sqlcipher 的依賴:
compile 'net.zetetic:android-database-sqlcipher:3.5.4@aar'  
2>:然后修改 GreenDaoHelper代碼如下:
/**
 * Email: 2185134304@qq.com
 * Created by JackChen 2018/4/11 13:18
 * Version 1.0
 * Params:
 * Description:  便于數(shù)據(jù)的讀取和添加,新建GreenDaoHelper輔助類
*/
public class GreenDaoHelper  extends Application {

    private GreenDaoHelper Instance;
    private static DaoMaster daoMaster;
    private static DaoSession daoSession;

    public GreenDaoHelper getInstance() {
        if (Instance == null) {
            Instance = this;
        }
        return Instance;
    }

    /**
     * 獲取DaoMaster
     *
     * @param context
     * @return
     */
    public static DaoMaster getDaoMaster(Context context) {

        if (daoMaster == null) {

            try{
                ContextWrapper wrapper = new ContextWrapper(context) {
                    /**
                     * 獲得數(shù)據(jù)庫路徑,如果不存在,則創(chuàng)建對象對象
                     */
                    @Override
                    public File getDatabasePath(String name) {
                        // 判斷是否存在sd卡
                        boolean sdExist = android.os.Environment.MEDIA_MOUNTED.equals(android.os.Environment.getExternalStorageState());
                        if (!sdExist) { // 如果不存在,
                            Log.e("SD卡管理:", "SD卡不存在,請加載SD卡");
                            return null;
                        } else {// 如果存在
                            // 獲取sd卡路徑
                            String dbDir = android.os.Environment.getExternalStorageDirectory().getAbsolutePath();
                            dbDir += "/Android";// 數(shù)據(jù)庫所在目錄
                            String dbPath = dbDir + "/" + name;// 數(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;
                            // 返回數(shù)據(jù)庫文件對象
                            if (isFileCreateSuccess)
                                return dbFile;
                            else
                                return super.getDatabasePath(name);
                        }
                    }

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

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


                /** 如果使用DevOpenHelper升級數(shù)據(jù)庫時,表都會刪除重建,所以需要自定義 一個類繼承 DaoMaster.OpenHelper,實(shí)現(xiàn)onUpdate()方法即可 */

                /*DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(wrapper,"test.db",null);
                daoMaster = new DaoMaster(helper.getWritableDatabase());*/ //獲取未加密的數(shù)據(jù)庫
//                daoMaster = new DaoMaster(helper.getEncryptedWritableDb("1234")) ;  // 獲取加密的數(shù)據(jù)庫  下邊的2種方法都是可以的
//                daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234")) ;


                //適用于未加密的數(shù)據(jù)庫
                DaoMaster.OpenHelper helper = new MySQLiteOpenHelper(wrapper,"test.db",null);
                daoMaster = new DaoMaster(helper.getWritableDatabase()); //獲取未加密的數(shù)據(jù)庫


                // 適用于加密的數(shù)據(jù)庫
//                MyEncryptedSQLiteOpenHelper helper = new MyEncryptedSQLiteOpenHelper(wrapper , "test.db" , null) ;





            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return daoMaster;
    }

    /**
     * 獲取DaoSession對象
     *
     * @param context
     * @return
     */
    public static DaoSession getDaoSession(Context context) {

        if (daoSession == null) {
            if (daoMaster == null) {
                getDaoMaster(context);
            }
            daoSession = daoMaster.newSession();
        }

        return daoSession;
    }


    /**
     * 適用于未加密的數(shù)據(jù)庫
     */
    private static class MySQLiteOpenHelper extends DaoMaster.OpenHelper {

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

        private static final String UPGRADE="upgrade";

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            MigrationHelper.migrate(db,UserDao.class, PeopleDao.class, ProductDao.class);
            Log.e("TAG" ,"upgrade run success");   //  TAG: upgrade run success
        }
    }




    /**
     * 適用于加密的數(shù)據(jù)庫
     */
    private static class MyEncryptedSQLiteOpenHelper extends DaoMaster.OpenHelper{

        public MyEncryptedSQLiteOpenHelper(Context context, String name) {
            super(context, name);
        }

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

        @Override
        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
            super.onUpgrade(db, oldVersion, newVersion);
            MigrationHelper.migrate(db , UserDao.class , PeopleDao.class , ProductDao.class);
            Log.e("TAG" , "update run success") ;
        }
    }
}
3>:需要拷貝 EncryptedMigrationHelper類到項(xiàng)目中,該類與 MigrationHelper類類似,只是將 android.database.sqlite.SQLiteDatabase 替換為 net.sqlcipher.database.SQLiteDatabase,然后修改了一小部分代碼,這個類的代碼就不貼了;

最后,一定不要忘記添加權(quán)限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />  
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />  
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、關(guān)于greenDAO greenDAO應(yīng)該算是當(dāng)前最火的數(shù)據(jù)庫開源框架了,它是一個將對象映射到SQLite數(shù)據(jù)...
    當(dāng)幸福來敲門58閱讀 14,034評論 3 19
  • 前言 最近的項(xiàng)目需要使用到數(shù)據(jù)庫,本來想用Sqlite數(shù)據(jù)來做的,但是聽同事說使用Greendao數(shù)據(jù)庫是真的好用...
    Smile__EveryDay閱讀 3,691評論 2 7
  • GreenDao 介紹:greenDAO是一個對象關(guān)系映射(ORM)的框架,能夠提供一個接口通過操作對象的方式去操...
    小董666閱讀 843評論 0 1
  • greenDAO greenDAO 是一個將對象映射到 SQLite 數(shù)據(jù)庫中的輕量且快速的 ORM 解決方案。它...
    蕉下孤客閱讀 16,232評論 18 104
  • (一)GreenDao簡介 GreenDao是一個對象關(guān)系映射(ORM)的開源框架,目前最主流的安卓數(shù)據(jù)庫操作框架...
    miss2008閱讀 5,546評論 4 18

友情鏈接更多精彩內(nèi)容