Android笔记之greenDao3.0学习总结

前言

本博文旨在记录greenDao3.0的基本用法以及基于greenDao3.0的数据库的升级策略。本文基于Window10下Android Studio环境操作使用。废话不多说,直入正题。


关于greenDao3.0

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特点

greenDao3.0主要有以下特点:

·        1.性能高,号称Android最快的关系型数据库

          2.内存占用小

·        3.库文件比较小,小于100K,编译时间低,而且可以避免65K方法限制

·        4.支持数据库加密 greendao支持SQLCipher进行数据库加密

         5.简洁易用的API


greenDao3.0使用

greenDao3.0基本配置:

在项目的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基本使用:

现在我们就可以使用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。

此外,定义实体类时对于字段还可以添加以下注解:

  • @Id :主键 Long型,可以通过@Id(autoincrement = true)设置自增长
  • @Property:设置一个非默认关系映射所对应的列名,默认是的使用字段名 举例:@Property (nameInDb="name")
  • @NotNul:设置数据库表当前列不能为空
  • @Transient :添加次标记之后不会生成数据库表的列

再次同步编译,编译完成后,我们可以发现,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,我们还可以添加下面一些数据库的基本属性配置

  • schemaVersion: 数据库schema版本,也可以理解为数据库版本号
  • daoPackage:设置DaoMaster 、DaoSession、Dao包名
  • targetGenDir:设置DaoMaster 、DaoSession、Dao目录
  • targetGenDirTest:设置生成单元测试目录
  • generateTests:设置自动生成单元测试用例
现在,我们就可以进行greendao数据库的实在操作了。

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<User> 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<User> 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<User> queryUserList() {
        DaoMaster daoMaster = new DaoMaster(getReadableDatabase());
        DaoSession daoSession = daoMaster.newSession();
        UserDao userDao = daoSession.getUserDao();
        QueryBuilder<User> qb = userDao.queryBuilder();
        List<User> list = qb.list();
        return list;
    }

    /**
     * 根据条件查询用户列表
     */
    public List<User> queryUserList(int age) {
        DaoMaster daoMaster = new DaoMaster(getReadableDatabase());
        DaoSession daoSession = daoMaster.newSession();
        UserDao userDao = daoSession.getUserDao();
        QueryBuilder<User> qb = userDao.queryBuilder();
        qb.where(UserDao.Properties.Age.gt(age)).orderAsc(UserDao.Properties.Age);
        List<User> 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<User> 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<News> 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
    }

这就好办了,我们直接使用该方法,研究里面方法里面的sql语句发现,我们要将ifNotExists设为true,即表不存在才创建。

@Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
        DaoMaster.createAllTables(db,true);//如果表不存在,则创建
        MigrationHelper.migrate(db, UserDao.class,NewsDao.class);
    }

重新编译运行


是的,成功,没毛病偷笑

至此,我们基于greenDao的数据库升级就基本搞定啦。

示例源码链接










猜你喜欢

转载自blog.csdn.net/zsp765098084/article/details/53742506