本博文旨在记录greenDao3.0的基本用法以及基于greenDao3.0的数据库的升级策略。本文基于Window10下Android Studio环境操作使用。废话不多说,直入正题。
greenDao是一个对象关系映射(ORM)的框架,能够提供一个接口通过操作对象的方式去操作关系型数据库,它能够让你操作数据库时更简单、更方便。如下图来之其官网的图片:
官网地址:http://greenrobot.org/greendao/
github地址:https://github.com/greenrobot/greenDAO
关于greenDao3.0之前的2.0、1.0版本,本人没有接触(现在似乎也没必要再去了解)过。据网上大神介绍,3.0之前需要通过新建GreenDaoGenerator工程生成Java数据对象(实体)和DAO对象,非常的繁琐而且也加大了使用成本。而GreenDao3.0最大的变化就是采用注解的方式通过编译方式生成Java数据对象和DAO对象。
greenDao3.0主要有以下特点:
· 1.性能高,号称Android最快的关系型数据库
2.内存占用小
· 3.库文件比较小,小于100K,编译时间低,而且可以避免65K方法限制
· 4.支持数据库加密 greendao支持SQLCipher进行数据库加密
5.简洁易用的API
在项目的build.gradle添加如下配置:
dependencies {
classpath 'org.greenrobot:greendao-gradle-plugin:3.0.0'
}
这里添加的是greenDao插件路径。
在主工程下的build.gradle下添加如下配置:
apply plugin: 'org.greenrobot.greendao'
dependencies {
compile 'org.greenrobot:greendao:3.0.1'
}
如以上配置所示,greenDao3.0使用需要依赖greenDao插件配合使用。完成这些基本配置后我们同步编译(Sync Project),没毛病,接着往下。
现在我们就可以使用greenDao框架了,新建实体类:
@Entity
public class User {
@Id(autoincrement = true)
private Long id;
private String name;
private int age;
...
}
注意:因为我们要使用greenDao3.0框架,所以我们的实体类要添加greenDao3.0的注解@Entity,表示该实体类的相关数据要添加到数据库中。此外,我们还对实体类中id字段添加了注解@Id(autoincrement = true) 表示将该字段设为主键并自动增长,需要注意的是,主键是Long型,不是long。
此外,定义实体类时对于字段还可以添加以下注解:
再次同步编译,编译完成后,我们可以发现,User类中已经自动给我们添加了字段对应的setter、getter方法以及构造函数:
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Long getId() {
return this.id;
}
public void setId(Long id) {
this.id = id;
}
@Generated(hash = 1309193360)
public User(Long id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Generated(hash = 586692638)
public User() {
}
这些都是greenDao3.0插件为我们自动生成的代码。此外,我们还可以在app/build/generated/source目录下新增了一个greendao目录,里面有三个java文件分别是DaoMaster、DaoSession、UserDao。没错,这些文件也是greenDao3.0插件为我们自动生成的。这些类就是我们后面操作数据库主要依赖的类。
那么问题来了,如果我想让这些文件放到其它目录下该怎么办?good question!
这时我们就可以在主工程下的build.gradle添加相应的辅助配置(比如我想将这些文件放在src/mian/java目录下):
greendao {
targetGenDir 'src/main/java'
}
这时,我们重新编译,就会发现这些文件已经转移到这个目录下了。
这里的greendao,我们还可以添加下面一些数据库的基本属性配置
借助上面生成的DaoMaster、DaoSession、UserDao三个类,我们可以创建一个数据库工具类DBManager
-------------------------------------------------------明天接着写
。。。一天后,继续……
-------------------------------------------------------
首先明确我们这个工具类的功能。很明显,对于数据库的操作,无非就是增、删、改、查,也就是说,我们要在DBManager这个类里实现实现数据库增删改查的功能。
1.声明一个数据库管理者DBManager单例
public class DBManager {
private static final String dbName = "my_test_db";
private static DBManager mInstance;
private DaoMaster.DevOpenHelper mOpenHelper;
private Context mContext;
public DBManager(Context context){
this.mContext = context;
mOpenHelper = new DaoMaster.DevOpenHelper(mContext,dbName,null);
}
/**
* 获取单例引用
*
* @param context
* @return
*/
public static DBManager getInstance(Context context) {
if (mInstance == null){
synchronized (DBManager.class){
if (mInstance == null){
mInstance = new DBManager(context);
}
}
}
return mInstance;
}
}
2.获取可写、可读数据库
/**
* 获取可读数据库
* @return
*/
private SQLiteDatabase getReadableDatabase(){
if (mOpenHelper == null){
mOpenHelper = new DaoMaster.DevOpenHelper(mContext,dbName,null);
}
SQLiteDatabase db = mOpenHelper.getReadableDatabase();
return db;
}
/**
* 获取可写数据库
* @return
*/
private SQLiteDatabase getWritableDatabase(){
if (mOpenHelper == null){
mOpenHelper = new DaoMaster.DevOpenHelper(mContext,dbName,null);
}
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
return db;
}
3.增删改查系列方法
通过greenDao插件给我们自动生成的这三个工具类,我们可以很轻易的实现增删改查系列方法
/**
* 插入一条记录
* @param user
*/
public void insertUser(User user){
DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
DaoSession daoSession = daoMaster.newSession();
UserDao userDao = daoSession.getUserDao();
userDao.insert(user);
}
/**
* 插入用户集合
*
* @param users
*/
public void insertUserList(List users) {
if (users == null || users.isEmpty()) {
return;
}
DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
DaoSession daoSession = daoMaster.newSession();
UserDao userDao = daoSession.getUserDao();
userDao.insertInTx(users);
}
/**
* 删除一条记录
*
* @param user
*/
public void deleteUser(User user) {
DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
DaoSession daoSession = daoMaster.newSession();
UserDao userDao = daoSession.getUserDao();
userDao.delete(user);
}
/**
* 清空所有记录
*/
public void clearUser(){
DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
DaoSession daoSession = daoMaster.newSession();
UserDao userDao = daoSession.getUserDao();
userDao.deleteAll();
}
/**
* 批量删除
* @param list
*/
public void deleteUsers(List list){
DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
DaoSession daoSession = daoMaster.newSession();
UserDao userDao = daoSession.getUserDao();
userDao.deleteInTx(list);
}
/**
* 更新一条记录
*
* @param user
*/
public void updateUser(User user) {
DaoMaster daoMaster = new DaoMaster(getWritableDatabase());
DaoSession daoSession = daoMaster.newSession();
UserDao userDao = daoSession.getUserDao();
userDao.update(user);
}
/**
* 查询用户列表
*/
public List queryUserList() {
DaoMaster daoMaster = new DaoMaster(getReadableDatabase());
DaoSession daoSession = daoMaster.newSession();
UserDao userDao = daoSession.getUserDao();
QueryBuilder qb = userDao.queryBuilder();
List list = qb.list();
return list;
}
/**
* 根据条件查询用户列表
*/
public List queryUserList(int age) {
DaoMaster daoMaster = new DaoMaster(getReadableDatabase());
DaoSession daoSession = daoMaster.newSession();
UserDao userDao = daoSession.getUserDao();
QueryBuilder qb = userDao.queryBuilder();
qb.where(UserDao.Properties.Age.gt(age)).orderAsc(UserDao.Properties.Age);
List list = qb.list();
return list;
}
至此,我们可以真正测试操作数据库了
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
}
private void initData(){
DBManager dbManager = DBManager.getInstance(this);
for (int i = 0;i<6;i++){
User user = new User("测试数据"+i,10+i);
dbManager.insertUser(user);
}
List users = dbManager.queryUserList();
for (User user : users){
Log.i(TAG,user.toString()+"\n");
}
}
}
运行结果:
初步测试,没毛病。其它增删改查功能,大家自己可以测试下,这里就不做示例了。接下来,我们讨论下一个问题:数据库升级。
应用上线之后,app的更新或多或少涉及到数据库的版本升级。可能要给某张表添加一个字段,也可能要新增一张表等等此类情况。那么,这里数据库更新可以有两种方案:
方案一:删除旧的数据库,创建新的数据库和数据库表
这种方案,简单粗暴,一般也不会出什么Bug,但也出在一个很明显的问题,就是在已经上线的应用,更新app升级数据库时删除了用户原有的数据,对于用户体验来说是很不友好的。
方案二:保留原有数据库数据,并在此基础上添加新的字段或新表。这种方案也就是为了解决方案一存在的问题。
那么,greenDao3.0在升级数据库上是怎么做的呢?
通过调查源码发现,我们操作的对象mOpenHelper对应的类DevOpenHelper重写了一个方法onUpgrade(),没错,这就是数据库版本升级时要调用的方法,看内容:
@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);
}
通过源码我们发现:
dropAllTables(db,true);//删除当前数据库所有表单;
onCreate(db);//新建当前所有表
即greenDao默认的升级方法就是我们的方案一,简单粗暴,问题也突出。然而,我们还是先来调查一下它什么时候会调用onUpgrade()方法。
查看源码,发现在执行mOpenHelper.getWritableDatabase()和mOpenHelper.gerReadableDatabase()的时候会比较数据库的版本号和app设置的数据库版本号(即所谓的数据库的旧版本号和新版本号),如果不同者会执行升级(onUpgrade())或降级(onDowngrde())数据库操作,看源码:
final int version = db.getVersion();
if (version != mNewVersion) {
if (db.isReadOnly()) {
throw new SQLiteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + mName);
}
db.beginTransaction();
try {
if (version == 0) {
onCreate(db);
} else {
if (version > mNewVersion) {
onDowngrade(db, version, mNewVersion);
} else {
onUpgrade(db, version, mNewVersion);
}
}
db.setVersion(mNewVersion);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
很明显的,这里的version就是从当前数据库中获取的版本号(即旧版本号),而mNewVersion,我们通过追踪,发现它是从DaoMaster类里传过来的,也就是SCHEMA_VERSION
public class DaoMaster extends AbstractDaoMaster {
public static final int SCHEMA_VERSION = 1;
...
}
换言之,我们要升级数据库,就要改变这个值,但是我们都知道这是greenDao框架自动生成的,不能手动修改:
// THIS CODE IS GENERATED BY greenDAO, DO NOT EDIT.
是的,即使我们非要手动修改,你会发现编译之后它又还原了。这是,我们就要用到上面提到的greendao{}辅助配置
我们添加配置schemaVersion,这就是设置新数据库的版本号:
greendao {
schemaVersion 2
...
}
编译,我们发现,DaoMaster的变成了SCHEMA_VERSION我们设置的值了
/**
* Master of DAO (schema version 2): knows all DAOs.
*/
public class DaoMaster extends AbstractDaoMaster {
public static final int SCHEMA_VERSION = 2;
。。。
}
Ok,现在就可以升级数据库了,运行:
查看打印信息,我们知道,它确实执行了onUpgrade()方法,也就是完成了数据库的升级
知道了如何执行数据库升级,现在就还剩下最后一个问题,如何实现方案二?
这里我通过网上查阅资料,看到了某大神提供的方法,合并(MigrationHelper),其核心思路如下:
1 把旧表改为临时表
2 建立新表
3 临时表数据写入新表,删除临时表
看MigrationHelper.java核心方法:public class MigrationHelper {
/**
* 调用升级方法
* @param db
* @param daoClasses 一系列dao.class
*/
public static void migrate(Database db, Class extends AbstractDao, ?>>... daoClasses) {
//1 新建临时表
generateTempTables(db, daoClasses);
//2 创建新表
createAllTables(db, false, daoClasses);
//3 临时表数据写入新表,删除临时表
restoreData(db, daoClasses);
}
...
}
所以我们需要新建一个类MyDBOpenHelper继承DaoMaster.DevOpenHelper,重写onUpgrade()方法,里面调用MigrationHelper.migrate()方法就行了。
public class MyDbOpenHelper extends DaoMaster.DevOpenHelper {
public MyDbOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory) {
super(context, name, factory);
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
MigrationHelper.migrate(db, UserDao.class);
}
}
现在,我们修改SCHEMA_VERSION为3,给User添加新字段number,将DBManager中的DaoMaster.DevOpenHelper替换成MyDBOpenHelper,以及修改其他对应代码,编译运行:
我们发现数据库确实保存了原来的数据,并添加了新的字段number。至此,我们终于实现了方案二,完美
BUT,我们新增表能行吗?这里,我们测试下添加一个实体类News
@Entity
public class News {
private int id;
private String newsInfo;
...
}
同步编译,同样在DBManager.java里添加增删改查方法,并在onUpgrade()方法里添加NewsDao.class
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
MigrationHelper.migrate(db, UserDao.class, NewsDao.class);
}
在MainActivity里添加代码
private void initData(){
...
for (int i = 0;i<6;i++){
News user = new News(i,"测试数据"+i);
dbManager.insertNews(user);
}
List newsList = dbManager.queryNewsList();
for (News user : newsList){
Log.i(TAG,user.toString()+"\n");
}
}
修改SCHEMA_VERSION为4,运行,发现提示错误 E/SQLiteLog: (1) no such table: NEWS即没有创建NEWS表
所以大神的MigrationHelper这个类的功能似乎也稍有欠缺,呐,现在我们又有了新的目标,当旧数据库里不存在某表时,我们创建该表。
怎么做呢?是的,greenDao框架怎么做,我们就怎么做。
greenDao是怎么做的呢,通过研究代码发现,greenDao是通过DaoMaster调用createAllTables(Database db, boolean ifNotExists)方法创建表的
public class DaoMaster extends AbstractDaoMaster {
public static final int SCHEMA_VERSION = 4;
/** Creates underlying database table using DAOs. */
public static void createAllTables(Database db, boolean ifNotExists) {
UserDao.createTable(db, ifNotExists);
NewsDao.createTable(db, ifNotExists);
}
...
}
/** Creates the underlying database table. */
public static void createTable(Database db, boolean ifNotExists) {
String constraint = ifNotExists? "IF NOT EXISTS ": "";
db.execSQL("CREATE TABLE " + constraint + "\"NEWS\" (" + //
"\"ID\" INTEGER NOT NULL ," + // 0: id
"\"NEWS_INFO\" TEXT);"); // 1: newsInfo
}
@Override
public void onUpgrade(Database db, int oldVersion, int newVersion) {
DaoMaster.createAllTables(db,true);//如果表不存在,则创建
MigrationHelper.migrate(db, UserDao.class,NewsDao.class);
}
是的,成功,没毛病!
至此,我们基于greenDao的数据库升级就基本搞定啦。
示例源码链接