GreenDao设置数据库路径以及数据库升级

来源:互联网 发布:java list 对象去重 编辑:程序博客网 时间:2024/05/02 04:19

1. APP开发期间的数据库

在上一篇文章中封装了GreenDaoManager中有个构造方法如下:

private GreenDaoManager(){        if (mInstance == null) {//            DaoMaster.DevOpenHelper devOpenHelper = new//                    DaoMaster.DevOpenHelper(MyApplication.getContext(), "database_name", null);//此处openhelper为自动生成开发所使用,发布版本需自定义            MySQLiteOpenHelper devOpenHelper = new                    MySQLiteOpenHelper(new GreenDaoContext(), "database_name.db", null);//GreenDaoContext为创建数据库路径使用            mDaoMaster = new DaoMaster(devOpenHelper.getWritableDatabase());            mDaoSession = mDaoMaster.newSession();        }    }

其中注释掉的两行代码中:

DaoMaster.DevOpenHelper devOpenHelper = newDaoMaster.DevOpenHelper(MyApplication.getContext(), "database_name", null);//此处DevOpenHelper 为自动生成开发所使用,发布版本需自定义

其中的MyApplication.getContext()上下文表示了数据库存储路径为手机内存。这里的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.
数据库升级的话,会删除所有表,然后重新创建。这种方式在开发期间,APP还没有上线之前是可以的。

当APP上线后,我们不能使用这种方式,因为这样会导致已经存在的数据会被删除。

2. APP上线后,数据库升级

我们需要重写一个类MySQLiteOpenHelper实现OpenHelper.
1. 需要自己实现了onUpgrade方法来自定义升级过程。
2. 当然升级过程中也要修改DaoMaster.SCHEMA_VERSION
3. 当DaoMaster.SCHEMA_VERSION跟你当前数据库的版本比较后,会根据你当前数据库的版本,然后进行升级。
4. 关键代码onUpgrade方法,会比较新数据库和旧数据库的版本,然后执行相应的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) {        //数据迁移模块        MigrationHelper.migrate(db,                UserDao.class,                ProfessionDao.class,                LTestDao.class);    }}

数据库升级不外乎两种情况:
  1.修改表结构。
  2.添加新表。

针对onUpgrade中的实现方法可以采用两种不同策略:(稍后都会提供两种策略的代码实现,下面主要讲述第二种数据迁移实现方式)
 1.逐级版本迭代升级,比如当前版本为1,最新版本为3,此方案就是先从1–>2–>3.
 2.把数据库中的数据备份,然后全删数据库,再重新创建新的数据库,把备份数据导入。此方案直接从1–>3。

3. 数据迁移(数据库备份)的具体实现

  1. android上面对的数据库基本上就是轻量级数据库sqlite,首先我们需要了解一些后面需要用到的数据库操作语句:
    • 判断sqlite表是否存在
      SELECT * FROM sqlite_master WHERE type='table' AND name='tempTableName';
    • sqlite判断临时表是否存在
      SELECT * FROM sqlite_temp_master WHERE type='table' AND name='tempTableName';
    • sqlite创建表tableName的临时表
      create temporary table tempTableName as select * from tableName;
    • 删除临时表
      drop table tempTableName;
  2. sqlite的系统表sqlite_master、临时表sqlite_temp_master
    SQLite数据库中一个特殊的名叫 SQLITE_MASTER上执行一个SELECT查询以获得所有表的索引。每一个 SQLite 数据库都有一个叫 SQLITE_MASTER的表, 它定义数据库的模式。 SQLITE_MASTER表看起来如下:
    CREATE TABLE sqlite_master (
    type TEXT,
    name TEXT,
    tbl_name TEXT,
    rootpage INTEGER,
    sql TEXT
    );

    type 字段永远是 ‘table’,name 字段永远是对应表的名字。所以,要获得数据库中所有表的列表, 使用下列SELECT语句:
    SELECT name FROM sqlite_master
    WHERE type=’table’
    ORDER BY name;

    对于索引,type 等于 ‘index’, name 则是索引的名字,tbl_name 是该索引所属的表的名字。 不管是表还是索引,sql 字段是原先用 CREATE TABLE 或 CREATE INDEX 语句创建它们时的命令文本。对于自动创建的索引(用来实现 PRIMARY KEY 或 UNIQUE 约束),sql字段为NULL。
    SQLITE_MASTER 表是只读的。不能对它使用 UPDATE、INSERT 或 DELETE。 它会被 CREATE TABLE、CREATE INDEX、DROP TABLE 和 DROP INDEX 命令自动更新。
    临时表及其索引和触发器存放在另外一个叫 SQLITE_TEMP_MASTER 的表中。SQLITE_TEMP_MASTER 跟 SQLITE_MASTER 差不多, 但它只是对于创建那些临时表的应用可见。如果要获得所有表的列表, 不管是永久的还是临时的,可以使用类似下面的命令:
    SELECT name FROM
    (SELECT * FROM sqlite_master UNION ALL
    SELECT * FROM sqlite_temp_master)
    WHERE type=’table’
    ORDER BY name

  3. 数据迁移的工具类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)) {//不存在系统表中,表明是新增表,不需要创建临时表                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. 设置数据库路径

在第一模块中有句代码:
MySQLiteOpenHelper devOpenHelper = new MySQLiteOpenHelper(new GreenDaoContext(), "database_name.db", null);//GreenDaoContext为创建数据库路径使用
里面有个上下文GreenDaoContext继承了ContextWrapper,里面设置了数据库路径,代码如下:

public class GreenDaoContext  extends ContextWrapper {    private String currentUserId = "greendao";//一般用来针对一个用户一个数据库,以免数据混乱问题    private Context mContext;    public GreenDaoContext() {        super(MyApplication.getContext());        this.mContext = MyApplication.getContext();//        this.currentUserId = "greendao";//初始化    }    /**     * 获得数据库路径,如果不存在,则创建对象     *     * @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);        // 目录不存在则自动创建目录        if (!baseFile.exists()){            baseFile.mkdirs();        }        StringBuffer buffer = new StringBuffer();        buffer.append(baseFile.getPath());        buffer.append(File.separator);        buffer.append(currentUserId);        dbDir = buffer.toString();// 数据库所在目录        buffer.append(File.separator);//        buffer.append(dbName+"_"+currentUserId);//也可以采用此种方式,将用户id与表名联系到一块命名        buffer.append(dbName);        String dbPath = buffer.toString();// 数据库路径        // 判断目录是否存在,不存在则创建该目录        File dirFile = new File(dbDir);        if (!dirFile.exists()){            dirFile.mkdirs();        }        // 数据库文件是否创建成功        boolean isFileCreateSuccess = false;        // 判断文件是否存在,不存在则创建该文件        File dbFile = new File(dbPath);        if (!dbFile.exists()) {            try {                isFileCreateSuccess = dbFile.createNewFile();// 创建文件            } catch (IOException e) {                e.printStackTrace();            }        } else            isFileCreateSuccess = true;        // 返回数据库文件对象        if (isFileCreateSuccess)            return dbFile;        else            return super.getDatabasePath(dbName);    }    /**     * 重载这个方法,是用来打开SD卡上的数据库的,android 2.3及以下会调用这个方法。     *     * @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会调用此方法获取数据库。     *     * @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,先到这了。。。

源码奉上,自行参考

0 0
原创粉丝点击