1. 程式人生 > >Android 深入GreenDao3.0

Android 深入GreenDao3.0

大家好,在上一篇文章中,我主要介紹了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
  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<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。

https://github.com/greenrobot/greenDAO

https://github.com/quhuainan/FootPrinting/blob/master/app/src/main/java/com/qhn/bhne/footprinting/mvp/entries/Project.java