GreenDAO系列(一) GreenDAO和Realm的简单使用

概述

最近打算研究一下Android的ORM框架,即对象关系数据映射,ORM框架能很好的帮我们简化数据库操作逻辑,增加开发效率,而且好的ORM还能帮我们增加执行效率。世面上有很多ORM框架,比如OrmLite、SugarORM、GreenDAO、ActiveAndroid、realm等,搜了一些资料进行对比,感觉GreenDAO和Realm这两款还是比较优秀的,所以这里先介绍下GreenDAO和Realm的简单使用。

GreenDAO

GreenDAO是基于Android原生数据库框架进行封装的,使我们不用写复杂的SQL语句及很多的重复性语句。GreenDAO对Android进行了高度优化,具有轻量级、高性能、支持加密、支持protobuf、对象激活、代码自动生成等特点。

  • 配置依赖
    引入GreenDAO需要分别在工程的build.gradle和Module的build.gradle进行配置。

工程的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' // 添加插件路径
    }
}

Module的build.gradle:

apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // 添加插件的调用
 
dependencies {
    implementation 'org.greenrobot:greendao:3.2.2' // 添加库的依赖
}
  • 配置数据库版本号和代码生成路径
    GreenDAO可以管理数据库版本、自动生成代码,需要我们通过配置告诉它数据库版本号以及生成代码的路径,配置写在Module的build.gradle,如下:
greendao {
    schemaVersion 6                       //数据库版本号
    targetGenDir "src/main/java"     //生成代码的根路径
    daoPackage "com.example.lenovo.dao"          //在根路径下生成类的包名
}
  • 编写Entity类
    GreedDAO会根据注解自动生成代码,所以开始只需要编写实体类即可,GreenDAO会帮我们自动生成创建数据库的代码及各个表对应的dao类。如下是一个Entity类:
@Entity
public class Student {
    @Id(autoincrement = true) Long id;
    String name;
    int age;
    String grades;
    @ToMany(referencedJoinProperty = "studentId")
    List<Course> courseList;
}

这就是个简单的数据类,只是增加了几个注解,然后编译一下工程,神奇的事情就会发生,这个类会扩张成一个比较复杂的类,其中Course是另一个类似的实体类,这里不再贴出代码了。扩张后的Student类如下:

@Entity
public class Student {
    @Id(autoincrement = true) Long id;
    String name;
    int age;
    String grades;
    @ToMany(referencedJoinProperty = "studentId")
    List<Course> courseList;
    /** Used to resolve relations */
    @Generated(hash = 2040040024)
    private transient DaoSession daoSession;
    /** Used for active entity operations. */
    @Generated(hash = 1943931642)
    private transient StudentDao myDao;
    @Generated(hash = 990890750)
    public Student(Long id, String name, int age, String grades) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.grades = grades;
    }
    @Generated(hash = 1556870573)
    public Student() {
    }
    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;
    }
    public String getGrades() {
        return this.grades;
    }
    public void setGrades(String grades) {
        this.grades = grades;
    }
    /**
     * To-many relationship, resolved on first access (and after reset).
     * Changes to to-many relations are not persisted, make changes to the target entity.
     */
    @Generated(hash = 838351874)
    public List<Course> getCourseList() {
        if (courseList == null) {
            final DaoSession daoSession = this.daoSession;
            if (daoSession == null) {
                throw new DaoException("Entity is detached from DAO context");
            }
            CourseDao targetDao = daoSession.getCourseDao();
            List<Course> courseListNew = targetDao._queryStudent_CourseList(id);
            synchronized (this) {
                if (courseList == null) {
                    courseList = courseListNew;
                }
            }
        }
        return courseList;
    }
    /** Resets a to-many relationship, making the next get call to query for a fresh result. */
    @Generated(hash = 829241409)
    public synchronized void resetCourseList() {
        courseList = null;
    }
    /**
     * Convenient call for {@link org.greenrobot.greendao.AbstractDao#delete(Object)}.
     * Entity must attached to an entity context.
     */
    @Generated(hash = 128553479)
    public void delete() {
        if (myDao == null) {
            throw new DaoException("Entity is detached from DAO context");
        }
        myDao.delete(this);
    }
    /**
     * Convenient call for {@link org.greenrobot.greendao.AbstractDao#refresh(Object)}.
     * Entity must attached to an entity context.
     */
    @Generated(hash = 1942392019)
    public void refresh() {
        if (myDao == null) {
            throw new DaoException("Entity is detached from DAO context");
        }
        myDao.refresh(this);
    }
    /**
     * Convenient call for {@link org.greenrobot.greendao.AbstractDao#update(Object)}.
     * Entity must attached to an entity context.
     */
    @Generated(hash = 713229351)
    public void update() {
        if (myDao == null) {
            throw new DaoException("Entity is detached from DAO context");
        }
        myDao.update(this);
    }
    /** called by internal mechanisms, do not call yourself. */
    @Generated(hash = 1701634981)
    public void __setDaoSession(DaoSession daoSession) {
        this.daoSession = daoSession;
        myDao = daoSession != null ? daoSession.getStudentDao() : null;
    }
    
}

