史上最高效的 ORM 方案——GreenDao3.0 高级用法

5,512 阅读8分钟

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

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

目录

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

session 缓存

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

QueryBuilder projectQueryBuilder = projectDao
                .queryBuilder()
                .where(ProjectDao.Properties.UserName.eq("123456"));
Query query = projectQueryBuilder.build();
Project project1=query.unique();
QueryBuilder projectQueryBuilder1 = projectDao
                .queryBuilder()
                .where(ProjectDao.Properties.UserName.eq("123456"));
Query 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 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 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 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 orders1 = customer.getOrders();

// 插入一个新订单
Order order = new Order();
order.setCustomerId(customer.getId());
daoSession.insert(order);

// 再一次获取顾客的订单
List 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 queryBuilder = userDao.queryBuilder();
queryBuilder.join(Address.class, AddressDao.Properties.userId)
  .where(AddressDao.Properties.Street.eq("迪拜大楼"));
List 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 bigEuropeanCities = qb.list();

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

自定义参数类型

  1. 默认类型参数 :greenDao默认支持的类型参数如下

    boolean, Boolean
    int, Integer
    short, Short
    long, Long
    float, Float
    double, Double
    byte, Byte
    byte[]
    String
    Date
  2. 自定义类型参数: 如果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 {
      //将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 queryRef = queriesForThreads.get(threadId);
            Q query = queryRef != null ? queryRef.get() : null;
            if (query == null) {
                gc();
                query = createQuery();
                //保存query
                queriesForThreads.put(threadId, new WeakReference(query));
            } else {
                System.arraycopy(initialValues, 0, query.parameters, 0, initialValues.length);
            }
            return query;
        }

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

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