深入GreenDao3.0

作者: 8527e43ec43a | 来源:发表于2016-12-06 10:26 被阅读13144次

    大家好,在上一篇文章中,我主要介绍了GreenDao3.0的最基本的用法,当然也是最常用的用法,如果你的项目里没有特别复杂的多表关联需求的话,我相信那篇文章的知识点已经足够使用了。但是,如果你是一个求知欲特别强的人或者手上有要在本地创建复杂的数据库需求的话,我相信认真读完本篇文章,你一定会有所收获。

    好了废话不多说,今天我们来学习下GreenDao的高级用法有哪些吧!阅读本篇文章前你需要对GreenDao有一定的了解,如果你对GreenDao了解还不够的话,建议先去阅读史上最高效的ORM方案——GreenDao3.0详解

    目录

    • session 缓存
    • 多表关联
    • 多表查询
    • ** 自定义参数类型**
    • 与数据库操作相关的AS插件

    session 缓存

    如果你有多个相同的查询语句去执行,猜猜看返回给你的对象是一个还是多个?比如说像下面这样

    QueryBuilder<Project> projectQueryBuilder = projectDao
                    .queryBuilder()
                    .where(ProjectDao.Properties.UserName.eq("123456"));
    Query<Project> query = projectQueryBuilder.build();
    Project project1=query.unique();
    QueryBuilder<Project> projectQueryBuilder1 = projectDao
                    .queryBuilder()
                    .where(ProjectDao.Properties.UserName.eq("123456"));
    Query<Project> query2 = projectQueryBuilder1.build();
    Project project2=query.unique();
    
    

    答案是project1==project2而且project2查询出来的速度要比project1查询出来的速度快很多倍;
    这是因为在同一个session中如果一个entities已经被session记录那么下一次再次操作该实体时,greenDao会先从内存中查找,如果内存中没有再去数据库中查找。这样一方面就极大的提高greenDao的查询效率,另一方面也是需要特别注意的是当entities更新过 greenDao仍然会从内存中取出旧值,所以如果entities更新过,需要去调用daoseesion.clear()方法清除缓存后才能查到最新值,否则查询到的将还是保存在内存中的值
    下面介绍下清除缓存有两种方法

    • 清除所所有的缓存
    daoSession.clear();
    
    • 清除指定Dao类的缓存
    projectDao = daoSession.getNoteDao();
    projectDao.detachAll();
    

    多表关联

    1. 1:1关联
    当我们在使用sqlite数据库来实现表的1:1关联时,通常我们会在主表中定义一个外键去关联副表,当要查询对应的数据时,首先我们要知道查询数据的外键,然后需要用外键去副表中查询所需要的数据。比如下面这样

      public class Customer {
        private Long id;
      }
      public class Order {
        private Long id;
        private Date date;
        private long customerId;
      }
    

    Customer表通过id与Order表关联,查询Order的Customer时需要先知道Order中的customerId然后根据id=customerId值再去数据库中查询该id所对应的Customer对象。然而在greenDao中一个注释就可以搞定,只需要使用@ToOne注释来定义一个关联对象即可。

    @ToOne 定义了一个entities与另一个entities的1:1对应关系。通过joinProperty参数来定义一个外键下面是代码示例

    public class Order {    
      @Id 
      private Long id;   
    
      private long customerId;  
      
      @ToOne(joinProperty = "customerId")  
      private Customer customer;
    }
    
    @Entity
    public class Customer {    
        @Id 
        private Long id;
    }
    

    这样只要获得Order对象就能通过getCustomer()方法获取Order所对应的Customer了,这样是不是很高效,很简便。其实getCustomer方法也很简单,就是在底层帮助我们封装好了查询语句而已,另外getCustomer获取的对象也是懒查询机制,只有真正使用getCustomer方法查询到的对象时greenDao才会执行查询操作。如果你想立即执行查询操作可以调用DAO类的loadDeep()与queryDeep()方法。

    2. 1:N 关联
    在1对1关联中每个顾客只能与一个订单对应,但是现实生活中肯定不只是这样,也会出现一个顾客下多个订单的情况。如果出现这种需求的话,按照原生Sqlite的思路一样是通过外键关联即可,只是这一次查询的对象会有很多个,但是使用greenDao的1:1关联方式显然不行。不过别担心greenDao还给我们准备了@ToMany注释。

    @ToMany 定义了一个entities(这个标记为源实体)与另一个entities(这个标记为目标实体)的多个对象的关联关系:@Tomany有一下三种方式来定义1:N的映射关系。

    • referencedJoinProperty 在目标实体中我们需要定义一个与源实体关联起来的外键,即Order中的customerId,然后需要在源实体里我们需要将customerId作为referencedJoinProperty的属性。说起来很拗口,其实代码很简单;
        @Entity
        public class Customer {
            @Id private Long id;
    
            @ToMany(referencedJoinProperty = "customerId")
            @OrderBy("date ASC")
            private List<Order> orders;
        }
    
        @Entity
        public class Order {
            @Id private Long id;
            private Date date;
            private long customerId;
        }
    

    是不是很简单通过referencedJoinProperty来标注下俩个实体之间的外键即可

    • joinProperties这个参数是referencedJoinProperty 参数的升级版。在referencedJoinProperty参数中我们发现俩个实体关联的外键是CustomerId与id,但是如果我们的需求是外键不能通过id来定义,需要用自己自定义属性来定义,第一种方法就没法用了,而joinProperties就是为了解决这个需求的。
        @Entity
        public class Customer {
            @Id private Long id;
            @Unique private String tag;
    
            @ToMany(joinProperties = {
                    @JoinProperty(name = "tag", referencedName = "customerTag")
            })
            @OrderBy("date ASC")
            private List<Site> orders;
        }
    
        @Entity
        public class Order {
            @Id private Long id;
            private Date date;
            @NotNull private String customerTag;
        }
    

    其实如果把

     @ToMany(joinProperties = {
                    @JoinProperty(name = "id", referencedName = "customerId")
            })
    

    这样的话就和第一种方法实现原理是一样的了。

    • @JoinEntity 定义了N:M的映射关系。
        @Entity
        public class Product {
            @Id private Long id;
    
            @ToMany
            @JoinEntity(
                    entity = JoinProductsWithOrders.class,
                    sourceProperty = "productId",
                    targetProperty = "orderId"
            )
            private List<Order> ordersWithThisProduct;
        }
    
        @Entity
        public class JoinProductsWithOrders {
            @Id private Long id;
            private Long productId;
            private Long orderId;
        }
    
        @Entity
        public class Order {
            @Id private Long id;
        }
    

    3. 关联表的更新与解析
    关联的查询也是懒加载机制,而且查询的结果会保存在缓存中下一次查询的时候如果缓存有会直接从缓存中获取结果。

    同样关联表更新时因为有缓存机制的存在你需要将改动的表手动的通过add()方法来更新关联表中的对象或者直接清除缓存。

    // 获取当前顾客的订单列表
    List<Order> orders1 = customer.getOrders();
    
    // 插入一个新订单
    Order order = new Order();
    order.setCustomerId(customer.getId());
    daoSession.insert(order);
    
    // 再一次获取顾客的订单
    List<Order> orders2 = customer.getOrders();
    
    // 因为缓存列表没有更新所以订单1与订单2的大小相等
    assert(orders1.size() == orders2.size);
    // 也是相同的对象
    assert(orders1.equals(orders2));
    
    //调用该方法后,才能更新缓存列表
    orders1.add(newOrder);
    
    //删除时也许要手动将缓存列表里面的数据删除
    List orders = customer.getOrders();
    // 从数据库中移除
    daoSession.delete(someOrder);
    // 手动从缓存列表移除
    orders.remove(someOrder);
    
    //如果数据库更新后不想手动添加数据可以使用resetXX()方法来清除缓存
    
    customer.resetOrders();
    List orders = customer.getOrders();
    

    多表查询

    有些时候我们的表没有使用ToOneToMany建立关联关系,但是我们又想一步到位。这时我们可以使用greenDao的多表查询功能来帮助我们减少不必要的代码。
    1. 关联单个表

    //查询地址是住在迪拜大楼的用户
    QueryBuilder<User> queryBuilder = userDao.queryBuilder();
    queryBuilder.join(Address.class, AddressDao.Properties.userId)
      .where(AddressDao.Properties.Street.eq("迪拜大楼"));
    List<User> users = queryBuilder.list();
    

    通过queryBuilder.join()方法即可完成,其用法也很简单第一个参数是关联的类,第二个是关联类中的关联属性。

    2.关联多个表

    //查询在欧洲人口超过100000的城市
    QueryBuilder qb = cityDao.queryBuilder().where(Properties.Population.ge(1000000));
    Join country = qb.join(Properties.CountryId, Country.class);
    Join continent = qb.join(country, CountryDao.Properties.ContinentId,
    Continent.class, ContinentDao.Properties.Id);
    continent.where(ContinentDao.Properties.Name.eq("Europe"));
    List<City> bigEuropeanCities = qb.list();
    

    通过queryBuilder.join()链式调用来实现多表查询
    注意:多表查询的前提是我们已经定义好了外键来关联表与表之间的关系。

    自定义参数类型

    1. 默认类型参数 :greenDao默认支持的类型参数如下
    boolean, Boolean
    int, Integer
    short, Short
    long, Long
    float, Float
    double, Double
    byte, Byte
    byte[]
    String
    Date
    
    1. 自定义类型参数: 如果greenDao的默认参数类型满足不了你的需求,比如你想定义一个颜色属性,那么你可以使用数据库支持的原生数据类型通过PropertyConverter类转换成你想要的颜色属性。
    • 首先你需要给自定义类型参数添加 @Convert注释并添加对应参数
      converter:参数转换类,columnType:在数据库中对应的类型
    • 实现PropertyConverter
      下面是用通过使用数据库支持的Integer类型来转换成数据库不支持的枚举类型
    @Entity
    public class User {
        @Id
        private Long id;
     
        @Convert(converter = RoleConverter.class, columnType = Integer.class)
        private Role role;
     
        public enum Role {
            DEFAULT(0), AUTHOR(1), ADMIN(2);
            final int id;
            
            Role(int id) {
                this.id = id;
            }
        }
     
        public static class RoleConverter implements PropertyConverter<Role, Integer> {
          //将Integer值转换成Role值
            @Override
            public Role convertToEntityProperty(Integer databaseValue) {
                if (databaseValue == null) {
                    return null;
                }
                for (Role role : Role.values()) {
                    if (role.id == databaseValue) {
                        return role;
                    }
                }
                return Role.DEFAULT;
            }
           
          //将Role值转换成Integer值
            @Override
            public Integer convertToDatabaseValue(Role entityProperty) {
                return entityProperty == null ? null : entityProperty.id;
            }
        }
    }
    

    与数据库相关的AS插件

    • 快速清除数据库本地数据。ADB IDEA
    • 调试工具同时可以快速查看数据表结构和数据。 Stetho

    感兴趣的同学可以搜索下这俩个插件真的很好用。

    后记

    上期有同学提问greenDao的多线程同步机制,在这里我简单解释下:
    greenDao多线程同步可以通过forCurrentThread()来实现的,具体原理很简单我们看下源码就知道了

          //获取当前线程id
           long threadId = Thread.currentThread().getId();
          //加锁
            synchronized (queriesForThreads) {
                //queryRef是一个Map集合
                WeakReference<Q> queryRef = queriesForThreads.get(threadId);
                Q query = queryRef != null ? queryRef.get() : null;
                if (query == null) {
                    gc();
                    query = createQuery();
                    //保存query
                    queriesForThreads.put(threadId, new WeakReference<Q>(query));
                } else {
                    System.arraycopy(initialValues, 0, query.parameters, 0, initialValues.length);
                }
                return query;
            }
    

    这是源码的核心部分,从上面我们可以看出greenDao是通过将线程id与query对象存储在Map集合中建立1:N的映射关系,不同线程只会取出属于自己的query而不会调用其他线程的query。

    对了,这个源码地址我会放在简书的评论里,如果有需要的同学可以来我的简书中获取。
    如果你觉得本篇文章对你有帮助的话,点个喜欢或者关注吧!下一期我将介绍关于android动画的相关知识,如果对动画还不太了解的同学,赶紧关注我吧!

    相关文章

      网友评论

      • STTXGR:表结构更新,该怎么操作啊?
      • 5014161d46ba:请问怎么判断表是否存在?
        Z_Liqiang:一个@Entity相当于一个表,不用判断表是否已经存在
      • 3d71d914ea27:@D丶C 关于缓存这块比较疑惑,我做了个demo,查询用query.list取出数据,然后修改数据保存到数据库,再查询query.list 发现数据更新了,没有出现缓存错数据的情况,想问什么时候会出现缓存出现脏数据,按照我自己的想法GreenDao肯定也不会在这么简单的情况下出现脏数据,只是不知缓存在什么情况下会造成脏数据
        Z_Liqiang:你可以先查询,在做数据修改,在查询一下,可数据是否更新
      • 164b5ecd311b:你好,关于“一对多:一个顾客可以有多个订单”,
        如果我要查询表中所有订单的信息,查询的时候也要知道知道该订单对应顾客的信息,请问如何查询?
        Z_Liqiang:拿到订单关联的ID到顾客表查询
      • cdcdec:官方的源码里面没有看到ToOne Tomany的例子,楼主的这几个例子的源码由吗?
      • muboluo:请问亲们,怎么设置属性的默认值呢?
        muboluo:好了,我知道了。刚在GitHub上找到方法了,尴尬了:sweat:
      • 蜗牛奈奈:亲,能不能给我一份关于GreenDao3.0关于一对多的例子?
        8527e43ec43a:@蜗牛不毛 可以,我把我以前写的代码发给你吧!
      • 养猫的柯基: @Entity
        public class Product {
        @ID private Long id;

        @ToMany
        @JoinEntity(
        entity = JoinProductsWithOrders.class,
        sourceProperty = "productId",
        targetProperty = "orderId"
        )
        private List<Order> ordersWithThisProduct;
        }

        @Entity
        public class JoinProductsWithOrders {
        @ID private Long id;
        private Long productId;
        private Long orderId;
        }

        @Entity
        public class Order {
        @ID private Long id;
        }


        这个N:M的映射关系
        @JoinEntity(
        entity = JoinProductsWithOrders.class,
        sourceProperty = "productId",
        targetProperty = "orderId"
        )
        这个段 是JoinProductsWithOrders 的 productId 于 Product 的 id 对应 , oinProductsWithOrders 的 orderId 和 Order id 对应的对吧, 但需求如上面的 “外键不能通过id来定义,需要用自己自定义属性来定义” 这种情况N:M的情况该怎么处理呢? 还有1:1时 同样 “外键不能通过id来定义,需要用自己自定义属性来定义”怎么么处理?
      • 夜雨古风:请教一个问题:比如,PatientGroupNew这个实体类,有一个字段是: private List<WxPatientNew> patients; 一个PatientGroupNew对应多个WxPatientNew;
        所以,我给它加了:
        @ToMany(referencedJoinProperty = "wx_pid")
        private List<WxPatientNew> patients;

        在进行全部数据查询时,可以查到:PatientGroupNew 对应的全部数据
        但是PatientGroupNew下面对应的WxPatientNew (List<WxPatientNew> patients)的数据为空;

        全部查询代码:
        public List<PatientGroupNew> queryAllPatientsData() {
        daoSession.clear();

        Query query = getPatientGroupNewDao().queryBuilder().build();
        List<PatientGroupNew> list = query.list();
        return list;
        }

        请问是怎么回事?
      • 7b9d8050a925:demo方便发一下吗
        8527e43ec43a: @laotoumaizi 评论里面有呀
      • 若者浮生:文中提到的 “如果entities更新过,需要去调用daoseesion.clear()方法清除缓存后才能查到最新值,否则查询到的将还是保存在内存中的值。” 这句话,不知道entities更新过指的是数据库数据的更新,还是什么更新? 如果是数据库数据的更新,在更新的时候默认会把最新的数据写入内存缓存,查询时,虽然也是从内存中获取,但数据还是最新的。如果单单只是多次重复查询,就没必要清除内存缓存了。当然也可以利用IdentityScope.detach(K key, T entity)移除单个缓存。
      • 则卷滔滔:有用,谢谢
        8527e43ec43a: @935ad646ce49 客气呀!有啥不会的可以随时给我发私信哈
      • 5c09702e6bda:大哥给个源码吧!
        8527e43ec43a:https://github.com/greenrobot/greenDAO,这个是官方源码教程
      • 17bb25661912:楼主,源码地址在哪里?
      • 1da4256c6d24:超赞。刚好项目要用GreenDao,我比较小白。大神,那个GreenDao步骤能不能尽可能 详细点呢。
        8527e43ec43a:@苏苏的苏 你先照着写哪里不懂私信我就成
        1da4256c6d24:@D丶C 说来惭愧,前面配置不太懂 :joy:
        8527e43ec43a:@1da4256c6d24 看我的上一篇文章讲的很详细关于greenDao的有什么不会的直接私信我吧!
      • 辞令:赞
        8527e43ec43a: @辞令 谢谢!😀
      • RayMoo:📡
        8527e43ec43a: @小机器玩android 咋啦!😂
      • 相互交流:楼下,greenDao多线程操作同一个表,内部有做并发处理吗?
        8527e43ec43a: @相互交流 你想问得是不是多线程处理呀!
        相互交流:我的意思是,,比如原生的sqlite增删改查,,如果不做任何处理的话,,他们都是在主线程里面执行操作的,,楼主的意思是greenDao如果我们不在子线程操作的话,用greenDao增删改查也是和sqlite在主线程执行的。。。如果都是的话,,比如在不同的线程同时插入,,可以加锁判断,,我怕greenDao内部做处理的话,,,我在上层控制的话,,怕出问题...
        8527e43ec43a: @相互交流 greenDao本身不是数据库只是方便我们对sqlite3操作的工具类而已!所以如果问greenDao支持不支持高并发,就要看sqlite支持不支持!我从网上查的资料来看不涉及高并发,平均每0.8s访问一次数据库,这也许就是sqlite用在移动端而不是服务端的原因吧!
      • 024a6bba9544:问个问题,现在greenDao生成表,还是写个JAVA工程生成吗,还是用插件自动生成
        8527e43ec43a: @魂魄 你建好java bean类后然后build工程会自动生成!自动生成的代码是插件生成的,这个插件就是你配置的时候添加的!这个详情可以看我的第一篇文章那里有介绍
      • 8527e43ec43a:占个坑!明天再放源码地址
        因帅被判刑:好的 谢谢啦 没有收到呢
        8527e43ec43a: @因帅被判刑 好吧!我看没多少人阅读我就想着过俩天再给!不好意思呀!晚上有时间我给你发
        因帅被判刑:@D丶C 大哥源码呢 我等了一天了

      本文标题:深入GreenDao3.0

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