前言:
在Android开发中,或多或少总要接触SQLite。然而在使用它时,我们往往需要做许多额外的工作,像编写 SQL 语句与解析查询结果等。所以,适用于 Android 的ORM 框架也就孕育而生了,现在市面上主流的框架有 OrmLite、SugarORM、Active Android、Realm 与 GreenDAO。而greenDAO号称是速度最快的ORM(见官网)。
简单的讲,greenDAO 是一个将对象映射到 SQLite 数据库中的轻量且快速的 ORM 解决方案。
下面,我将详解地介绍如何在 Android Studio 上使用 greenDAO,并结合代码总结一些使用过程中的心得。
导入依赖和Schema设置:
导入依赖和Schema参数设置详细内容可参见官网和如下网址: http://www.jianshu.com/p/4e6d72e7f57a
我的Schema设置如下:
greendao{
schemaVersion 1
targetGenDir 'src/main/java'
}
一、数据库模型生成及读取操作:
1、greenDao支持的数据模型生成方式包括以下两种:
(1)编写greendaoGenerator的纯Java类库,以生成DaoMaster、DaoSession、bean和beanDao等;
可参见: http://www.open-open.com/lib/view/open1438065400878.html
注:greenDao 3.0版本以下只能采用这种方式。在升级3.0以后,因支持了注释方式,该方式不再推荐。
(2)利用注释的方式,生成
DaoMaster、DaoSession、bean和beanDao等;
2、下文仅就注释方式生成流程进行简单的介绍,详细的讲解可参见 http://www.jianshu.com/p/4e6d72e7f57a
首先,新建datamodel包,用以包含
DaoMaster、DaoSession、bean和beanDao等。
然后新建Area实体类,代码如下:
@Entity
public class Area {
@Id
private String AreaCode;
private String AreaName;
}
最后,Build->Make Module 'app',即可自动生成
DaoMaster、DaoSession、Area和AreaDao。此时Area实体类的代码如下:
@Entity
public class Area {
@Id
private String AreaCode;
private String AreaName;
@Generated(hash = 262290694)
public Area(String AreaCode, String AreaName) {
this.AreaCode = AreaCode;
this.AreaName = AreaName;
}
@Generated(hash = 179626505)
public Area() {
}
public String getAreaCode() {
return this.AreaCode;
}
public void setAreaCode(String AreaCode) {
this.AreaCode = AreaCode;
}
public String getAreaName() {
return this.AreaName;
}
public void setAreaName(String AreaName) {
this.AreaName = AreaName;
}
}
添加其他实体类的方法与Area一样。需要注意的是,不要手动修改
DaoMaster、DaoSession、bean和beanDao的代码,因为每一次编译项目,都会重新生成一次DaoMaster、DaoSession、bean和beanDao。如果修改的话,就会被覆盖掉。
为了便于数据的读取和添加,新建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()); //获取未加密的数据库
}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;
}
}
在读写数据库之前,要添加读写权限:
在MainActivity.java中添加读写代码:
public class MainActivity extends AppCompatActivity {
private TextView textview;
private DaoSession session;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textview=(TextView)findViewById(R.id.textview);
session = GreenDaoHelper.getDaoSession(this);
session.getAreaDao().deleteAll();//清空所有记录
//添加记录
Area area = new Area("01","北京");
Area area1 = new Area("02","天津");
session.getAreaDao().insert(area);
session.getAreaDao().insert(area1);
//查询记录
StringBuilder stringBuilder = new StringBuilder();
List areas = session.getAreaDao().loadAll();
for (int i = 0,n = areas.size();i
运行结果如下图所示:
二、修改数据库文件路径:
默认情况下,新创建的数据存储在data的包名目录下,设备如果不root的话,是无法查看SQLite数据库文件的。而实际应用中,我们往往需要copy数据库,或借用第三方工具查阅或编辑数据库内容。此时我们可以通过重写Context的
getDatabasePath(
String name
)
、
openOrCreateDatabase(String name,
int
mode, CursorFactory factory)
、
openOrCreateDatabase(String name,
int
mode, CursorFactory factory, DatabaseErrorHandler errorHandler)
等三个方法
来修改SQLite文件的存储路径。
通过查询资料,发现 http://blog.csdn.net/chenzhenlindx/article/details/39183691
中的内容基本符合我们的需求。但是博主是在DaoMaster中重写方法的。通过上文我们知道,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) {
/**
* 获得数据库路径,如果不存在,则创建对象对象
*
* @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";// 数据库所在目录
String dbPath = dbDir + "/" + name;// 数据库路径
// 判断目录是否存在,不存在则创建该目录
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(name);
}
}
/**
* 重载这个方法,是用来打开SD卡上的数据库的,android 2.3及以下会调用这个方法。
*
* @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会调用此方法获取数据库。
*
* @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()); //获取未加密的数据库
}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;
}
}
此时,再运行上述代码,就会在Android目录下发现我们的test.db文件。通过第三方工具,即可查看我们的数据库内容。下图是我用手机端的SqliteLookup工具查看到的数据库内容:
三、获取加密的数据库:
修改GreenDaoHelper.java,通过调用DaoMaster.OpenHelper类的getEncryptedWritableDb(password)或者
getEncryptedReadableDb(password)方法,
即可获取加密的数据库。
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"));//获取加密的数据库
//daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234"));//获取加密的数据库
//daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
}catch (Exception e){
e.printStackTrace();
}
}
return daoMaster;
}
此时,若要解密或重新加密数据库,可参考
博客《利用SQLCipher加解密数据库(包括加解密已有的数据库)》。
四、数据库升级又不删除数据:
在实际开发的过程中,数据库的结构可能会有所改变。而
使用 DevOpenHelper 每次升级数据库时,表都会删除重建。因此,实际使用中需要建立类继承 DaoMaster.OpenHelper,实现 onUpgrade()方法。通过查询资料,对未加密的数据库,推荐使用
升级辅助库 GreenDaoUpgradeHelper(
可参见 https://github.com/yuweiguocn/GreenDaoUpgradeHelper/blob/master/README_CH.md
)。该库通过 MigrationHelper在删表重建的过程中,使用临时表保存数据并还原。
示例程序
直接导入
MigrationHelper.java
源码。同时修改GreenDaoHelper.java文件,新建一个继承自
DaoMaster.OpenHelper的内部类MySQLiteOpenHelper。具体代码如下:
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"));//获取加密的数据库
//daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234"));//获取加密的数据库
daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
}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");
}
}
}
另外添加一个People实体类,并修改schemaVersion为更高的版本号,然后
Build->Make Module 'app',生成新的模型类。修改OnUpgrade方法如下:
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
MigrationHelper.migrate(db,AreaDao.class, PeopleDao.class);
Log.e(UPGRADE,"upgrade run success");
}
此时,运行程序。虽然程序成功启动,但是报如下错误:
12-30 10:02:12.503 6312-6312/com.wjk.greendaoexample E/SQLiteLog: (1) no such table: PEOPLE
12-30 10:02:12.508 6312-6312/com.wjk.greendaoexample E/MigrationHelper: 【Failed to generate temp table】PEOPLE_TEMP android.database.sqlite.SQLiteException: no such table: PEOPLE (code 1): , while compiling: CREATE TEMPORARY TABLE PEOPLE_TEMP AS SELECT * FROM PEOPLE;
通过阅读源码发现,程序根据传入的beanDao对
所有
bean表都创建了临时表,并从bean表复制数据到bean_temp表中。而此时,People实体是新创建的,数据库中并没有这个表,因此报上面的错误。此时,我们需对源码进行修改,仅对数据库中已有的表创建临时表并保存数据。此外,源码中是按字段恢复数据,为方便起见,本程序修改为全表查询恢复。代码如下:
public final class MigrationHelper {
public static boolean DEBUG = false;
private static String TAG = "MigrationHelper";
private static List tablenames = new ArrayList<>();
public static List getTables(SQLiteDatabase db){
List 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)){//如果数据库中没有该表,则继续下次循环
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,修改版本号,同时
修改OnUpgrade方法 ,
再次运行程序,则成功运行。
注意:MigrationHelper.migrate(),暂时只接收 SQLiteDatabase ,不接收 Database,且对
加密的
数据库是无效的。而实际应用中,由于数据的重要性,数据库往往是必须要加密的。因此,对于加密的
数据库该如何进行更新支持呢?
具体思路如下,首先分析
MigrationHelper.migrate()
为什么不支持
对
加密的
数据库的更新,然后再找出对应的解决方案。
(1)MigrationHelper.migrate()为什么不支持对加密的数据库的更新
通过加断点调试发现,数据库更新时并没有走MySQLiteOpenHelper的onUpgrade()方法,而是走的DatabaseOpenHelper抽象类里的EncryptedHelper内部类的
onUpgrade()方法。如图所示:
EncryptedHelper内部类的
onUpgrade()方法调用的是
DatabaseOpenHelper抽象类本身的
onUpgrade()方法,但DatabaseOpenHelper抽象类本身的
onUpgrade()方法默认是啥也不执行的,如下图所示。因此,
MigrationHelper.migrate()
为什么不支持
对
加密的
数据库的更新也就显而易见了。
(2)对加密的数据库的更新支持的解决方案
此时,可以通过修改greenDao的源码,在
EncryptedHelper内部类的
onUpgrade()方法中添加对
MigrationHelper.migrate()的调用。
但,如果我不想修改源码,该怎么解决呢?默认情况下,获取和更新加密的数据库,调用的是
DatabaseOpenHelper抽象类本身的getEncryptedWritableDb(String password)和
onUpgrade()方法
。此时,我们可以在GreenDaoHelper中定义一个新的类MyEncryptedSQLiteOpenHelper继承自DaoMaster.OpenHelper,并在这个类中对这两个方法进行重写,同时在内部自定义一个net.sqlcipher.database.SQLiteOpenHelper的继承类,以代替
DatabaseOpenHelper抽象类里的EncryptedHelper内部类。
在添加代码之前,要先添加对sqlcipher的依赖,如下:
compile 'net.zetetic:android-database-sqlcipher:3.5.4@aar'
具体代码如下:
public class GreenDaoHelper extends Application {
......
public static DaoMaster getDaoMaster(Context context) {
if (daoMaster == null) {
try{
ContextWrapper wrapper = new ContextWrapper(context) {
...
};
//DaoMaster.OpenHelper helper = new MySQLiteOpenHelper(wrapper,"test.db",null);
MyEncryptedSQLiteOpenHelper helper = new MyEncryptedSQLiteOpenHelper(wrapper,"test.db",null);
daoMaster = new DaoMaster(helper.getEncryptedWritableDb("1234"));//获取加密的数据库
//daoMaster = new DaoMaster(helper.getEncryptedReadableDb("1234"));//获取加密的数据库
//daoMaster = new DaoMaster(helper.getWritableDatabase()); //获取未加密的数据库
}catch (Exception e){
e.printStackTrace();
}
}
return daoMaster;
}
......
private static class MyEncryptedSQLiteOpenHelper extends DaoMaster.OpenHelper {
private final Context context;
private final String name;
private final int version = DaoMaster.SCHEMA_VERSION;
private boolean loadSQLCipherNativeLibs = true;
public MyEncryptedSQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
this.context=context;
this.name=name;
}
private static final String UPGRADE="upgrade";
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
EncryptedMigrationHelper.migrate((EncryptedDatabase) db,AreaDao.class, PeopleDao.class, ProductDao.class);
Log.e(UPGRADE,"upgrade run success");
}
@Override
public Database getEncryptedWritableDb(String password) {
MyEncryptedHelper encryptedHelper = new MyEncryptedHelper(context,name,version,loadSQLCipherNativeLibs);
return encryptedHelper.wrap(encryptedHelper.getReadableDatabase(password));
}
private class MyEncryptedHelper extends net.sqlcipher.database.SQLiteOpenHelper {
public MyEncryptedHelper(Context context, String name, int version, boolean loadLibs) {
super(context, name, null, version);
if (loadLibs) {
net.sqlcipher.database.SQLiteDatabase.loadLibs(context);
}
}
@Override
public void onCreate(net.sqlcipher.database.SQLiteDatabase db) {
MyEncryptedSQLiteOpenHelper.this.onCreate(wrap(db));
}
@Override
public void onUpgrade(net.sqlcipher.database.SQLiteDatabase db, int oldVersion, int newVersion) {
MyEncryptedSQLiteOpenHelper.this.onUpgrade(wrap(db), oldVersion, newVersion);
}
@Override
public void onOpen(net.sqlcipher.database.SQLiteDatabase db) {
MyEncryptedSQLiteOpenHelper.this.onOpen(wrap(db));
}
protected Database wrap(net.sqlcipher.database.SQLiteDatabase sqLiteDatabase) {
return new EncryptedDatabase(sqLiteDatabase);
}
}
}
}
上述代码中引用了EncryptedMigrationHelper.java类。该类与
MigrationHelper.java类似,只不过将android.database.sqlite.SQLiteDatabase替换为net.sqlcipher.database.SQLiteDatabase,同时对代码做了微小的改动。
EncryptedMigrationHelper.java类的代码如下:
public class EncryptedMigrationHelper {
public static boolean DEBUG = true;
private static String TAG = "UpgradeHelper";
private static List tablenames = new ArrayList<>();
public static List getTables(SQLiteDatabase db){
List 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(EncryptedDatabase db, Class extends AbstractDao, ?>>... daoClasses) {
Database database = db;
if (DEBUG) {
Log.d(TAG, "【Database Version】" + db.getSQLiteDatabase().getVersion());
Log.d(TAG, "【Generate temp table】start");
}
tablenames=getTables(db.getSQLiteDatabase());
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)){
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);
}
}
}
}
通过加断点调试发现,数据库更新时调用了MyEncryptedSQLiteOpenHelper的onUpgrade()方法
。如图所示:
至此,我们已能够对加密的数据库进行更新支持。
五、总结:
该博客对greenDao模型生成、增删改查、存储路径
修改
、加解密及更新升级等都有了较为详细的描述,在实际开发中也基本够用。不过,需要注意的是,本文引用的EncryptedMigrationHelper.java类和
MigrationHelper.java类均只支持对数据库对象的新增和删除,对于对象字段有修改的情况没做考虑。如果读者有需要的话,可另行开发。
参考文献如下:
官网: http://greenrobot.org/greendao/
github网站: https://github.com/greenrobot/greenDAO
greenDao Generator方式介绍: http://www.open-open.com/lib/view/open1438065400878.html
注释方式及增删改查详细介绍: http://www.jianshu.com/p/4e6d72e7f57a
greenDao数据库存储路径修改: http://blog.csdn.net/chenzhenlindx/article/details/39183691
greenDao数据库升级: https://github.com/yuweiguocn/GreenDaoUpgradeHelper
源码下载
我的github地址:https://github.com/WJKCharlie/GreenDaoExample