GreenDao设置数据库路径以及数据库升级
2016-12-06 20:10
495 查看
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 = new DaoMaster.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. 数据迁移(数据库备份)的具体实现
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;
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
数据迁移的工具类
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,先到这了。。。
源码奉上,自行参考
相关文章推荐
- SQL相关工具 集
- MongoDB基本操作
- Oracle使用sqluldr2快速导出数据
- mysql 初始化
- ORACLE分组查询和统计等
- Redis的持久化机制
- MongoDB
- 使用POWERDESIGNER设计数据库的20条技巧(转)
- sql like N'%...%' 在C#里的写法
- xtraBackup 备份mysql
- 结合MongoDB开发LBS应用
- MYSQL存储过程:批量更新数据2(产品品牌)
- MYSQL存储过程:批量更新数据
- Head First SQL 总结
- WINDOWS计划任务设置MYSQL自动备份
- install source mysql 5.7.9
- CentOS安装mariadb-10.0.12和php-5.5.15
- MYSQL导出CSV的2种格式
- CentOS安装mariadb
- install source mysql 5.6.13