美文网首页
GreenDao 3.2使用简介

GreenDao 3.2使用简介

作者: MorisWang | 来源:发表于2019-01-03 17:54 被阅读0次

    前言

    Android开发中,经常用到的三种本地数据持久化的方式为:

    • SharedPreference
    • 文件存储的方式
    • 数据库(SQLite)
      我们在开发过程中,经常会需要存储相对大量的数据,因此数据库常常会用到。Android使用数据库一般有两种方案:
    1. 使用原生数据库SQLite直接操作;
    2. 使用第三方数据库,如GreenDao。

    GreenDao使用简单,加上其本身存取快、体积小、支持缓存、支持加密等优点,使得它成为了一个受欢迎的ORM解决方案,今天我们就简要介绍一下GreenDao的用法和使用过程中一些坑。

    基本使用

    github地址

    GreenDao

    项目中引入

    • 项目的build.gradle中引入
    buildscript {
        repositories {
            jcenter()
            mavenCentral()
        }
        dependencies {
            classpath 'com.android.tools.build:gradle:3.1.1'
            classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2' // 添加GreenDao依赖
        }
    }
    
    • module的build.gradle中引入
    apply plugin: 'com.android.application'
    apply plugin: 'org.greenrobot.greendao' // 添加GreenDao Plugin
    
    dependencies {
        implementation 'org.greenrobot:greendao:3.2.2' // 添加GreenDao依赖
    }
    
    • 数据库版本号与路径设置
      GreenDao核心类共有三个分别为DaoMaster.java、DaoSession.java以及根据你创建的持久化数据结构而生成的XXDao.java。这三个类都可以自动生成,无需手动编写代码。
      生成以上三个类需要在module的build.gradle文件中进行如下配置:
    greendao{
        schemaVersion 1  //数据库版本号
        daoPackage 'com.example.greendaotest.dao'//DaoMaster、DaoSession、XXDao的包名
        targetGenDir 'src/main/java' //DaoMaster、DaoSession、XXDao的路径
        generateTests false //设置为true以自动生成单元测试。
        targetGenDirTests 'src/androidTest/java' //应存储生成的单元测试的基本目录。
    }
    

    其中,schemaVersion为数据库版本号必填;若不需要单元测试,generateTests和targetGenDirTests可以删除;daoPackage和targetGenDir选填,如果填写会根据填写的路径生成相关的代码,否则会在build/generated/source/greendao中自动生成相关的类。
    到此,GreenDao的引入工作已经完成,可以再代码中使用GreenDao进行数据库相关开发了。

    构建实体类

    • 注意:实体类中的属性即为数据库中对应的字段
    @Entity
    public class UserBean {
        @Id(autoincrement = true)
        private Long id;
        @NotNull
        private String name;
        private int age;
    }
    
    GreenDao注解
    • @Entity:告诉GreenDao该对象为实体,只有被@Entity注释的Bean类才能被dao类操作
    • @Id:对象的Id,使用Long类型作为EntityId,否则会报错。(autoincrement = true)表示主键会自增,如果false就会使用旧值
    • @Property:可以自定义字段名,注意外键不能使用该属性
    • @NotNull:属性不能为空
    • @Transient:使用该注释的属性不会被存入数据库的字段中
    • @Unique:该属性值必须在数据库中是唯一值
    • @Generated:编译后自动生成的构造函数、方法等的注释,提示构造函数、方法等不能被修改

    实体类创建后,Build --> Make Module 'app', 会自动生成相应的类,build后刚刚创建的实体类如下:

    @Entity
    public class UserBean {
        @Id(autoincrement = true)
        private Long id;
        @NotNull
        private String name;
        private int age;
        @Generated(hash = 1420883130)
        public UserBean(Long id, @NotNull String name, int age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
        @Generated(hash = 1203313951)
        public UserBean() {
        }
        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 int getAge() {
            return this.age;
        }
        public void setAge(int age) {
            this.age = age;
        }
    }
    

    并且,在刚刚build.gradle 中配置的daoPackage目录下生成了三个java文件,分别为:

    • DaoMaster.java
    • DaoSession.java
    • UserBeanDao.java
      这三个类是自动生成的,不需要手动做修改。自此,实体类构建完成。

    初始化数据库

    1. 创建数据库管理类DbManager,也可以不创建,直接在Application中初始化:
    public class DbManager {
    
        private static final String DB_NAME = "search";
        private static DbManager mDbManager;
        private static DaoMaster.DevOpenHelper mDevOpenHelper;
        private static DaoMaster mDaoMaster;
        private static DaoSession mDaoSession;
    
        private Context mContext;
    
        private DbManager(Context context) {
            this.mContext = context;
            // 初始化数据库信息
            mDevOpenHelper = new DaoMaster.DevOpenHelper(context, DB_NAME);
            getDaoMaster(context);
            getDaoSession(context);
        }
    
        public static DbManager getInstance(Context context) {
            if (null == mDbManager) {
                synchronized (DbManager.class) {
                    if (null == mDbManager) {
                        mDbManager = new DbManager(context);
                    }
                }
            }
            return mDbManager;
        }
    
        /**
         * 获取可读数据库
         *
         * @param context
         * @return
         */
        public static SQLiteDatabase getReadableDatabase(Context context) {
            if (null == mDevOpenHelper) {
                getInstance(context);
            }
            return mDevOpenHelper.getReadableDatabase();
        }
    
        /**
         * 获取可写数据库
         *
         * @param context
         * @return
         */
        public static SQLiteDatabase getWritableDatabase(Context context) {
            if (null == mDevOpenHelper) {
                getInstance(context);
            }
    
            return mDevOpenHelper.getWritableDatabase();
        }
    
        /**
         * 获取DaoMaster
         *
         * @param context
         * @return
         */
        public static DaoMaster getDaoMaster(Context context) {
            if (null == mDaoMaster) {
                synchronized (DbManager.class) {
                    if (null == mDaoMaster) {
                        mDaoMaster = new DaoMaster(getWritableDatabase(context));
                    }
                }
            }
            return mDaoMaster;
        }
    
        /**
         * 获取DaoSession
         *
         * @param context
         * @return
         */
        public static DaoSession getDaoSession(Context context) {
            if (null == mDaoSession) {
                synchronized (DbManager.class) {
                    mDaoSession = getDaoMaster(context).newSession();
                }
            }
            return mDaoSession;
        }
    }
    

    2.初始化GreenDao:

    public class GreenDaoApplication extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            //初始化GreenDao
            DbManager.getInstance(this);
        }
    }
    

    增删改查

    为了方便介绍,单独建立一个单例类,创建UserBeanDaoManager,如下:

    package com.example.greendaotest;
    
    import android.content.Context;
    
    import com.example.greendaotest.bean.UserBean;
    import com.example.greendaotest.dao.UserBeanDao;
    
    import java.util.List;
    
    /**
     * 1 * FileName: UserBeanDaoManager
     * 2 * Author: WangDongDong
     * 3 * Date: 2019/1/3 4:34 PM
     * 4 * Description:
     * 5 * History:
     * 6 * <author> <time> <version> <desc>
     * 7 * 作者姓名 修改时间 版本号 描述
     */
    public class UserBeanDaoManager {
        private static UserBeanDaoManager mManger;
        private static UserBeanDao mUserBeanDao;
    
        public static UserBeanDaoManager getInstance(Context context) {
            synchronized (UserBeanDaoManager.class) {
                if (mManger == null) {
                    mManger = new UserBeanDaoManager(context);
                }
                return mManger;
            }
        }
    
        public UserBeanDaoManager(Context context) {
            mUserBeanDao = DbManager.getDaoSession(context).getUserBeanDao();
        }
    
        /**
         * 新增数据
         * @param bean
         */
        public void insertData(UserBean bean) {
            mUserBeanDao.insert(bean);
        }
    
        /**
         * 增加一组数据
         * @param list
         */
        public void insertData(List<UserBean> list) {
            mUserBeanDao.insertInTx(list);
        }
    
        /**
         * 修改数据
         * @param bean
         */
        public void updateData(UserBean bean) {
            mUserBeanDao.update(bean);
        }
    
        /**
         * 查询数量
         * @return
         */
        public long getUserCount() {
            return mUserBeanDao.count();
        }
    
        /**
         * 按年龄降序查询所有数据
         * @return
         */
        public List<UserBean> queryAll() {
            return mUserBeanDao
                    .queryBuilder()
                    .orderDesc(UserBeanDao.Properties.Age)
                    .list();
        }
    
        /**
         * 按条件查询
         * @param name
         * @return
         */
        public List<UserBean> queryByName(String name) {
            List<UserBean> list = mUserBeanDao
                    .queryBuilder()
                    .where(UserBeanDao.Properties.Name.eq(name))
                    .list();
            return list;
        }
    
        /**
         * 删除数据
         * @param bean
         */
        public void deleteData(UserBean bean) {
            mUserBeanDao.delete(bean);
        }
    
        /**
         * 删除全部
         */
        public void deleteAll() {
            mUserBeanDao.deleteAll();
        }
    }
    

    以上是GreenDao基本的增删改查操作,GreenDao提供了更加丰富的API,这些API可以通过java doc直接看到,再此不做更多介绍。

    联合查询

    创建班级类,ClassBean.java:

    @Entity
    public class ClassBean {
        @Id(autoincrement = true)
        private Long id;
        private String className;
        @Generated(hash = 2143102101)
        public ClassBean(Long id, String className) {
            this.id = id;
            this.className = className;
        }
        @Generated(hash = 1395092832)
        public ClassBean() {
        }
        public Long getId() {
            return this.id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public String getClassName() {
            return this.className;
        }
        public void setClassName(String className) {
            this.className = className;
        }
    }
    

    在UserBeanDaoManager中新增方法:

    /**
         * 联合查询,查询班级为一班的学生
         * @param name
         * @param age
         * @return
         */
        public List<UserBean> queryByNameAndAge(String name, int age) {
            QueryBuilder builder = mUserBeanDao.queryBuilder();
            builder.join(ClassBean.class, ClassBeanDao.Properties.Id)
                    .where(ClassBeanDao.Properties.ClassName.eq("一班"));
            List<UserBean> list = builder.list();
            return list;
        }
    
    查询条件
    • distinct() 过滤字段
    • limit() 限制数量
    • offset() 忽略查询出的前n条结果
    • orderAsc() 升序排列
    • orderDesc() 降序排列
    • eq() 等于
    • notEq() 不等于
    • in() 在范围之内
    • notIn() 不再范围之内
    • or() 或者
    • like() 就是sql语句的 LIKE "%" +string+ "%"
    • between() 也就是BETWEEN ? AND ? 可以取两个值的区间
    • gt() 相当于 >
    • ge() 相当于 >=
    • lt() 相当于 <
    • le() 相当于 <=
    • isNull 为空
    • notIsNull 不为空

    数据库升级

    build.gradle 的greendao配置中,有schemaVersion字段:

    greendao{
        schemaVersion 1  //数据库版本号
        daoPackage 'com.example.greendaotest.dao'//DaoMaster、DaoSession、XXDao的包名
        targetGenDir 'src/main/java' //DaoMaster、DaoSession、XXDao的路径
        generateTests false //设置为true以自动生成单元测试。
    //    targetGenDirTests 'src/androidTest/java' //应存储生成的单元测试的基本目录。
    }
    

    升级数据库需要将该字段值加1。
    但是!但是!!但是!!!如果只是版本+1 ,数据库字段确实添加成功了,但所有的数据都没了,这在实际应用中,是一个很严重的问题。
    原因可见自动生成的DaoMaster类中,onUpgrade处的注释:

    /** WARNING: Drops all table on Upgrade! Use only during development. */
        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);
            }
        }
    

    原来,GreenDao在升级时,首先会删除所有table,然后重新创建,因此数据在升级是会丢。
    因此,我们可以通过以下的方式进行升级:

    • 方案一:每次升级时,在onUpgrade中,执行升级数据库的sql:
    @Override
    public void onUpgrade(Database db, int oldVersion, int newVersion) {
                if (oldVersion < 2) {
                    //添加性别字段
                    db.execSQL(String.format("ALTER TABLE %s ADD %s varchar", DbManager.DB_NAME, "gender"));
                }
                if (oldVersion < 3) {
                    //添加成绩字段
                    db.execSQL(String.format("ALTER TABLE %s ADD %s integer default 0", DbManager.DB_NAME, "score"));
                }
            }
    
    • 方案二:
      1.首先创建临时表。
      2.把当前表的数据插入到临时表中去。
      3.删除掉原表,创建新表。
      4.把临时表数据插入到新表中去,然后删除临时表。

    数据库升级方案可以参考GreenDaoUpgradeHelper,这里提供了完整的数据库升级解决方案,在此处就不搬运了。

    混淆

    #greendao3.2.0,此是针对3.2.0,如果是之前的,可能需要更换下包名
    -keep class org.greenrobot.greendao.**{*;}
    -keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
    public static java.lang.String TABLENAME;
    }
    -keep class **$Properties
    

    一些坑

    1. 自动生成的类(DaoMaster.java、DaoSession.java,XXDao.java)是动态编译的,因此在多人协作开发时,会出现在Git更改记录中,总是需要提交。因此,该部分应放在gitignore中。
    2. JavaBean在编译之后,会生成@Generated(hash = xxxxxxxxx)样式的注解,如:
     @Generated(hash = 1420883130)
        public UserBean(Long id, @NotNull String name, int age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
    

    该hash值也是动态生成的,因此如果动态编译之后,hash值有可能会改变,会发生编译错误

    java.lang.RuntimeException: Constructor (see UserBean:24) has been changed after generation.
    Please either mark it with @Keep annotation instead of @Generated to keep it untouched,
    or use @Generated (without hash) to allow to replace it.
    

    因此,此部分需要在生成之后,使用@Keep注解替换:

    @Keep
        public UserBean(Long id, @NotNull String name, int age) {
            this.id = id;
            this.name = name;
            this.age = age;
        }
    
    1. 数据库升级问题,如果需要升级数据库,一定记得重写DaoMaster中onUpgrade的方法,升级数据库的过程中,数据会直接丢失,造成严重后果。

    相关文章

      网友评论

          本文标题:GreenDao 3.2使用简介

          本文链接:https://www.haomeiwen.com/subject/iqvikqtx.html