Android GreenDao数据库—基础详解
Android GreenDao数据库—高级详解
本章节讲述GreenDao数据库升级
在版本迭代时,我们经常需要对数据库进行升级,而GreenDAO默认的DaoMaster.DevOpenHelper在进行数据升级时,会把旧表删除,然后创建新表,并没有迁移旧数据到新表中,从而造成数据丢失。
1.代码说明
获取DevOpenHelper帮助类
//创建数据库shop.db 创建SQLite数据库的SQLiteOpenHelper的具体实现
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "greendaodemo.db", null);
DevOpenHelper 部分源码
public static class DevOpenHelper extends OpenHelper {
public DevOpenHelper(Context context, String name) {
super(context, name);
}
public DevOpenHelper(Context context, String name, CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
Log.i("greenDAO", "Upgrading schema from version " + oldVersion + " to " + newVersion + " by dropping all tables");
dropAllTables(db, true);
onCreate(db);
}
}
由上述部分源码可以看出 在onUpgrade方法中 默认的DevOpenHelper 类是直接将所有的表都删除了然后重新执行onCreate(db)方法创建新表。这在实际中是不可取的,因为我们在绝大数情况下是需要保留原始数据的。所以需要解决这一问题。
2.代码举例
版本为1时
greendao {
// 版本号
schemaVersion 1
//greendao输出dao的数据库操作实体类文件夹
daoPackage 'com.wjn.androiddbdemo.greendao'
//greenDao实体类包文件夹
targetGenDir 'src/main/java'
}
插入数据+查询数据
package com.wjn.androiddbdemo.activity.greendao;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import com.wjn.androiddbdemo.MyApplication;
import com.wjn.androiddbdemo.R;
import com.wjn.androiddbdemo.greendao.UserInfo;
import com.wjn.androiddbdemo.greendao.UserInfoDao;
import com.wjn.androiddbdemo.utils.ui.StatusBarUtil;
import org.greenrobot.greendao.query.QueryBuilder;
import java.util.ArrayList;
import java.util.List;
public class GreenDaoUpdateActivity extends AppCompatActivity implements View.OnClickListener {
private TextView textView1;
private TextView textView2;
private TextView textView3;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_greendaoupdate);
initView();
}
/**
* 初始化各种View
*/
private void initView() {
//根据状态栏颜色来决定 状态栏背景 用黑色还是白色 true:是否修改状态栏字体颜色
StatusBarUtil.setStatusBarMode(this, false, false, R.color.colorPrimary);
textView1 = findViewById(R.id.activity_greendaoupdate_textview1);
textView2 = findViewById(R.id.activity_greendaoupdate_textview2);
textView3 = findViewById(R.id.activity_greendaoupdate_textview3);
textView = findViewById(R.id.activity_greendaoupdate_textview);
textView1.setOnClickListener(this);
textView2.setOnClickListener(this);
textView3.setOnClickListener(this);
}
/**
* 各种点击事件的方法
*/
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.activity_greendaoupdate_textview1://插入数据
insertUser();
break;
case R.id.activity_greendaoupdate_textview2://查询数据
List list=queryUserList();
StringBuilder sbBuilder = new StringBuilder();
for(int i=0;i list=new ArrayList<>();
list.add(userInfo);
list.add(userInfo1);
list.add(userInfo2);
list.add(userInfo3);
UserInfoDao userInfoDao= MyApplication.getDaoInstant().getUserInfoDao();
userInfoDao.insertInTx(list);
}
/**
* 查询数据列表 姓名=“张三”
*/
public List queryUserList() {
UserInfoDao userInfoDao= MyApplication.getDaoInstant().getUserInfoDao();
QueryBuilder qb = userInfoDao.queryBuilder();
List list = qb.list();
return list;
}
}
结果
版本为2时
greendao {
// 版本号
schemaVersion 2
//greendao输出dao的数据库操作实体类文件夹
daoPackage 'com.wjn.androiddbdemo.greendao'
//greenDao实体类包文件夹
targetGenDir 'src/main/java'
}
再次查询数据结果
这就证明默认情况下,GreenDao数据库升级时会把原始数据删除。
解决方案
解决方案1:自己手动修改实体类和相应的DaoMaster.OpenHelper(DaoMaster.DevOpenHelper)类
由上可知,在GreenDao数据库升级时,要想保留原始数据要操作DaoMaster.OpenHelper(DaoMaster.DevOpenHelper)类的onUpgrade方法。由于GreenDao数据库的建表是按实体类创建的所以也要修改实体类。
修改前的实体类
package com.wjn.androiddbdemo.greendao;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Id;
@Entity
public class UserInfo {
@Id(autoincrement = true)
private Long id;//主键 Long型,可以通过@Id(autoincrement = true)设置自增长
private String name;
private String age;
@Generated(hash = 752529704)
public UserInfo(Long id, String name, String age) {
this.id = id;
this.name = name;
this.age = age;
}
@Generated(hash = 1279772520)
public UserInfo() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return this.age;
}
public void setAge(String age) {
this.age = age;
}
}
添加或删除完实体类属性后 将原本自动生成的构造方法以及getter/setter方法删除,重新Build—>Make Project进行生成。
修改后的实体类
package com.wjn.androiddbdemo.greendao;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Id;
@Entity
public class UserInfo {
@Id(autoincrement = true)
private Long id;//主键 Long型,可以通过@Id(autoincrement = true)设置自增长
private String name;
private String age;
private String describe;
@Generated(hash = 819903449)
public UserInfo(Long id, String name, String age, String describe) {
this.id = id;
this.name = name;
this.age = age;
this.describe = describe;
}
@Generated(hash = 1279772520)
public UserInfo() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return this.age;
}
public void setAge(String age) {
this.age = age;
}
public String getDescribe() {
return this.describe;
}
public void setDescribe(String describe) {
this.describe = describe;
}
}
即 添加了一个describe字段。
在表实体中,调整其中的变量(表字段),一般就是新增/删除/修改字段。注意:
新增的字段或修改的字段,其变量类型应使用基础数据类型的包装类,如使用Integer而不是int,避免升级过程中报错。
自定义DaoMaster.OpenHelper
package com.wjn.androiddbdemo.utils.db;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import com.wjn.androiddbdemo.greendao.DaoMaster;
import org.greenrobot.greendao.database.Database;
public class MyGreenDaoOpenHelper extends DaoMaster.OpenHelper {
/**
* 构造方法
* */
public MyGreenDaoOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
/**
* onUpgrade方法
* 将老表的数据复制到新表
* */
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
super.onUpgrade(db, oldVersion, newVersion);
//1.将旧表改名成临时表
String sql1 = "ALTER TABLE USER_INFO RENAME TO _USER_INFO";
//2.创建新表
String sql2=" CREATE TABLE USER_INFO (_id INTEGER PRIMARY KEY AUTOINCREMENT,NAME TEXT,AGE TEXT,DESCRIBE TEXT);";
//3.将临时表的数据导入到新表 原表中没有的要自己设个默认值
String sql3="INSERT INTO USER_INFO SELECT _id,NAME,AGE,\"这是描述\" FROM _USER_INFO";
//删除临时表
String sql4="DROP TABLE _USER_INFO";
//执行SQL语句
db.execSQL(sql1);
db.execSQL(sql2);
db.execSQL(sql3);
db.execSQL(sql4);
}
}
注意:
这里和SQLite数据库升级一样,要判断版本问题(比如从版本1直接到版本3,中间没有版本2的某个实体类)。详见
Android SQLite数据库升级
修改程序入口初始化Helper类的方式
/**
* 配置数据库
*/
private void setupDatabase() {
//创建数据库shop.db 创建SQLite数据库的SQLiteOpenHelper的具体实现
// DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "greendaodemo.db", null);
MyGreenDaoOpenHelper helper=new MyGreenDaoOpenHelper(this,"greendaodemo.db",null);
//获取SQLiteDatabase对象
SQLiteDatabase db = helper.getReadableDatabase();
//获取数据库对象
DaoMaster daoMaster = new DaoMaster(db);
//获取dao对象管理者
daoSession = daoMaster.newSession();
}
即
将
DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "greendaodemo.db", null);
换成自定义的
MyGreenDaoOpenHelper helper=new MyGreenDaoOpenHelper(this,"greendaodemo.db",null);
然后再将版本从1——>2 再次查询
package com.wjn.androiddbdemo.activity.greendao;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import com.wjn.androiddbdemo.MyApplication;
import com.wjn.androiddbdemo.R;
import com.wjn.androiddbdemo.greendao.UserInfo;
import com.wjn.androiddbdemo.greendao.UserInfoDao;
import com.wjn.androiddbdemo.utils.ui.StatusBarUtil;
import org.greenrobot.greendao.query.QueryBuilder;
import java.util.ArrayList;
import java.util.List;
public class GreenDaoUpdateActivity extends AppCompatActivity implements View.OnClickListener {
private TextView textView1;
private TextView textView2;
private TextView textView3;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_greendaoupdate);
initView();
}
/**
* 初始化各种View
*/
private void initView() {
//根据状态栏颜色来决定 状态栏背景 用黑色还是白色 true:是否修改状态栏字体颜色
StatusBarUtil.setStatusBarMode(this, false, false, R.color.colorPrimary);
textView1 = findViewById(R.id.activity_greendaoupdate_textview1);
textView2 = findViewById(R.id.activity_greendaoupdate_textview2);
textView3 = findViewById(R.id.activity_greendaoupdate_textview3);
textView = findViewById(R.id.activity_greendaoupdate_textview);
textView1.setOnClickListener(this);
textView2.setOnClickListener(this);
textView3.setOnClickListener(this);
}
/**
* 各种点击事件的方法
*/
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.activity_greendaoupdate_textview1://插入数据
insertUser();
break;
case R.id.activity_greendaoupdate_textview2://查询数据
List list=queryUserList();
StringBuilder sbBuilder = new StringBuilder();
for(int i=0;i lists=queryUserList();
StringBuilder sbBuilders = new StringBuilder();
for(int i=0;i list=new ArrayList<>();
list.add(userInfo);
list.add(userInfo1);
list.add(userInfo2);
list.add(userInfo3);
UserInfoDao userInfoDao= MyApplication.getDaoInstant().getUserInfoDao();
userInfoDao.insertInTx(list);
}
/**
* 查询数据列表 姓名=“张三”
*/
public List queryUserList() {
UserInfoDao userInfoDao= MyApplication.getDaoInstant().getUserInfoDao();
QueryBuilder qb = userInfoDao.queryBuilder();
List list = qb.list();
return list;
}
}
结果
解决方案2:GitHub上的帮助类
Gradle添加依赖
根Gradle
allprojects {
repositories {
google()
jcenter()
maven { url "https://jitpack.io" }//GreenDao数据库升级
}
}
APP Gradle
//GreenDao数据库升级
implementation 'com.github.yuweiguocn:GreenDaoUpgradeHelper:v2.1.0'
帮助类
package com.wjn.androiddbdemo.utils.db;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import com.github.yuweiguocn.library.greendao.MigrationHelper;
import com.wjn.androiddbdemo.greendao.DaoMaster;
import com.wjn.androiddbdemo.greendao.UserInfoDao;
import org.greenrobot.greendao.database.Database;
public class MyGitHubGreenDaoOpenHelper extends DaoMaster.OpenHelper{
/**
* 构造方法
* */
public MyGitHubGreenDaoOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
/**
* onUpgrade方法
* 使用GitHub MigrationHelper 将老表的数据复制到新表
* */
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
super.onUpgrade(db, oldVersion, newVersion);
//把需要管理的数据库表DAO作为最后一个参数传入到方法中
MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {
@Override
public void onCreateAllTables(Database db, boolean ifNotExists) {
DaoMaster.createAllTables(db, ifNotExists);
}
@Override
public void onDropAllTables(Database db, boolean ifExists) {
DaoMaster.dropAllTables(db, ifExists);
}
}, UserInfoDao.class);
}
}
注意:
在表实体中,调整其中的变量(表字段),一般就是新增/删除/修改字段。注意:
1)新增的字段或修改的字段,其变量类型应使用基础数据类型的包装类,如使用Integer而不是int,避免升级过程中报错。
2)根据MigrationHelper中的代码,升级后,新增的字段和修改的字段,都会默认被赋予null值。
APP入口替换
/**
* 配置数据库
*/
private void setupDatabase() {
//创建数据库shop.db 创建SQLite数据库的SQLiteOpenHelper的具体实现
// DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "greendaodemo.db", null);
// MyGreenDaoOpenHelper helper=new MyGreenDaoOpenHelper(this,"greendaodemo.db",null);
MyGitHubGreenDaoOpenHelper helper=new MyGitHubGreenDaoOpenHelper(this,"greendaodemo.db",null);
//获取SQLiteDatabase对象
SQLiteDatabase db = helper.getReadableDatabase();
//获取数据库对象
DaoMaster daoMaster = new DaoMaster(db);
//获取dao对象管理者
daoSession = daoMaster.newSession();
}
实体类添加字段
将原本自动生成的构造方法以及getter/setter方法删除,重新Build—>Make Project进行生成。
package com.wjn.androiddbdemo.greendao;
import org.greenrobot.greendao.annotation.Entity;
import org.greenrobot.greendao.annotation.Generated;
import org.greenrobot.greendao.annotation.Id;
@Entity
public class UserInfo {
@Id(autoincrement = true)
private Long id;//主键 Long型,可以通过@Id(autoincrement = true)设置自增长
private String name;
private String age;
private String githubdes;
@Generated(hash = 466573838)
public UserInfo(Long id, String name, String age, String githubdes) {
this.id = id;
this.name = name;
this.age = age;
this.githubdes = githubdes;
}
@Generated(hash = 1279772520)
public UserInfo() {
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return this.age;
}
public void setAge(String age) {
this.age = age;
}
public String getGithubdes() {
return this.githubdes;
}
public void setGithubdes(String githubdes) {
this.githubdes = githubdes;
}
}
然后再将版本从1——>2 再次查询
package com.wjn.androiddbdemo.activity.greendao;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.TextView;
import com.wjn.androiddbdemo.MyApplication;
import com.wjn.androiddbdemo.R;
import com.wjn.androiddbdemo.greendao.UserInfo;
import com.wjn.androiddbdemo.greendao.UserInfoDao;
import com.wjn.androiddbdemo.utils.ui.StatusBarUtil;
import org.greenrobot.greendao.query.QueryBuilder;
import java.util.ArrayList;
import java.util.List;
public class GreenDaoUpdateActivity extends AppCompatActivity implements View.OnClickListener {
private TextView textView1;
private TextView textView2;
private TextView textView3;
private TextView textView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_greendaoupdate);
initView();
}
/**
* 初始化各种View
*/
private void initView() {
//根据状态栏颜色来决定 状态栏背景 用黑色还是白色 true:是否修改状态栏字体颜色
StatusBarUtil.setStatusBarMode(this, false, false, R.color.colorPrimary);
textView1 = findViewById(R.id.activity_greendaoupdate_textview1);
textView2 = findViewById(R.id.activity_greendaoupdate_textview2);
textView3 = findViewById(R.id.activity_greendaoupdate_textview3);
textView = findViewById(R.id.activity_greendaoupdate_textview);
textView1.setOnClickListener(this);
textView2.setOnClickListener(this);
textView3.setOnClickListener(this);
}
/**
* 各种点击事件的方法
*/
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.activity_greendaoupdate_textview1://插入数据
insertUser();
break;
case R.id.activity_greendaoupdate_textview2://查询数据
List list=queryUserList();
StringBuilder sbBuilder = new StringBuilder();
for(int i=0;i lists=queryUserList();
StringBuilder sbBuilders = new StringBuilder();
for(int i=0;i list=new ArrayList<>();
list.add(userInfo);
list.add(userInfo1);
list.add(userInfo2);
list.add(userInfo3);
UserInfoDao userInfoDao= MyApplication.getDaoInstant().getUserInfoDao();
userInfoDao.insertInTx(list);
}
/**
* 查询数据列表 姓名=“张三”
*/
public List queryUserList() {
UserInfoDao userInfoDao= MyApplication.getDaoInstant().getUserInfoDao();
QueryBuilder qb = userInfoDao.queryBuilder();
List list = qb.list();
return list;
}
}
结果
MigrationHelper 源码解析
由上可知 在onUpgrade方法中调用了MigrationHelper.migrate()方法
MigrationHelper.migrate(db, new MigrationHelper.ReCreateAllTableListener() {
@Override
public void onCreateAllTables(Database db, boolean ifNotExists) {
DaoMaster.createAllTables(db, ifNotExists);
}
@Override
public void onDropAllTables(Database db, boolean ifExists) {
DaoMaster.dropAllTables(db, ifExists);
}
}, UserInfoDao.class);
也就是说 升级数据库的操作是在这个方法中。
public static void migrate(Database database, ReCreateAllTableListener listener, Class extends AbstractDao, ?>>... daoClasses) {
weakListener = new WeakReference<>(listener);
migrate(database, daoClasses);
}
public static void migrate(Database database, Class extends AbstractDao, ?>>... daoClasses) {
printLog("【Generate temp table】start");
generateTempTables(database, daoClasses);
printLog("【Generate temp table】complete");
ReCreateAllTableListener listener = null;
if (weakListener != null) {
listener = weakListener.get();
}
if (listener != null) {
listener.onDropAllTables(database, true);
printLog("【Drop all table by listener】");
listener.onCreateAllTables(database, false);
printLog("【Create all table by listener】");
} else {
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 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 newTableInfos = TableInfo.getTableInfo(db, tableName);
List tempTableInfos = TableInfo.getTableInfo(db, tempTableName);
ArrayList selectColumns = new ArrayList<>(newTableInfos.size());
ArrayList intoColumns = new ArrayList<>(newTableInfos.size());
for (TableInfo tableInfo : tempTableInfos) {
if (newTableInfos.contains(tableInfo)) {
String column = '`' + tableInfo.name + '`';
intoColumns.add(column);
selectColumns.add(column);
}
}
// NOT NULL columns list
for (TableInfo tableInfo : newTableInfos) {
if (tableInfo.notnull && !tempTableInfos.contains(tableInfo)) {
String column = '`' + tableInfo.name + '`';
intoColumns.add(column);
String value;
if (tableInfo.dfltValue != null) {
value = "'" + tableInfo.dfltValue + "' AS ";
} else {
value = "'' AS ";
}
selectColumns.add(value + column);
}
}
if (intoColumns.size() != 0) {
StringBuilder insertTableStringBuilder = new StringBuilder();
insertTableStringBuilder.append("REPLACE INTO ").append(tableName).append(" (");
insertTableStringBuilder.append(TextUtils.join(",", intoColumns));
insertTableStringBuilder.append(") SELECT ");
insertTableStringBuilder.append(TextUtils.join(",", selectColumns));
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);
}
}
}
可以看出,其实它也是创建临时表,然后创建新表,再将临时表中数据拷贝到新表。最后删除临时表。
代码链接:https://github.com/wujianning/AndroidDBDemo
附:
GitHub链接:
https://github.com/yuweiguocn/GreenDaoUpgradeHelper