可以看到自动生成了很多代码,包括构造函数、get、set、增删改查方法等。这些代码就是GreenDAO根据我们写的那几个注解生成的。
@Entity表示这个类会生成一个对应的表,这个注解还有一些属性可以设置:

@Entity(
        schema = "schemaName",    //表示实体类对应的表
        active = true,                         //表示实体类是“活的”,具有增删改查
        nameInDb = "AWESOME_USERS",   //数据库中使用的别名
        indexes = {                                             //定义索引
                @Index(value = "name DESC", unique = true)
        },
        createInDb = true,                 //是否生成表,默认true
        generateConstructors = true,    //是否生成构造方法
        generateGettersSetters = true  //是否生成get、set方法
)

@Id(autoincrement = true) 表示表的主键,并且自动增加
@ToMany(referencedJoinProperty = "studentId") 表示一个一对多的关系,并通过studentId关联。
这类注解还有很多,比如
@Property(nameInDb="name") 可以配置一个非字段名的列,不配置默认使用字段名
@NotNull 表示这列不能为空
@Transient 标记的字段不会生成表的一列
@Unique 表示这列只能有唯一值
@ToOne 表示一对一关系 ,如@ToOne(joinProperty = "name")
@OrderBy 表示这列如何排序,当get这列数据的时候会按照这个排序给出

再看一下之前配置的路径src/main/java之下的com.example.lenovo.dao包下,也生成了几个类:



这个几个类就是我们会用到的数据库操作类。
其中的DaoMaster类有两个内部类,OpenHelper和DevOpenHelper,如下


    /**
     * Calls {@link #createAllTables(Database, boolean)} in {@link #onCreate(Database)} -
     */
    public static abstract class OpenHelper extends DatabaseOpenHelper {
        public OpenHelper(Context context, String name) {
            super(context, name, SCHEMA_VERSION);
        }

        public OpenHelper(Context context, String name, CursorFactory factory) {
            super(context, name, factory, SCHEMA_VERSION);
        }

        @Override
        public void onCreate(Database db) {
            Log.i("greenDAO", "Creating tables for schema version " + SCHEMA_VERSION);
            createAllTables(db, false);
        }
    }

    /** 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);
        }
    }

后者是前者子类,在OpenHelper的onCreate方法中进行了数据库创建,而DevOpenHelper的onUpdate方法中是删除数据库并重新创建,每次升级数据库就会调用这个方法,所以实际开发中我们可以自己实现一个OpenHelper,并在onUpdate方法中处理数据库升级。

  • 初始化
    有了上面生成的几个类后,就可以进行初始化工作了,一般是在Application的onCreate方法中进行,如下代码:
public class MyApplication extends Application {
    private DaoSession daoSession;
    @Override
    public void onCreate() {
        super.onCreate();
        DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "test.db");
        SQLiteDatabase database = devOpenHelper.getWritableDatabase();
        DaoMaster daoMaster = new DaoMaster(database);
        daoSession = daoMaster.newSession();
    }

    public DaoSession getDaoSession(){
        return daoSession;
    }
}

这里通过OpenHelper生成了一个名为test的数据库,并最终通过DaoMaster获得了一个DaoSession保存在了Application中。

  • 增删改查
    我们使用数据表对应的dao类对表进行操作,dao类可以通过Application中的DaoSession获得。
StudentDao studentDao = ((MyApplication)getApplication()).getDaoSession().getStudentDao();

//增
studentDao.insert(student);   //插入一条数据
studentDao.insertOrReplace(student);   //增加一条数据,如果有这条数据则更新

//删
studentDao.delete(student);      //通过对象删除
studentDao.deleteByKey(1L);   //通过id删除

//改
studentDao.update(student);

//查
List<Student> studentList = studentDao.loadAll();   //获取所有数据
studentDao.queryRaw("where age>?","18"); //直接写查询条件
studentDao.loadByRowId(1);  //根据ID查询
QueryBuilder builder = userDao.queryBuilder();
return  builder.where(StudentDao.Properties.Age.gt(18)).build().list();  //通过QueryBuilder拼查询条件

Realm

Realm和GreenDAO有很大的不同,Realm本身就是一种数据库,而且是可以支持跨平台的数据库,比SQLite更轻量级,速度也更快,支持JSON格式、数据变更通知、数据同步等等特性。下面介绍它的简单使用流程。

  • 引入依赖
    Realm同样需要在工程的build.gradle文件和Module的build.gradle文件配置依赖。
    工程的build.gradle:
buildscript {
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        classpath "io.realm:realm-gradle-plugin:5.0.0"     //realm插件路径
    }
}

Module中的build.gradle只需引入插件即可:

apply plugin: 'realm-android'
  • 初始化
    Realm的初始化也推荐在Application中进行,例子如下:
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
        RealmConfiguration configuration = new RealmConfiguration
                .Builder()
                .name("test.realm")    //设置数据库名称
                .migration(new RealmMigration() {            //设置升级策略
                    @Override
                    public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {

                        RealmSchema schema = realm.getSchema();

                        switch ((int) oldVersion){
                            case 0:
                                schema.create("Person")
                                        .addField("name", String.class)
                                        .addField("age", int.class);
                                oldVersion++;
                            case 1:
                                schema.get("Person")
                                        .addField("id", long.class, FieldAttribute.PRIMARY_KEY)
                                        .addRealmObjectField("favoriteDog", schema.get("Dog"))
                                        .addRealmListField("dogs", schema.get("Dog"));
                                oldVersion++;
                        }
                    }
                })
                .deleteRealmIfMigrationNeeded()   //合并需要删除可以删除
                .schemaVersion(4)                //设置版本号
                .inMemory()                     //设置为内存数据库
                .readOnly()                     //设置数据库只读
                .build();
        Realm.setDefaultConfiguration(configuration);
    }
}

首先调用Realm.init(this),如果需要设置一些初始化功能,可以创建一个RealmConfiguration,代码中可以看到,RealmConfiguration可以指定很多功能,比如数据库名称、升级策略、版本号、以及设置只读、内存数据库等等。然后把这个RealmConfiguration设置为Realm的默认配置即可。

  • 编写Entity类
    Realm的实体类需要继承RealmObject,比GreenDAO侵入性要高,也有一些功能性的注解可以使用,但没有GreenDAO的注解强大,如下是一个实体类:
public class Student extends RealmObject{
    @PrimaryKey
    long id;
    String name;
    int age;
    String grades;

    RealmList<Course> courses;

    public RealmList<Course> getCourses() {
        return courses;
    }

    public void setCourses(RealmList<Course> courses) {
        this.courses = courses;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getGrades() {
        return grades;
    }

    public void setGrades(String grades) {
        this.grades = grades;
    }
}

其中@PrimaryKey表示主键,但是没有自动增加的功能,需要自己实现。学生和课程关系是用RealmList表示的,这种关系和SQLite中的一对多、多对多有可能是不太一样的,但还需要进一步研究才能确定。Course类也是类似的,不再贴代码了。

  • 增删改查
    Realm的增删改查是通过Realm类的实例来实现的,并且必须在事务中进行而且不能跨线程操作,先看代码:
Realm realm = Realm.getDefaultInstance();    //获取默认配置的Realm实例

//或者这样随时用一个RealmConfiguration参数生成一个Realm
//realm = Realm.getInstance(new RealmConfiguration.Builder().name("othor.realm").build()); 

//增
//增加3个课程
        realm.beginTransaction();      //必须先开事务
        List<Course> courseList = new ArrayList<>();
        Course course1 = realm.createObject(Course.class);    //使用createObject方法创建对象
        course1.setName("数学");
        course1.setId(0);
        courseList.add(course1);
        Course course2 = new Course();  //用new创建对象,这个对象对于Realm是非托管的
        course2.setName("语文");
        course2.setId(1);
        realm.copyToRealm(course2); //new 出的对象必须使用copyToRealm方法“拷贝”给Realm,不能直接插入,这也是个值得研究的问题
        courseList.add(course2);
        Course course3 = realm.createObject(Course.class);
        course3.setName("英语");
        course3.setId(2);
        courseList.add(course3);
        realm.insert(courseList);              //批量插入
        realm.commitTransaction();       //关闭事务

       //或者用回调的方式操作,可以代替事务开关
        realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
                Student student = new Student();
                student.setName("小明");
                student.setAge(18);
                student.setGrades("一年级");
                realm.copyToRealm(student);
            }
        });
        
//删
         realm.beginTransaction();
         RealmResults<Student> students = realm.where(Student.class).findAll();
         students.get(0).deleteFromRealm();    //通过实体类自己的方法删除
         students.deleteFirstFromRealm();       //通过RealmResults删除
         students.deleteLastFromRealm();    //通过RealmResults删除
         students.deleteFromRealm(1);   //通过RealmResults删除
         realm.commitTransaction();

//改
          realm.beginTransaction();
          RealmResults<Student> students = realm.where(Student.class).findAll();
          students.get(0).setName("更新名字");     //直接查出一个数据修改就行
          realm.commitTransaction();
         
//查
          //查的方式很多,比如上面的findAll方法,还有下面的equalTo、between,查询某个字段等于某个值或在某个区间等等,这种方法有很多很多
          Student student = realm.where(Student.class).equalTo("age",18).findFirst();
          RealmQuery<Student> studentList = realm.where(Student.class).between("age",10,18);

如上就是GreenDAO和Realm的简单对比及使用流程,更深入的分析,可以关注后续文章。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 概述 最近打算研究一下Android的ORM框架,即对象关系数据映射,ORM框架能很好的帮我们简化数据库操作逻辑,...
    lbhl阅读 1,316评论 1 1
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,563评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • 昨晚看11月15日的《我是演说家》,感触颇多吧。张晨从北京地铁的角度来说梦想人生,让本来迷茫的我更加迷茫,作为一个...
    Swet阅读 145评论 0 0
  • 夜色阑珊 月光独亮在黑暗 影子赤黑在树前 静谧 火光一片 赤红在晚霞里 寂寞的云 被燃烧 淡出蓝天 雀啼鸦鸣 把日...
    丹宁原创文学阅读 320评论 3 14