从使用到源码—GreenDao(基本使用篇)

引言

  • 在Android应用开发过程中,几乎每个应用都需要使用到数据库,而直接使用系统的Sqlite存储数据的话又不是加密的,并且当我们需要插入多种类型的Entity时,就需要编写多少种增删改查的SQL语句,然后拼接或者反射转换,繁琐程度只有写过的老哥才知道。因此,Ummm..为了不写到吐血且保证一定的数据安全性,咱还是选择一个能加密数据库并且能简洁使用的ORM吧。
  • 为啥用GreenDao呢?宣传描述可查看官网介绍 GreenDao , 简单概述一下就是: 比Sqlite原生更快、可加密、使用更简单、极小的内存消耗、库体积小于100k等;EventBus也是出自他们的手笔,可信赖程度高。
  • OK,接下来我们会从基本使用着手逐步分析它的内部实现,看看它内部有啥猫腻。

准备工作

  • 环境配置
    • projects#build.gradle文件中如下配置。
buildscript {
    repositories {
        jcenter()
        mavenCentral() // add repository
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.1'
        classpath 'org.greenrobot:greendao-gradle-plugin:3.2.2'  
    }
}
  • app#build.gradle文件中如下配置 。
apply plugin: 'com.android.application'
apply plugin: 'org.greenrobot.greendao' // apply plugin

