GeenDao 3.0

關於GreenDao

greenDao是一個將對象映射到SQLite數據庫中的輕量且快速的ORM解決方案。 關於greenDAO的概念可以看官網greenDAO

greenDAO 優勢

1、一個精簡的庫 2、性能最大化 3、內存開銷最小化 4、易於使用的 APIs 5、對 Android 進行高度優化

GreenDao 3.0使用

GreenDao 3.0採用註解的方式來定義實體類,通過gradle插件生成相應的代碼。

一,在as中導入相關的包
compile'org.greenrobot:greendao:3.0.1'
compile'org.greenrobot:greendao-generator:3.0.0'
二,在build.gradle中進行配置:
apply plugin: 'org.greenrobot.greendao'
buildscript { 
    repositories { 
        mavenCentral()    
}    
dependencies {
    classpath 'org.greenrobot:greendao-gradle-plugin:3.0.0'    
    }
}


三. 在app build.gradle中添加 在android{} 下添加
greendao {
schemaVersion 1 //數據庫schema版本,也可以理解爲數據庫版本號
daoPackage 'com.hlx.xqcopy.greendao.dao'  //設置DaoMaster、DaoSession、Dao包名
targetGenDir 'src/main/java' //設置DaoMaster、DaoSession、Dao目錄
}

在gradle的根模塊中加入上述代碼,就完成了我們的基本配置了。

四,創建一個User的實體類
@Entity
public class User {
    @Id 
    private Long id; 
    private String name; 
    @Transient 
    private int tempUsageCount; // not persisted  
}

編譯就會生成DaoMaster、DaoSession、xxxDao
五.註解類型
  • @Entity可以在不使用參數下使用,但是也可以給Entity配置參數,其參數如下
//如果該實體屬於多個表單,可以使用該參數; 
schema = "myschema",  
// 該實體屬於激活狀態,激活狀態的實體有更新,刪除,刷新方法; 
active = true,  
// 給這個表指定一個名字,默認情況下是名字是類名 
nameInDb = "AWESOME_USERS",
 // 可以給多個屬性定義索引和其他屬性.
 indexes = { @Index(value = "name DESC", unique = true) },  
//是否使用GreenDao創建該表.
 createInDb = false,   
// 是否所有的屬性構造器都應該被生成,無參構造器總是被要求
 generateConstructors = true,  
// 如果該類中沒有set get方法是否自動生成 
generateGettersSetters = true

  • 基本註釋屬性

  • @ID 一般會選擇long/Long屬性作爲Entity ID(即數據庫中的主鍵)autoincrement=true表示主鍵會自增如果false就會使用舊值

  • @Property 可以自定義一個該屬性在數據庫中的名稱,默認情況下數據庫中該屬性名稱是Bean對象中的 屬性名但是不是以駝峯式而是以大寫與下劃線組合形式來命名的比如:customName將命名爲 CUSTOM_NAME;注意:外鍵不能使用該屬性;

  • @NotNull 確保屬性值不會爲null值;

  • @Transient 使用該註釋的屬性不會被存入數據庫中;

  • @Unique 將屬性變成唯一約束屬性;也就是說在數據庫中該值必須唯一

  • @Generated 提示開發者該屬性不能被修改;並且實體類的方法,屬性,構造器一旦被@Generated註釋就不能被再次修改,否則或報錯

Error:Execution failed for task ':app:greendao'.> Constructor (see 
ExampleEntity:21) has been changed after generation.Please either mark 
it with @Keep annotation instead of @Generated to keep it untouched,or
 use @Generated (without hash) to allow to replace it.

這是因爲在通過javabean對象自動生成entities類時,greenDao會增加實體類代碼,@Generated註釋部分與GreenDao增加的代碼相關,胡亂修改@Generated代碼,就會導致entities部分屬性與javabean不匹配導致報錯;有倆種方法可以避免這種錯誤

  • 還原@Generated 改動的部分,當然你也可以完全刪除@Generated 註釋的部分下一次 app build時將會自動生成;

  • 使用@Keep 代替@Generated 這將告訴greenDao 不會使用該屬性註釋的代碼,但是這種改變可能會破壞entities類和greenDAO的其他部分的連接;注意:默認情況下 greenDao會使用合理的默認值去設置實體類,因此開發者不需要爲每個屬性都添加註釋

@Entity
public class User { 
      @Id(autoincrement = true)
       private Long id;  
      @Property(nameInDb = "USERNAME") 
       private String name;  
      @NotNull 
      private int repos;  
      @Transient 
      private int tempUsageCount; 
 ...}

  • 主鍵限制
    每個實體類都應該有一個long或者LONG型屬性作爲主鍵;如果你不想用long或者LONG型作爲主鍵,你可以使用一個唯一索引(使用@Index(unique = true)註釋使普通屬性改變成唯一索引屬性)屬性作爲關鍵屬性。
@Id
private Long id; 
@Index(unique = true)
private String key;
  • 索引屬性
    使用@Index 可以將一個屬性變爲數據庫索引;其有倆個參數
  • name :不使用默認名稱,自定義索引名稱
  • unique : 給索引增加一個唯一約束,迫使該值唯一
@Entity
public class User { 
@Id 
private Long id;
 @Index(unique = true)
 private String name;
}
  • 核心代碼初始化
    創建數據庫過程
