引言
- 在Android應(yīng)用開發(fā)過程中,幾乎每個(gè)應(yīng)用都需要使用到數(shù)據(jù)庫栅炒,而直接使用系統(tǒng)的
Sqlite
存儲數(shù)據(jù)的話又不是加密的,并且當(dāng)我們需要插入多種類型的Entity
時(shí),就需要編寫多少種增刪改查的SQL
語句沃疮,然后拼接或者反射轉(zhuǎn)換,繁瑣程度只有寫過的老哥才知道梅肤。因此司蔬,Ummm..為了不寫到吐血且保證一定的數(shù)據(jù)安全性,咱還是選擇一個(gè)能加密數(shù)據(jù)庫并且能簡潔使用的ORM吧姨蝴。 - 為啥用
GreenDao
呢俊啼?宣傳描述可查看官網(wǎng)介紹 GreenDao , 簡單概述一下就是: 比Sqlite
原生更快、可加密左医、使用更簡單授帕、極小的內(nèi)存消耗、庫體積小于100k等浮梢;EventBus
也是出自他們的手筆跛十,可信賴程度高。 - OK秕硝,接下來我們會從基本使用著手逐步分析它的內(nèi)部實(shí)現(xiàn)芥映,看看它內(nèi)部有啥貓膩。
準(zhǔn)備工作
-
環(huán)境配置:
- 在
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'
// 需要加密數(shù)據(jù)庫的話添加這個(gè)
implementation 'net.zetetic:android-database-sqlcipher:3.5.6'
}
接下來需要 建立模型入口(
Model Entity
),只有在創(chuàng)建了至少一個(gè)模型入口并且編譯通過的情況下才能開始使用greenDao
躯护。-
生成
Entity
相關(guān)代碼- 生成模板代碼的方式有兩種:
- 方式一: 編寫JAVA代碼惊来,然后執(zhí)行生成,如下示例:
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); // 一對一關(guān)聯(lián) ToMany customerToOrders = customer.addToMany(order, customerId); // 一對多關(guān)聯(lián) customerToOrders.setName("orders"); customerToOrders.orderAsc(orderDate); } }
-
方式二: 使用
gradle
插件配置:在app#build.gradle
中如下配置棺滞,這種方式會便捷很多裁蚁,因?yàn)槲覀冎恍枰獙W⒂?code>Entity的編寫就好了,編譯器會自動生成對應(yīng)Dao文件继准。
android { ... } greendao { // 標(biāo)記當(dāng)前數(shù)據(jù)庫表版本枉证,*OpenHelpers會根據(jù)這個(gè)參數(shù)來進(jìn)行版本遷移 // 如果entity或數(shù)據(jù)庫表版本改變一次,這個(gè)值就會增加1锰瘸,默認(rèn)值是1 schemaVersion 1 // 指定生成的DAO刽严、DaoMaster、DaoSession類所在的包名,默認(rèn)是Entity所在包下 daoPackage "com.example.laixiaolong.greendaotraning.greenDao.db" // 生成類存放的路徑舞萄,默認(rèn)是“build/generated/source/greendao” targetGenDir "./app/src/main/java" // 是否自動生成單元測試 generateTests false // 單元測試存放的路徑眨补,默認(rèn)是“src/androidTest/java” targetGenDirTests "src/androidTest/java" }
- 生成模板代碼的方式有兩種:
下面一個(gè)簡單的
ChatObject
關(guān)聯(lián)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;
// 一對一關(guān)聯(lián)
@ToOne(joinProperty = "userIncrId")
private User user;
}
- 其中
@Entity
屬性用法解釋如下:@Entity( // 存在多張圖時(shí),設(shè)置schema參數(shù)告訴greenDao這個(gè)Entity屬于哪張圖 // 可以是任意名字 schema = "myschema", // 標(biāo)識這個(gè)entity是否“active”倒脓,active狀態(tài)的表才具備增刪改查的方法 active = true, // 指定表的名稱撑螺,默認(rèn)是entity的名稱 nameInDb = "AWESOME_USERS", // 申明某個(gè)列字段會會占用多個(gè)列 indexes = { @Index(value = "name DESC", unique = true) }, // 標(biāo)記是否生成數(shù)據(jù)表,默認(rèn)是true崎弃,如果false甘晤,那么多個(gè)entity會映射到一張表,或者在greenDao外創(chuàng)建表 createInDb = false, // 是否需要生成一個(gè)具有所有屬性的構(gòu)造函數(shù) // 無參構(gòu)造器是必須存在的 generateConstructors = true, // 是否需要在屬性的get和set方法缺失時(shí)自動為其生成 generateGettersSetters = true )
增刪改查(基本操作)
-
初始化
- 先在
Application
中如下初始化
public class App extends Application { private DaoSession daoSession; private static App app; @Override public void onCreate() { super.onCreate(); app = this; //使用普通數(shù)據(jù)庫 DaoMaster.DevOpenHelper devOpenHelper = new DaoMaster.DevOpenHelper(this, "roz-hjjm-db"); daoSession = new DaoMaster(devOpenHelper.getWritableDb()).newSession(); //使用 加密數(shù)據(jù)庫 // Database encryptedReadableDb = devOpenHelper.getEncryptedReadableDb("123456"); // daoSession = new DaoMaster(encryptedReadableDb).newSession(); } public static App app() { return app; } public DaoSession getDaoSession() { return daoSession; } }
- 先在
-
增刪改查
- 使用
GreenDao
時(shí)幾乎可以完全擺脫手寫Sql
的束縛饲做,不必?fù)?dān)心某個(gè)字段名稱寫錯线婚、關(guān)鍵字寫錯、標(biāo)點(diǎn)符號寫錯的問題盆均,從而更快穩(wěn)健的實(shí)現(xiàn)我們的想法塞弊。 -
插入
- 插入方式有以下兩種,包括普通插入和使用事務(wù)批量插入泪姨,兩者的區(qū)別通過下面代碼運(yùn)行結(jié)果可以很清晰的看出(++使用模擬器運(yùn)行++)游沿,同樣是插入
100
條數(shù)據(jù),如果使用事務(wù)批量插入肮砾,效率能高個(gè)3~4
倍诀黍,而如果只需要插入一條,那么對于本例而言(不排除其他情況)仗处,兩者耗時(shí)是一樣的眯勾,可見:- 當(dāng)數(shù)據(jù)量大時(shí)還是集中使用批量插入更為妥當(dāng)
- 如果只需要插入一條,那么二者可任選其一
- 插入方式有以下兩種,包括普通插入和使用事務(wù)批量插入泪姨,兩者的區(qū)別通過下面代碼運(yùn)行結(jié)果可以很清晰的看出(++使用模擬器運(yùn)行++)游沿,同樣是插入
@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. 使用事務(wù)插入疆柔,可以進(jìn)行批量插入咒精,也可以單條插入 //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)) ); }
-
修改
- 修改過程也很簡單镶柱,并且可以單條修改和使用事務(wù)批量修改旷档,和插入方式是一致的,不過需要注意的是:
- 修改時(shí)需要設(shè)置要修改條目的id(也就是key)歇拆,否則會修改失敗
- 修改過程是覆蓋式的鞋屈,意味著如果修改時(shí)
Entity
中某個(gè)字段值為null
,那么就會覆蓋掉表中對應(yīng)的字段數(shù)據(jù),這點(diǎn)尤為重要故觅,因?yàn)榭赡苄∈忠欢冻П樱愕臄?shù)據(jù)就沒了
- 修改過程也很簡單镶柱,并且可以單條修改和使用事務(wù)批量修改旷档,和插入方式是一致的,不過需要注意的是:
// 修改 @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); }
-
刪除
- 刪除過程也很簡單权旷,有以下四種方式進(jìn)行,具體可按需選擇贯溅,如果知道
id
那么1
和3
都ok
拄氯,如果需要指定刪除條件躲查,那么4
,刪除所有數(shù)據(jù)則2
译柏。
- 刪除過程也很簡單权旷,有以下四種方式進(jìn)行,具體可按需選擇贯溅,如果知道
// 刪除 @Test public void testGreenDaoDelete() { DaoSession daoSession = App.getDaoSession(); UserDao userDao = daoSession.getUserDao(); // 1. 刪除單個(gè) // 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(); }
-
查找
- 查找也很容易,可以按需選擇鄙麦,在以下實(shí)例中已經(jīng)體現(xiàn)的很清楚典唇,相對來說如果條件簡單的話,使用
sql
語句進(jìn)行查詢(方式2
)會簡潔很多胯府,其次是使用方式1
,最后是使用方式3
,不過我感覺沒人會喜歡使用方式3
,除非有什么特殊需求介衔,畢竟看到那一大串代碼你就想敬而遠(yuǎn)之,還有可能會忘記釋放cursor
,導(dǎo)致內(nèi)存泄漏骂因。
- 查找也很容易,可以按需選擇鄙麦,在以下實(shí)例中已經(jīng)體現(xiàn)的很清楚典唇,相對來說如果條件簡單的話,使用
// 查找 @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(); } // 統(tǒng)計(jì)相應(yīng)條件下的條目數(shù)量夜牡,如果需要查詢數(shù)量,而不需要提取數(shù)據(jù)侣签, // 那么請使用這種方式塘装,不然性能浪費(fèi)是明顯的。 /* long totalCount = userDao.queryBuilder() .limit(10) .orderAsc(UserDao.Properties.Age) .where(UserDao.Properties.Age.gt(15)) .buildCount() .count(); System.out.println("-->>Total Count::" + totalCount);*/ }
- 使用
關(guān)系型數(shù)據(jù)庫
關(guān)系型數(shù)據(jù)庫幾乎覆蓋了我們大部分實(shí)際使用場景影所,比如說實(shí)時(shí)通訊場景蹦肴、商城等等。這里我們以一個(gè)博客系統(tǒng)數(shù)據(jù)庫模型為例猴娩,熟悉一下如何使用greenDao
建立這些場景模型阴幌。
-
復(fù)合場景
-
假設(shè)我們存在一篇博客,那么肯定需要關(guān)聯(lián)若干條評論數(shù)量卷中,并且需要知道是誰寫的吧矛双,那么需要關(guān)聯(lián)一個(gè)用戶(發(fā)布者);因此對于評論而言蟆豫,我們也需要知道是是誰評論的吧议忽,那么我們也需要對每條評論關(guān)聯(lián)一個(gè)用戶; 如果我們還需要用戶可以評論用戶評論十减,那么我們還需要在用戶評論中關(guān)聯(lián)若干個(gè)用戶評論的回復(fù)評論栈幸;這樣的話,這個(gè)模型中存在兩種關(guān)聯(lián)關(guān)系:
- 一對一關(guān)聯(lián):比如這里的博客與發(fā)布者的關(guān)系帮辟,一篇博客對應(yīng)于一個(gè)發(fā)布者速址。
- 一對多關(guān)聯(lián):比如這里的博客與評論的關(guān)系,一篇博客對應(yīng)與若干條評論由驹。
-
假設(shè)我們存在一篇博客,那么肯定需要關(guān)聯(lián)若干條評論數(shù)量卷中,并且需要知道是誰寫的吧矛双,那么需要關(guān)聯(lián)一個(gè)用戶(發(fā)布者);因此對于評論而言蟆豫,我們也需要知道是是誰評論的吧议忽,那么我們也需要對每條評論關(guān)聯(lián)一個(gè)用戶; 如果我們還需要用戶可以評論用戶評論十减,那么我們還需要在用戶評論中關(guān)聯(lián)若干個(gè)用戶評論的回復(fù)評論栈幸;這樣的話,這個(gè)模型中存在兩種關(guān)聯(lián)關(guān)系:
-
根據(jù)以上這個(gè)相對復(fù)雜的應(yīng)用場景,可以如下建立數(shù)據(jù)庫模型:
- 博客
@Entity public class Blog { @Id(autoincrement = true) @Unique private long id; @NotNull private String title; private String content; @NotNull private Date createTime; // 發(fā)布者 // 關(guān)聯(lián)至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. 可以存在多個(gè)復(fù)雜的JOIN,或者你希望指定JOIN進(jìn)來的列的列名 @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; // 評論發(fā)布者 // 關(guān)聯(lián)至User表的PRIMARY KEY,也就是id private long userId; @ToOne(joinProperty = "userId") private User user; // 評論 // JOIN 關(guān)聯(lián)CommentReply中的commentSubId列 private long commentSubId; @ToMany( joinProperties = {@JoinProperty( name = "commentSubId" ,referencedName = "id")}) @OrderBy("createTime ASC") List<CommentReply> subComments; }
-
評論的回復(fù)評論
- 某個(gè)用戶對博客發(fā)表了一條評論并炮,其他用戶對這條評論的評論
@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; // 評論發(fā)布者 // 關(guān)聯(lián)至User表的PRIMARY KEY蒿赢,也就是id private long userId; @ToOne(joinProperty = "userId") private User user; }
-
那么如何操作這個(gè)關(guān)聯(lián)數(shù)據(jù)庫模型呢? 可以參考如下示例代碼渣触。
- 注意羡棵,我的代碼只起到演示作用,主要用于闡述如何使用
GreenDao
建立數(shù)據(jù)關(guān)聯(lián)嗅钻,實(shí)際應(yīng)用請思考再三皂冰,否則出問題了可別怪我哈。
- 注意羡棵,我的代碼只起到演示作用,主要用于闡述如何使用
@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. 發(fā)布一篇博客
// 發(fā)布者
User publisher = new User();
publisher.setAge(24)
.setGender("男")
.setFirstName("horseLai")
.setLastName("horseLai")
.setPhoneNum("22132323323");
userDao.insertOrReplace(publisher);
// 博客
Blog blog = new Blog();
blog.setTitle("GreenDao建立關(guān)系型數(shù)據(jù)庫模型");
blog.setContent("xxxxxxxxx");
blog.setCreateTime(Calendar.getInstance().getTime());
blog.setUser(publisher); // 關(guān)聯(lián)至發(fā)布者
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("評論內(nèi)容");
comment.setUser(commentUser); // 關(guān)聯(lián)評論者
comment.setCreateTime(Calendar.getInstance().getTime());
commentDao.insertOrReplace(comment);
// 3. 新增一條評論的回復(fù)評論
User commentReplyUser = new User()
.setPhoneNum("12345568875")
.setAge(23)
.setFirstName("horseLai");
userDao.insertOrReplace(commentReplyUser);
// 回復(fù)的評論
CommentReply commentReply = new CommentReply();
commentReply.setTitle("回復(fù)評論");
commentReply.setContent("評論回復(fù)內(nèi)容");
commentReply.setUser(commentReplyUser);
commentReply.setCreateTime(Calendar.getInstance().getTime());
commentReplyDao.insertOrReplace(commentReply);
}
-
查找數(shù)據(jù)
- 增刪該查和之前講解的內(nèi)容是一致的养篓,這里只演示簡單查找秃流,如下示例代碼:
@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(); })); }
- 數(shù)據(jù)結(jié)果如下:
- 可見其實(shí)整個(gè)模型是個(gè)類似于樹形的關(guān)系結(jié)構(gòu)。
Blog{ id=0, title='GreenDao建立關(guān)系型數(shù)據(jù)庫模型', 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='評論內(nèi)容', 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='回復(fù)評論', content='評論回復(fù)內(nèi)容', 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 } } ] } ] }