dependencies {
implementation 'org.greenrobot:greendao:3.2.2'  
    // 需要加密数据库的话添加这个
    implementation 'net.zetetic:android-database-sqlcipher:3.5.6'
}
  • 接下来需要 建立模型入口(Model Entity,只有在创建了至少一个模型入口并且编译通过的情况下才能开始使用greenDao

  • 生成Entity相关代码

    • 生成模板代码的方式有两种
      • 方式一: 编写JAVA代码,然后执行生成,如下示例:
    public class MyDaoGenerator
    { 
        public static void main(String[] args)
        {
            //指定生成类所在的包名
            Schema schema = new Schema(1, "com.example.laixiaolong.greendaotraning.greenDao.db");
    
             addNote(schema);
           // addCustomerOrder(schema);
     
            // 指定生成的类存放的目录
            new DaoGenerator().generateAll(schema, "./app/src/main/java");
        }
    
        private static void addNote(Schema schema) {
            Entity note = schema.addEntity("Note"); // 指定类名
            // id
            note.addIdProperty();
            // 属性即表的Column字段
            note.addStringProperty("title").notNull();
            note.addStringProperty("content");
            note.addDateProperty("date");
        }
        
        private static void addCustomerOrder(Schema schema) {
            Entity customer = schema.addEntity("Customer");
            customer.addIdProperty();
            customer.addStringProperty("name").notNull();
    
            Entity order = schema.addEntity("Order");
            order.setTableName("ORDERS"); // "ORDER" is a reserved keyword
            order.addIdProperty();
            Property orderDate = order.addDateProperty("date").getProperty();
            Property customerId = order.addLongProperty("customerId").notNull().getProperty();
            order.addToOne(customer, customerId);  // 一对一关联
    
            ToMany customerToOrders = customer.addToMany(order, customerId); // 一对多关联
            customerToOrders.setName("orders");
            customerToOrders.orderAsc(orderDate);
        }  }
    
    • 方式二: 使用gradle插件配置:在app#build.gradle中如下配置,这种方式会便捷很多,因为我们只需要专注于Entity的编写就好了,编译器会自动生成对应Dao文件。
    android {
    ...
    } 
    greendao {
        // 标记当前数据库表版本,*OpenHelpers会根据这个参数来进行版本迁移
        // 如果entity或数据库表版本改变一次,这个值就会增加1,默认值是1
        schemaVersion 1
        // 指定生成的DAO、DaoMaster、DaoSession类所在的包名,默认是Entity所在包下
        daoPackage "com.example.laixiaolong.greendaotraning.greenDao.db"
        // 生成类存放的路径,默认是“build/generated/source/greendao”
        targetGenDir "./app/src/main/java"
        // 是否自动生成单元测试
        generateTests false
        // 单元测试存放的路径,默认是“src/androidTest/java”
        targetGenDirTests "src/androidTest/java"
    }
    
  • 下面一个简单的ChatObject关联User的示例:

@Entity
public class User
{
   @Id(autoincrement = true) 
    private Long id;
    @Unique 
    private Long userIncrId; 
    @NotNull
    private String userAccount;
    private String userName;
    private Long orgId;
    private String orgName;
    // ...
}

@Entity
public class ChatObject
{
    @Id(autoincrement = true) 
    private Long id;
    private String userIcrId;
   
    // 一对一关联
    @ToOne(joinProperty = "userIncrId")
    private User user;
} 
  • 其中@Entity 属性用法解释如下:
    @Entity(
        // 存在多张图时,设置schema参数告诉greenDao这个Entity属于哪张图
       // 可以是任意名字
       schema = "myschema", 
       // 标识这个entity是否“active”,active状态的表才具备增删改查的方法
       active = true, 
        // 指定表的名称,默认是entity的名称
       nameInDb = "AWESOME_USERS",  
       // 申明某个列字段会会占用多个列
       indexes = {   @Index(value = "name DESC", unique = true)  }, 
       // 标记是否生成数据表,默认是true,如果false,那么多个entity会映射到一张表,或者在greenDao外创建表
       createInDb = false, 
       // 是否需要生成一个具有所有属性的构造函数
       // 无参构造器是必须存在的
       generateConstructors = true, 
        // 是否需要在属性的get和set方法缺失时自动为其生成
       generateGettersSetters = true
       )     
    

增删改查(基本操作)

  • 初始化

    • 先在Application中如下初始化
    public class App extends Application
    { 
        private DaoSession daoSession;
        private static App app; 
        @Override
        public void onCreate()
        {
            super.onCreate();
            app = this; 
            //使用普通数据库
            DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "roz-hjjm-db");
            daoSession = new DaoMaster(devOpenHelper.getWritableDb()).newSession();
             //使用 加密数据库
            //  Database encryptedReadableDb = devOpenHelper.getEncryptedReadableDb("123456");
            // daoSession =   new DaoMaster(encryptedReadableDb).newSession();
        } 
        public static App app()
        {  return app;  }
    
        public DaoSession getDaoSession()
        {   return daoSession;    }
    } 
    
  • 增删改查

    • 使用GreenDao时几乎可以完全摆脱手写Sql的束缚,不必担心某个字段名称写错、关键字写错、标点符号写错的问题,从而更快稳健的实现我们的想法。
    • 插入
      • 插入方式有以下两种,包括普通插入和使用事务批量插入,两者的区别通过下面代码运行结果可以很清晰的看出(++使用模拟器运行++),同样是插入100条数据,如果使用事务批量插入,效率能高个3~4倍,而如果只需要插入一条,那么对于本例而言(不排除其他情况),两者耗时是一样的,可见:
        • 当数据量大时还是集中使用批量插入更为妥当
        • 如果只需要插入一条,那么二者可任选其一
    @Test
    public void testGreenDaoInsert() {
       DaoSession daoSession = App.getDaoSession();
       UserDao userDao = daoSession.getUserDao();
    
       long start = System.currentTimeMillis();
       // 1. 普通插入,每次只能插入单条
       //insert 100: System.out: -->>Insert total used:: 83ms
       //insert 1:System.out: -->>Insert total used:: 3ms
       for (int i = 0; i < 100; i++) {
           // insertOrReplace
           userDao.insertOrReplace(new User()
                   .setFirstName("firstName::" + i)
                   .setLastName("lastName::" + i)
                   .setGender(i % 2 == 0 ? "male" : "female")
                   .setPhoneNum(String.valueOf(SystemClock.elapsedRealtimeNanos()))
                   .setAge(i + 10));
       }
       
       // 2. 使用事务插入,可以进行批量插入,也可以单条插入
       //insert 100: System.out: -->>Insert total used:: 26ms
       //insert 1:System.out: -->>Insert total used:: 3ms
       /*User[] usersArray = new User[100];
       for (int i = 0; i < 100; i++) {
           // insertOrReplace
           usersArray[i] =  new User()
                   .setFirstName("firstName::" + i)
                   .setLastName("lastName::" + i)
                   .setGender(i % 2 == 0 ? "male" : "female")
                   .setPhoneNum(String.valueOf(SystemClock.elapsedRealtimeNanos()))
                   .setAge(i + 10) ;
       }
       userDao.insertOrReplaceInTx(usersArray);*/
    
    
       System.out.println(String.format("-->>Insert total used:: %sms" , (System.currentTimeMillis() - start)) );
    
    }
    
    • 修改
      • 修改过程也很简单,并且可以单条修改和使用事务批量修改,和插入方式是一致的,不过需要注意的是
        • 修改时需要设置要修改条目的id(也就是key),否则会修改失败
        • 修改过程是覆盖式的,意味着如果修改时Entity中某个字段值为null,那么就会覆盖掉表中对应的字段数据,这点尤为重要,因为可能小手一抖,你的数据就没了
    // 修改
    @Test
    public void testGreenDaoUpdate() {
        DaoSession daoSession = App.getDaoSession();
        UserDao userDao = daoSession.getUserDao();
    
        User user = new User()
                .setId(51L)  
                .setFirstName("horseLai")
                .setAge(23)
                .setPhoneNum("1234");
        // before: User{id=51, phoneNum='5866201745180', firstName='firstName::7', gender='female', lastName='lastName::7', age=17}
        // after: User{id=51, phoneNum='1234', firstName='horseLai', gender='null', lastName='null', age=23}
        // 1. 普通修改 ,只能单条修改
        userDao.update(user);
        // 2. 批量修改, 可批量修改
        // userDao.updateInTx(user);
    }
    
    • 删除
      • 删除过程也很简单,有以下四种方式进行,具体可按需选择,如果知道id那么13ok,如果需要指定删除条件,那么4,删除所有数据则2
    // 删除
    @Test
    public void testGreenDaoDelete() {
        DaoSession daoSession = App.getDaoSession();
        UserDao userDao = daoSession.getUserDao();
    
        // 1. 删除单个
        // userDao.deleteByKey(15L);
        
        // 2. 删除所有
        userDao.deleteAll();
        
        // 3. 批量删除(前提是知道key,也就是id)
        userDao.deleteByKeyInTx(15L, 16L,17L);
    
        // 4. 批量(按查找条件)删除
        userDao.queryBuilder()
                .where(UserDao.Properties.Age.gt(15))
                .buildDelete()
                .executeDeleteWithoutDetachingEntities();
    }
    
    
    • 查找
      • 查找也很容易,可以按需选择,在以下实例中已经体现的很清楚,相对来说如果条件简单的话,使用sql语句进行查询(方式2)会简洁很多,其次是使用方式1,最后是使用方式3,不过我感觉没人会喜欢使用方式3,除非有什么特殊需求,毕竟看到那一大串代码你就想敬而远之,还有可能会忘记释放cursor,导致内存泄漏。
    // 查找
    @Test
    public void testGreenDaoQuery() {
    
        DaoSession daoSession = App.getDaoSession();
        UserDao userDao = daoSession.getUserDao();
        // 1. 查询
        /*List<User> users = userDao.queryBuilder()
                .limit(10)
                .orderAsc(UserDao.Properties.Age)
                .where(UserDao.Properties.Age.gt(15))
                .build()
                .list();*/
    
        //  2. 通过sql语句查询
        /*List<User> users = userDao.queryRaw("where age = ?", "15");
        for (User user : users) {
            System.out.println(user.toString());
        }*/
    
        // 3. 通过cursor查询
        Cursor cursor = userDao.queryBuilder()
                .limit(10)
                .orderAsc(UserDao.Properties.Age)
                .where(UserDao.Properties.Age.gt(15))
                .buildCursor().query();
        try {
            if (cursor != null) {
                System.out.println("-->> cursor count::" + cursor.getCount());
                if (cursor.getCount() == 0) {
                    return;
                }
                String[] columnNames = cursor.getColumnNames();
                System.out.println("-->> columnNames::" + Arrays.toString(columnNames));
    
                StringBuilder sb = new StringBuilder();
                cursor.moveToFirst();
                while (cursor.moveToNext()) {
                    String firstName = cursor.getString(cursor.getColumnIndex(UserDao.Properties.FirstName.columnName));
                    String lastName = cursor.getString(cursor.getColumnIndex(UserDao.Properties.LastName.columnName));
                    String gender = cursor.getString(cursor.getColumnIndex(UserDao.Properties.Gender.columnName));
                    String phone = cursor.getString(cursor.getColumnIndex(UserDao.Properties.PhoneNum.columnName));
                    int age = cursor.getInt(cursor.getColumnIndex(UserDao.Properties.Age.columnName));
    
                    sb.append("\n{\nfirstName=").append(firstName).append(",\n")
                            .append("lastName=").append(lastName).append(", \n")
                            .append("gender=").append(gender).append(", \n")
                            .append("phone=").append(phone).append(", \n")
                            .append("age=").append(age).append(", \n")
                            .append("}");
                }
    
                System.out.println(sb.toString());
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
    
        // 统计相应条件下的条目数量,如果需要查询数量,而不需要提取数据,
        // 那么请使用这种方式,不然性能浪费是明显的。
       /* long totalCount = userDao.queryBuilder()
                .limit(10)
                .orderAsc(UserDao.Properties.Age)
                .where(UserDao.Properties.Age.gt(15))
                .buildCount()
                .count();
    
        System.out.println("-->>Total Count::" + totalCount);*/
    
    }
    

关系型数据库

关系型数据库几乎覆盖了我们大部分实际使用场景,比如说实时通讯场景、商城等等。这里我们以一个博客系统数据库模型为例,熟悉一下如何使用greenDao建立这些场景模型。

  • 复合场景

    • 假设我们存在一篇博客,那么肯定需要关联若干条评论数量,并且需要知道是谁写的吧,那么需要关联一个用户(发布者);因此对于评论而言,我们也需要知道是是谁评论的吧,那么我们也需要对每条评论关联一个用户; 如果我们还需要用户可以评论用户评论,那么我们还需要在用户评论中关联若干个用户评论的回复评论;这样的话,这个模型中存在两种关联关系
      • 一对一关联:比如这里的博客与发布者的关系,一篇博客对应于一个发布者。
      • 一对多关联:比如这里的博客与评论的关系,一篇博客对应与若干条评论。
  • 根据以上这个相对复杂的应用场景,可以如下建立数据库模型:

    • 博客
    @Entity
    public class Blog { 
        @Id(autoincrement = true)
        @Unique
        private long id;
    
        @NotNull
        private String title;
        private String content;
        @NotNull
        private Date createTime;
    
        // 发布者
        // 关联至User表的PRIMARY KEY,也就是id
        @NotNull
        private long userId;
        @ToOne(joinProperty = "userId")
        private User user;
    
        // 评论
        // JOIN Comment表中的commentId列至本表的commentId列
        @NotNull
        private long commentId;
        // 1. 简单JOIN
        //  @ToMany(referencedJoinProperty = "commentId")
        // 2. 可以存在多个复杂的JOIN,或者你希望指定JOIN进来的列的列名
        @ToMany(joinProperties = {@JoinProperty(name = "commentId", referencedName = "id")})
        @OrderBy("createTime ASC")
        List<Comment> comments;
    }  
    
    • 评论
      • 用于评论博客
    @Entity
    public class Comment {
        @Id(autoincrement = true )
        private long id;
    
        @NotNull
        private String title;
        private String content;
        @NotNull
        private Date createTime;
     
        // 评论发布者
        // 关联至User表的PRIMARY KEY,也就是id
        private long userId;
        @ToOne(joinProperty = "userId")
        private User user;
     
        // 评论
        // JOIN 关联CommentReply中的commentSubId列
        private long commentSubId;
        @ToMany( joinProperties = {@JoinProperty( name = "commentSubId" ,referencedName = "id")})
        @OrderBy("createTime ASC")
        List<CommentReply> subComments;
    }
    
    • 评论的回复评论
      • 某个用户对博客发表了一条评论,其他用户对这条评论的评论
    @Entity
    public class CommentReply { 
        @Id(autoincrement = true )
        private long id;
        @NotNull
        @Unique
        private long commentSubId;
    
        @NotNull
        private String title;
        private String content;
        @NotNull
        private Date createTime;
     
        // 评论发布者
        // 关联至User表的PRIMARY KEY,也就是id
        private long userId;
        @ToOne(joinProperty = "userId")
        private User user;
    }
    
  • 那么如何操作这个关联数据库模型呢? 可以参考如下示例代码。

    • 注意,我的代码只起到演示作用,主要用于阐述如何使用GreenDao建立数据关联,实际应用请思考再三,否则出问题了可别怪我哈。
@Test
public void multiRelative() {

    UserDao userDao = App.getDaoSession().getUserDao();
    BlogDao blogDao = App.getDaoSession().getBlogDao();
    CommentDao commentDao = App.getDaoSession().getCommentDao();
    CommentReplyDao commentReplyDao = App.getDaoSession().getCommentReplyDao();
    
    // 1. 发布一篇博客
    // 发布者
    User publisher = new User();
    publisher.setAge(24)
            .setGender("男")
            .setFirstName("horseLai")
            .setLastName("horseLai")
            .setPhoneNum("22132323323");
    userDao.insertOrReplace(publisher);

    // 博客
    Blog blog = new Blog();
    blog.setTitle("GreenDao建立关系型数据库模型");
    blog.setContent("xxxxxxxxx");
    blog.setCreateTime(Calendar.getInstance().getTime());

    blog.setUser(publisher);  // 关联至发布者
    blogDao.insertOrReplace(blog);

    // 2. 添加一条评论
    // 评论者
    User commentUser = new User()
            .setPhoneNum("1234555")
            .setAge(24)
            .setFirstName("horseLai");
    userDao.insertOrReplace(commentUser);
    // 评论
    Comment comment = new Comment();
    comment.setTitle("评论");
    comment.setContent("评论内容");
    comment.setUser(commentUser);  // 关联评论者
    comment.setCreateTime(Calendar.getInstance().getTime());
    commentDao.insertOrReplace(comment);
    
 
    // 3. 新增一条评论的回复评论
    User commentReplyUser = new User()
            .setPhoneNum("12345568875")
            .setAge(23)
            .setFirstName("horseLai");
    userDao.insertOrReplace(commentReplyUser);
    // 回复的评论
    CommentReply commentReply = new CommentReply();
    commentReply.setTitle("回复评论");
    commentReply.setContent("评论回复内容");
    commentReply.setUser(commentReplyUser);
    commentReply.setCreateTime(Calendar.getInstance().getTime());
    commentReplyDao.insertOrReplace(commentReply);
      
}

  • 查找数据
    • 增删该查和之前讲解的内容是一致的,这里只演示简单查找,如下示例代码:
    @Test
    public void query() {
        BlogDao blogDao = App.getDaoSession().getBlogDao();
    
        List<Blog> blogs = blogDao.queryBuilder()
                .build()
                .list();
    
        blogs.forEach((blog -> {
            System.out.println();
            System.out.println(blog.toString()); 
            System.out.println();
        }));
    }
    
    
    • 数据结果如下:
      • 可见其实整个模型是个类似于树形的关系结构。
    Blog{
        id=0,
        title='GreenDao建立关系型数据库模型',
        content='xxxxxxxxx',
        createTime=Tue Oct 02 14:03:27 GMT 2018, 
        userId=1, 
        user=User{id=1, phoneNum='22132323323', firstName='horseLai', gender='男', lastName='horseLai', age=24},
        commentId=0,
        comments=[
         Comment{
                 id=0, 
                 title='评论',
                 content='评论内容', 
                 createTime=Tue Oct 02 14:03:27 GMT 2018, 
                 userId=2,
                 user=User{  id=2, phoneNum='1234555',   firstName='horseLai', gender='null', lastName='null',   age=24 }, 
                 subComments=[
                     CommentReply{
                     id=0, 
                     commentSubId=0, 
                     title='回复评论', 
                     content='评论回复内容',
                     createTime=Tue Oct 02 14:03:27 GMT 2018, 
                     userId=3,
                     user=User{ id=3,  phoneNum='12345568875', firstName='horseLai',  gender='null',  lastName='null',   age=23  }
                     }
                 ]
                }
        ] 
    }
     
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,056评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,842评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,938评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,296评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,292评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,413评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,824评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,493评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,686评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,502评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,553评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,281评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,820评论 3 305
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,873评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,109评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,699评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,257评论 2 341

推荐阅读更多精彩内容