// 下面代碼僅僅需要執行一次,一般會放在application
helper = new DaoMaster.DevOpenHelper(this, "notes-db", null);
db = helper.getWritableDatabase();
daoMaster = new DaoMaster(db);
daoSession = daoMaster.newSession();
// 在activity或者fragment中獲取Dao對象
noteDao = daoSession.getNoteDao()

完成以上所有工作以後,我們的數據庫就已經自動生成了,接下來就可以對數據庫進行操作了;

  • 增刪改查
    greenDao的增,刪 ,改操作比較簡單分別調用insert(),delete(),update()方法即可,save()方法比較特殊既能執行插入操作也能執行修改操作這個具體的可以查看greenDaoAPI

  • Query
    與原生SQLitedatabases的查詢操作相比,greenDao Query簡直不能再簡單;greenDao 使用QueryBuilder構建查詢語句也支持原生的SQL查詢語句

    • 簡單的查詢語句 在用戶表中查詢叫姓“Joe”的所有的用戶:
 List joes = userDao.queryBuilder() 
.where(Properties.FirstName.eq("Joe")) 
.orderAsc(Properties.LastName) .list(); 
  • 嵌套挑去查詢語句:查詢一個出生在1970年10月或者以後的"joe"用戶
QueryBuilder qb = userDao.queryBuilder();
qb.where(Properties.FirstName.eq("Joe"),//第一個約束條件姓喬
qb.or(Properties.YearOfBirth.gt(1970),//或者出生日期大於1970年
qb.and(Properties.YearOfBirth.eq(1970), 
Properties.MonthOfBirth.ge(10))//並且在1970年出生 但是月份大於
10月的));
List youngJoes = qb.list();
greenDao除了eq()操作之外還有很多其他方法大大方便了我們日常查詢操作比如:
- eq():==  
- noteq():!= 
- gt(): >
- lt():< 
- ge:>=
- le:<= 
- like():包含
- between:倆者之間
- in:在某個值內 
- notIn:不在某個值內
  • 分頁查詢
    • limit(int): 限制查詢的數量;
    • offset(int): 每次返回的數量; offset不能單獨使用;
  • 查詢與LazyList類
    • Query : Query類表示一個查詢能夠執行很多次;使用QueryBuilder.build()創建,而當通過QueryBuilder的任何查詢方法(eg:list())來獲取查詢結果時,querybuilder都會 在其內部創建Query來執行查詢語句的;如果執行多次查詢應該使用Query對象; 如果只想獲取一個結果時可以使用Query(or QueryBuilder)中的unique()方法;

    • LazyList : 可以通過以下方法獲取查詢結果集合;

    • list() 緩存查詢結果;list()類型一般爲ArrayList

    • listLazy() 懶查詢,只有當調用list()中的實體對象時纔會執行查詢操作並且只緩存第一次被查詢的結果,需要關閉

    • listlazyUncached() 懶查詢,只有當調用list()中的實體對象時纔會執行查詢操作並且不緩存;

    • listIterator() 對查詢結果進行遍歷,不緩存,需要關閉;

後面三個方法是LazyList類中的方法,LazyList爲了執行不同的緩存策略其內部持有數據庫的cursor對象;一般情況下這三個方法執行完畢後會自動關閉cursor;但是防止在還沒有執行完查詢結果時,對象被終結cursor還是無法被關閉的情況發生,需要手動關閉close();

  • 多次執行查詢語句
    Query對象一旦生成就能多次被使用,你也可以爲下一次查詢增加查詢條件
// fetch users with Joe as a first name born in 1970Query 
query = userDao.queryBuilder().where( 
Properties.FirstName.eq("Joe"), 
Properties.YearOfBirth.eq(1970)).build();List joesOf1970 = 
query.list(); // using the same Query object, we can change the 
parameters// to search for Marias born in 1977 
later:query.setParameter(0, "Maria");query.setParameter(1, 
1977);List mariasOf1977 = query.list();


  • 自定義SQL語句
  1. Cursor cursor = dao.getDatabase().rawQuery("select t.sales_wx_nick_name,t.wx_nick_name,count(*),t.talker_id,t.sales_wx_account from chat_history t group by t.talker_id,t.sales_wx_account order by t.created_at desc"null);  
  2. while (cursor.moveToNext()) {  
  3.     String salesWxNickName = cursor.getString(0);  
  4.     String clientWxNickName = cursor.getString(1);  
  5.     int chatCount = cursor.getInt(2);  
  6.     int talkerId = cursor.getInt(3);  
  7.     String salesWxAccount = cursor.getString(4);  
  8. }  
  • 在多線程執行查詢 如果有多條線程執行查詢語句時需要調用forCurrentThread()方法將query對象與當前線程進行綁定,如果其他線程修改該Query對象,greenDao將會拋出一個異常;forCurrentThread()方法通過將Query創建時的時間作爲 query標識;

  • 使用SQL查詢 如果QueryBuilder不能滿足需求可以使用以下倆種方法來實現你的需求;

  • 首選方法用SQL語句:

Query query = userDao.queryBuilder().where( new 
StringCondition("_ID IN " + "(SELECT USER_ID FROM 
USER_MESSAGE WHERE READ_FLAG = 0)")).build();
  • 備選方法 :
    使用queryRaw 或者queryRawCreate:
Query query = userDao.queryRawCreate( ", GROUP G WHERE 
G.NAME=? AND T.GROUP_ID=G._ID", "admin");


在同一個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;
        }
    }
}










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章