目录:
前言
导入依赖和 Schema 设置
一、数据库模型生成及读取操作
二、修改数据库文件路径
三、获取加密的数据库
四、数据库升级又不删除数据
五、总结
前言:
在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](http://www.jianshu.com/p/4e6d72e7f57a)
** **我的 Schema 设置如下:
[plain] view plain copy
- 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](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实体类,代码如下:
[java] view plain copy
-
@Entity
-
public class Area {
-
@Id
-
private String AreaCode;
-
private String AreaName;
-
}
最后,Build->Make Module 'app',即可自动生成 DaoMaster、DaoSession、Area 和 AreaDao。此时 Area 实体类的代码如下:
[java] view plain copy
-
@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 辅助类,代码如下:
[java] view plain copy
-
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;
-
}
-
}
在读写数据库之前,要添加读写权限:
[html] view plain copy
-
在 MainActivity.java 中添加读写代码:
[java] view plain copy
-
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
-
stringBuilder.append("地区编码:").append(areas.get(i).getAreaCode())
-
.append(",地区名称:").append(areas.get(i).getAreaName()).append("\n");
-
}
-
textview.setText(stringBuilder);
-
}
-
}
运行结果如下图所示:
![](http://img.blog.csdn.net/20161230145836894)
二、修改数据库文件路径:
默认情况下,新创建的数据存储在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](http://blog.csdn.net/chenzhenlindx/article/details/39183691)中的内容基本符合我们的需求。但是博主是在DaoMaster中重写方法的。通过上文我们知道,DaoMaster的代码是不能修改的。因此,我们可以将重写的方法放到GreenDaoHelper中去。代码如下:
[java] view plain copy
- 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)方法,即可获取加密的数据库。
[java] view plain copy
-
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](https://github.com/yuweiguocn/GreenDaoUpgradeHelper/blob/master/README_CH.md))。该库通过 MigrationHelper在删表重建的过程中,使用临时表保存数据并还原。
示例程序直接导入MigrationHelper.java源码。同时修改GreenDaoHelper.java文件,新建一个继承自DaoMaster.OpenHelper的内部类MySQLiteOpenHelper。具体代码如下:
[java] view plain copy
-
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 方法如下:
[java] view plain copy
-
@Override
-
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-
MigrationHelper.migrate(db,AreaDao.class, PeopleDao.class);
-
Log.e(UPGRADE,"upgrade run success");
-
}
此时,运行程序。虽然程序成功启动,但是报如下错误:
[java] view plain copy
-
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 实体是新创建的,数据库中并没有这个表,因此报上面的错误。此时,我们需对源码进行修改,仅对数据库中已有的表创建临时表并保存数据。此外,源码中是按字段恢复数据,为方便起见,本程序修改为全表查询恢复。代码如下:
[java] view plain copy
- 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, Classextends 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, Classextends 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 Classextends AbstractDao>... daoClasses) {
-
reflectMethod(db, "dropTable", ifExists, daoClasses);
-
if (DEBUG) {
-
Log.d(TAG, "【Drop all table】");
-
}
-
}
-
private static void createAllTables(Database db, boolean ifNotExists, @NonNull Classextends 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 Classextends 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, Classextends 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的依赖,如下:
[html] view plain copy
-
compile 'net.zetetic:android-database-sqlcipher:3.5.4@aar'
具体代码如下:
[java] view plain copy
-
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 类的代码如下:
[java] view plain copy
- 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, Classextends 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, Classextends 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 Classextends AbstractDao>... daoClasses) {
-
reflectMethod(db, "dropTable", ifExists, daoClasses);
-
if (DEBUG) {
-
Log.d(TAG, "【Drop all table】");
-
}
-
}
-
private static void createAllTables(Database db, boolean ifNotExists, @NonNull Classextends 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 Classextends 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, Classextends 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/](http://greenrobot.org/greendao/)
github网站:[https://github.com/greenrobot/greenDAO](https://github.com/greenrobot/greenDAO)
greenDao Generator方式介绍:[http://www.open-open.com/lib/view/open1438065400878.html](http://www.open-open.com/lib/view/open1438065400878.html)
注释方式及增删改查详细介绍:[http://www.jianshu.com/p/4e6d72e7f57a](http://www.jianshu.com/p/4e6d72e7f57a)
greenDao数据库存储路径修改:[http://blog.csdn.net/chenzhenlindx/article/details/39183691](http://blog.csdn.net/chenzhenlindx/article/details/39183691)
greenDao数据库升级:[https://github.com/yuweiguocn/GreenDaoUpgradeHelper](https://github.com/yuweiguocn/GreenDaoUpgradeHelper)
**我的 github 地址:**https://github.com/WJKCharlie/GreenDaoExample
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于