Android數據庫操作(三)——增、刪、改、查、聚合函數

前言

本文參考轉發摘自:郭霖博客http://blog.csdn.net/guolin_blog?viewmode=contents 強烈建議進入原博客查看學習

1、數據存儲操作

  • 通過學習,學會了創建表、升級表、以及建立表關聯,接下來就是對數據庫的CRUD操作了。其中LitePal提供的CRUD操作的API還是頗為豐富的。

1.1 傳統的儲存數據方式

  • 其實最傳統的存儲數據方式肯定是通過SQL語句拼接字符串來進行存儲的,不過這種方式有點過于“傳統”了,今天我們在這里就不討論這種情況。實際上,Android專門提供了一種用于存儲數據的簡便方法,使得我們不用編寫SQL語句就可以執行存儲操作。下面來看一下SQLiteDatabase中的 insert()方法:
public long insert(String table, String nullColumnHack, ContentValues values)  

可以看到,insert方法接收三個參數,第一個參數是表名,第二個參數通常都用不到,直接傳null,第三個參數則是一個封裝了待存儲數據的ContentValues對象。

  • 因此,比如說我們想往news表中插入一條新聞,就可以這樣寫:
SQLiteDatabase db = dbHelper.getWritableDatabase();
 ContentValues values = new ContentValues();
  values.put("title", "這是一條新聞標題");
  values.put("content", "這是一條新聞內容");
  values.put("publishdate", System.currentTimeMillis());

  long id = db.insert("news", null, values); 

其中,調用ContentValues的put()方法來添加待存儲數據,put()方法接收兩個參數,第一個參數是數據庫表中對應的列名,第二個參數就是要存儲的值,最后調用一下insert()方法,這條新聞就會插入到news表當中了,并且該數據行對應的id會作為返回值進行返回。

  • 用法很簡單是嗎?確實,比起直接使用SQL語句,SQLiteDatabase中提供的insert()方法的確簡單了很多。但insert()方法也并非是那么的完美,它還是有很多不方便的地方的,比如說沒有考慮表關聯的情況,我們需要手動對關聯表的外鍵進行存儲。再比如說,沒有提供批量存儲的功能,當我們有一個集合的數據需要存儲時,需要通過循環來遍歷這個集合,然后一次次地調用insert()方法來插入數據。

  • 好了,那么關于傳統存儲數據的用法就簡單介紹到這里,因為確實沒什么的更多的用法了,并且它也不是我們今天的主角。接下來,就讓我們看一看今天的驚喜,學習如何使用LitePal來進行數據庫存儲的操作。

1.2 使用LitePal存儲數據

  • LitePal中與存儲相關的API其實并不多,但用法還是頗為豐富的,而且比起傳統的insert()方法,使用LitePal來存儲數據可以簡單到讓你驚嘆的地步,那么今天我們就來完整地學習一下LitePal存儲數據的所有用法。

  • 在上篇文章當中,我們在項目里已經建好了News、Comment、Introduction、Category這幾個實體類,通過這些實體類,LitePal就可以把相應的表自動創建出來。

  • 現在來觀察這幾個實體類,我們發現這幾個類都是沒有繼承結構的。沒錯,因為LitePal進行表管理操作時不需要這些實體類有任何的繼承結構,當時為了簡單起見就沒有寫。但是進行CRUD操作時就不行了,LitePal要求所有的實體類都要繼承自DataSupport這個類,因此這里我們就要把繼承結構給加上才行。修改News類的代碼,如下所示:

public class News extends DataSupport{  
      
    ......  
      
    // 自動生成get、set方法  
}  

可以看到,這里只是讓News類繼承自了DataSupport,其它什么都沒有改變。另外幾個Comment、Introduction、Category類也使用同樣的改法,這里就不一一演示了。

  • 繼承了DataSupport類之后,這些實體類就擁有了進行CRUD操作的能力
  • 那么比如想要 存儲一條數據到news表當中,就可以這樣寫:
News news=new News();
news.setTitle("這是一條新聞標題");
news.setContent("這是一條新聞內容");
news.setPublishDate(new Date());
 //調用sava方法保存(沒有繼承DataSupport是沒有這個方法的)
 news.save();

怎么樣?是不是非常簡單,不需要SQLiteDatabase,不需要ContentValues,不需要通過列名組裝數據,甚至不需要指定表名,只需要new出一個News對象,然后把要存儲的數據通過setter方法傳入,最后調用一下save()方法就好了,而這個save()方法自然就是從DataSupport類中繼承而來的了。

  • 除此之外,save()方法還是有返回值的,我們可以根據返回值來判斷存儲是否成功,比如說這樣寫:
if (news.save()) {  
    Toast.makeText(context, "存儲成功", Toast.LENGTH_SHORT).show();  
} else {  
    Toast.makeText(context, "存儲失敗", Toast.LENGTH_SHORT).show();  
}  
  • 可以看出,save()方法返回的是一個布爾值,用于表示存儲成功還是失敗但同時也說明這個方法是不會拋出異常的有些朋友希望如果存儲失敗的話就拋出異常,而不是返回一個false,那就可以使用saveThrows()方法來代替,如下所示:
News news = new News();  
news.setTitle("這是一條新聞標題");  
news.setContent("這是一條新聞內容");  
news.setPublishDate(new Date());  
news.saveThrows();  

使用saveThrows()方法來存儲數據,一旦存儲失敗就會拋出一個DataSupportException異常,我們可以通過對這個異常進行捕獲來處理存儲失敗的情況。

  • 那有些細心的朋友可能已經注意到,使用的insert()方法來存儲數據時是有返回值的,返回的是插入行對應的id但LitePal中的save()方法返回的是布爾值,那么我們怎樣才能拿到存儲成功之后這條數據對應的id呢?對此,LitePal使用了一種非常巧妙的做法,還記得我們在每個實體類中都定義了一個id字段嗎?當調用save()方法或saveThrows()方法存儲成功之后,LitePal會自動將該條數據對應的id賦值到實體類的id字段上。讓我們來做個試驗吧,代碼如下所示:
News news = new News();  
news.setTitle("這是一條新聞標題");  
news.setContent("這是一條新聞內容");  
news.setPublishDate(new Date());  
Log.d("TAG", "news id is " + news.getId());  
news.save();  
Log.d("TAG", "news id is " + news.getId());  
  • 在save之前打印一下news的id,在save之后再打印一次,現在運行一下,打印結果如下所示:


可以看到,這條新聞確實已經存儲成功了,并且對應的id正是1,和我們前面打印的結果是一致的。

  • 不過LitePal的存儲功能顯示不僅僅只有這些用法,事實上,LitePal在存儲數據的時候默默幫我們做了很多的事情,比如多個實體類之間有關聯關系的話,我們不需要考慮在存儲數據的時候怎么去建立數據與數據之間的關聯,因為LitePal一切都幫我們做好了

  • 還是通過一個例子來看一下吧,Comment和News之間是多對一的關系,一條News中是可以包含多條評論的,因此我們就可以這樣寫:

Comment comment1 = new Comment();
 comment1.setContent("好評!");
 comment1.setPublishDate(new Date());
 comment1.save();
        
 Comment comment2 = new Comment();
  comment2.setContent("贊一個");
  comment2.setPublishDate(new Date());
  comment2.save();
        
   News news = new News();
   news.getCommentList().add(comment1);
   news.getCommentList().add(comment2);
   news.setTitle("第二條新聞標題");
   news.setContent("第二條新聞內容");
   news.setPublishDate(new Date());
   news.setCommentCount(news.getCommentList().size());
   news.save();

可以看到,這里先是存儲了一條comment1數據,然后存儲一條comment2數據,接著在存儲News之前先把剛才的兩個Comment對象添加到了News的commentList列表當中,這樣就表示這兩條Comment是屬于這個News對象的,最后再把News存儲到數據庫中,這樣它們之間的關聯關系就會自動建立了。讓我們查看數據庫表檢查一下吧,首先看一下news表,如下所示:

  • OK,第二條新聞已經成功存儲到news表中了,這條新聞的id是2。那么從哪里可以看出來關聯關系呢?我們在上一篇文章中學過,多對一關聯的時候,外鍵是存放在多方的,因此關聯關系我們要到comment表中去查看,如下所示:


可以看到,兩條評論都已經成功存儲到comment表中了,并且這兩條評論的news_id都是2,說明它們是屬于第二條新聞的。怎么樣,僅僅是在存儲數據之前建立好實體類之間的關系,再調用一下save()方法,那么數據之間的關聯關系就會自動建立了,是不是非常簡單?上面的代碼只是多對一情況的一種用法,還有一對一和多對多的情況,其實用法都是差不多的,相信你已經能舉一反三了。

  • 另外,LitePal對集合數據的存儲還專門提供了一個方法,比如說我們有一個News集合,那么應該怎樣去存儲這個集合中的每條News呢?傳統情況下可以這樣寫:
List<News> newsList;  
...  
for (News news : newsList) {  
    news.save();  
}  
  • 通過一個循環來遍歷出這個集合中的每一個News對象,然后逐個調用save()方法。這樣的寫法當然是可以的,但是效率會比較低,因為調用save()方法的時候除了會執行存儲操作之外,還會去分析News類的關聯關系,那么每次循環都去重新分析一遍關聯關系顯然是比較耗時的。因此,LitePal提供了一個saveAll()方法,專門用于存儲集合數據的,用法如下所示:
List<News> newsList;  
...  
DataSupport.saveAll(newsList);  

saveAll()方法接收一個Collection集合參數,只要把待存儲的集合數據傳入即可。這個方法可以完成和上面一段代碼完全一樣的功能,但效率卻會高得多,而且寫法也更加簡單。

  • 好了,這樣我們就把LitePal中提供的存儲操作的用法全部都學完了,下一節文章當中會開始講解更新和刪除操作的用法。

2、修改操作

  • 上文中,在 增刪改查四種操作中,我們已經把“增”學完了*,緊接著我們來學學修改操作。

2.1 傳統的方式修改操作

  • 上文中我們已經得知,SQLiteDatabase類中提供了一個 insert()方法用于插入數據,那么類似地,它還提供了 update()這個方法,用于修改數據。先來看一下update()方法的方法定義:
public int update(String table, ContentValues values, String whereClause, String[] whereArgs) 

update()方法接收四個參數,第一個參數是表名,第二個參數是一個封裝了待修改數據的ContentValues對象,第三和第四個參數用于指定修改哪些行,對應了SQL語句中的where部分

  • 那么 比如說我們想把news表中id為2的記錄的標題改成“今日iPhone6發布”,就可以這樣寫:
SQLiteDatabase db = dbHelper.getWritableDatabase();  
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6發布");  
db.update("news", values, "id = ?", new String[] {"2"});  

其作用相當于如下SQL語句:

update news set title='今日iPhone6發布' where id=2;  
  • 可以看出,比起直接使用SQL語句,update()方法的語義性明顯更強,也更容易讓人理解。

2.2 使用LitePal修改操作

方式一:

  • LitePal修改數據的API比較簡單,并沒有什么太多的用法,也比較好理解,方法都是定義在DataSupport類中的,我們先來看一下方法定義:
public static int update(Class<?> modelClass, ContentValues values, long id)  

這個靜態的update()方法接收三個參數,第一個參數是Class,傳入我們要修改的那個類的Class就好,第二個參數是ContentValues對象,這三個參數是一個指定的id,表示我們要修改哪一行數據

  • 那么比如說我們想把news表中id為2的記錄的標題改成“今日iPhone6發布”,就可以這樣寫:
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6發布");  
DataSupport.update(News.class, values, 2);  

可以看出,總體來講還是比原生的用法要簡單一些的,首先我們避免掉了要去獲取SQLiteDatabase對象的步驟,其次在指定修改某一條id記錄的時候只需要傳入這個id即可,語法更簡練

  • 那么有的朋友可能會問了,也許我想修改的是某一個條件下的所有數據,而不是僅僅修改某個id的數據,那該怎么辦呢?別擔心,LitePal還提供了另外一個簡便的方法,方法定義如下:
public static int updateAll(Class<?> modelClass, ContentValues values, String... conditions)  

updateAll()方法表示修改多行記錄,其中第一個參數仍然是Class,第二個參數還是ContentValues對象,第三個參數是一個conditions數組,用于指定修改哪些行的約束條件,返回值表示此次修改影響了多少行數據

  • 那么比如說我們想把news表中標題為“今日iPhone6發布”的所有新聞的標題改成“今日iPhone6 Plus發布”,就可以這樣寫:
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus發布");  
DataSupport.updateAll(News.class, values, "title = ?", "今日iPhone6發布");  

前面都沒什么好說的,重點我們看一下最后的這個 conditions數組,由于它的類型是一個String數組,我們可以在這里填入任意多個String參數,其中最前面一個String參數用于指定約束條件,后面所有的String參數用于填充約束條件中的占位符(即?號),比如約束條件中有一個占位符,那么后面就應該填寫一個參數,如果有兩個占位符,后面就應該填寫兩個參數,以此類推

  • 比如說我們想把news表中標題為“今日iPhone6發布”且評論數量大于0的所有新聞的標題改成“今日iPhone6 Plus發布”,就可以這樣寫:
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus發布");  
DataSupport.updateAll(News.class, values, "title = ? and commentcount > ?", "今日iPhone6發布", "0");  

可以看出,通過占位符的方式來實現條件約束明顯要比原生的API更加簡單易用。

  • 那么如果我們想把news表中所有新聞的標題都改成“今日iPhone6發布”,該怎么寫呢?其實這就更簡單了,只需要把最后的約束條件去掉就行了,如下所示:
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus發布");  
DataSupport.updateAll(News.class, values);  
  • 怎么樣,這種寫法是不是感覺語義性非常強?updateAll()方法在不指定約束條件的情況下就是修改所有行的數據,的的確確是update all了。

  • 當然有些朋友可能會覺得這樣用起來還是有點復雜,因為這個ContentValues對象很煩人,每次創建它的時候都要寫很多繁瑣的代碼。沒關系,LitePal也充分考慮了這種情況,提供了一種不需要ContentValues就能修改數據的方法,下面我們嘗試使用這種新方法來完成上述同樣的功能。

方式二:

  • 比如把news表中id為2的記錄的標題改成“今日iPhone6發布”,就可以這樣寫:
News updateNews = new News();  
updateNews.setTitle("今日iPhone6發布");  
updateNews.update(2); 
  • 這次我們并沒有用ContentValues,而是new出了一個News對象,把要修改的數據直接set進去,最后調用一下update()方法并傳入id就可以了。不僅不用創建ContentValues對象,連表名都不用指定了,因為News對象默認就是修改的news表。

  • 這是其中一種用法,那么如果我們想把news表中標題為“今日iPhone6發布”且評論數量大于0的所有新聞的標題改成“今日iPhone6 Plus發布”,就可以這樣寫:

News updateNews = new News();  
updateNews.setTitle("今日iPhone6發布");  
updateNews.updateAll("title = ? and commentcount > ?", "今日iPhone6發布", "0");  

還是非常好理解的,這里我就不再詳細解釋了。

  • 但是這種用法有一點需要注意,就是如果我們想把某一條數據修改成默認值,比如說將評論數修改成0,只是調用updateNews.setCommentCount(0)這樣是不能修改成功的,因為即使不調用這行代碼,commentCount的值也默認是0。所以如果想要將某一列的數據修改成默認值的話,還需要借助setToDefault()方法。用法也很簡單,在setToDefault()方法中傳入要修改的字段名就可以了(類中的字段名),比如說我們想要把news表中所有新聞的評論數清零,就可以這樣寫:
News updateNews = new News();  
updateNews.setToDefault("commentCount");  
updateNews.updateAll();  

3、刪除操作

3.1 傳統的刪除數據方式

  • 上文中我們已經得知,SQLiteDatabase類中提供了 insert()和update()方法,分別用于插入和修改數據,那么類似地,它還提供了 delete()這個方法,用于刪除數據

  • 接下來再看一下delete()方法的方法定義:

public int delete(String table, String whereClause, String[] whereArgs)  

delete()方法接收三個參數,第一個參數同樣是表名,第二和第三個參數用于指定刪除哪些行,對應了SQL語句中的where部分

  • 那么比如說我們想把news表中所有沒有評論的新聞都刪除掉,就可以這樣寫:
SQLiteDatabase db = dbHelper.getWritableDatabase();  
db.delete("news", "commentcount = ?", new String[] {"0"});  

其作用相當于如下SQL語句:

delete from news where commentcount=0;  
  • 由此可見,android給我們提供的這些幫助方法,在很大程度上確實簡化了不少數據庫操作的復雜度。不過LitePal顯然做到了更好,下面就讓我們學習一下如何使用LitePal來進行刪除操作。

3.2 使用LitePal刪除數據

方式一:

  • LitePal刪除數據的API和修改數據是比較類似的,但是更加的簡單一些,我們先來看一下DataSupport類中的方法定義,如下所示:
public static int delete(Class<?> modelClass, long id)  

delete()方法接收兩個參數,第一個參數是Class,傳入我們要刪除的那個類的Class就好,第二個參數是一個指定的id,表示我們要刪除哪一行數據

  • 那么比如說我們想刪除news表中id為2的記錄,就可以這樣寫:
DataSupport.delete(News.class, 2); 
  • 需要注意的是,這不僅僅會將news表中id為2的記錄刪除,同時還會將其它表中以news id為2的這條記錄作為外鍵的數據一起刪除掉,因為外鍵既然不存在了,那么這么數據也就沒有保留的意義了。

  • 說起來可能有點拗口,我們還是舉例看一下。比如news表中目前有兩條數據,如下圖所示:


  • 然后comment表中也有兩條數據,如下圖所示:


  • 其中comment表中兩條數據的外鍵都是2,指向的news表中id為2的這條記錄。那么下面我們執行如下刪除語句:

int deleteCount = DataSupport.delete(News.class, 2);  
Log.d("TAG", "delete count is " + deleteCount);  

其中delete()方法的返回值表示被刪除的記錄數,打印結果如下所示:


  • 可以看到,有三條記錄被刪除了,那我們再到news表中查詢一下:


  • OK,只剩下一條記錄了,id為2的那條記錄確實被刪除了。那么再到comment表中看一下呢,如下圖所示:


  • 數據全沒了!為什么呢?因為comment表中的兩條數據都是以news表中id為2的數據作為外鍵的,現在外鍵不存在了,那么這兩條數據自然也沒有存在的意義了,因此被刪除的記錄數一共是3條。這樣是不是就好理解了很多呢?

  • 除了刪除指定id的數據之外,DataSupport中也提供了一個 通過where語句來批量刪除數據的方法,先看一下方法定義:

public static int deleteAll(Class<?> modelClass, String... conditions)  

看起來很眼熟吧?非常簡單,deleteAll()方法接收兩個參數,第一個參數是Class,傳入我們要刪除的那個類的Class就好,第二個參數是一個conditions數組,用于指定刪除哪些行的約束條件,返回值表示此次刪除了多少行數據,用法和updateAll()方法是基本相同的

  • 那么比如說我們想把news表中標題為“今日iPhone6發布”且評論數等于0的所有新聞都刪除掉,就可以這樣寫:
DataSupport.deleteAll(News.class, "title = ? and commentcount = ?", "今日iPhone6發布", "0");  
  • 而如果我們想把news表中所有的數據全部刪除掉,就可以這樣寫:
DataSupport.deleteAll(News.class); 

在不指定約束條件的情況下,deleteAll()方法就會刪除表中所有的數據了

方式二:

  • 除了DataSupport類中提供的靜態刪除方法之外,還有一個刪除方法是作用于對象上的,即任何一個繼承自DataSupport類的實例都可以通過調用delete()這個實例方法來刪除數據。但 前提是這個對象一定是要持久化之后 的,一個非持久化的對象如果調用了delete()方法則不會產生任何效果。

  • 比如說下面這種寫法:

News news = new News();  
news.delete();  

這里new出了一個News對象,這個對象明顯是沒有持久化的,那么此時調用delete()方法則不會刪除任何數據

  • 但如果我們之前將這個對象持久化過了,那么再調用delete()方法就會把這個對象對應的數據刪除掉了,比如:
News news = new News();  
news.setTitle("這是一條新聞標題");  
news.setContent("這是一條新聞內容");  
news.save();  
...  
news.delete();  
  • 一個對象如果save過了之后,那就是持久化的了除了調用save()方法之外,通過DataSupport中提供的查詢方法從數據庫中查出來的對象也是經過持久化的,查詢的功能會在下節中講解。

  • 另外還有一個簡單的辦法可以幫助我們判斷一個對象是否是持久化之后的,DataSupport類中提供了一個isSaved()方法,這個方法返回true就表示該對象是經過持久化的,返回false則表示該對象未經過持久化。那么刪除一個對象對應的數據也就可以這樣寫了:

News news;  
...  
if (news.isSaved()) {  
    news.delete();  
}  
  • 好了,這樣我們就把LitePal中提供的刪除數據操作的用法基本都學習完了

4、查詢數據

  • 在所有的數據庫操作當中,查詢操作肯定是最復雜的,用法也是最多的,因此LitePal在查詢方面提供的API也是比較豐富,而且LitePal在查詢方面的API設計也是頗為藝術的。

4.1 傳統的查詢數據方式

  • 其實最傳統的查詢數據的方式當然是使用SQL語句了,Android當中也提供了直接使用原生SQL語句來查詢數據庫表的方法,即SQLiteDatabase中的 rawQuery() 方法,方法定義如下:
public Cursor rawQuery(String sql, String[] selectionArgs)  

其中,rawQuery()方法接收兩個參數,第一個參數接收的就是一個SQL字符串,第二個參數是用于替換SQL語句中占位符(?)的字符串數組。rawQuery()方法返回一個Cursor對象,所有查詢到的數據都是封閉在這個對象當中的,我們只要一一取出就可以了

  • 當然這種用法其實并不是很常用,因為相信大多數人都還是不喜歡編寫SQL語句的。所以,android專門提供了一種封裝好的API,使得我們不用編寫SQL語句也能查詢出數據,即SQLiteDatabase中的 query()方法。query()提供了三個方法重載,其中參數最少的一個也有七個參數,我們來看下方法定義:
public Cursor query(String table, String[] columns, String selection,  
            String[] selectionArgs, String groupBy, String having,  
            String orderBy)  

其中第一參數是表名,表示我們希望從哪張表中查詢數據。第二個參數用于指定去查詢哪幾列,如果不指定則默認查詢所有列。第三、第四個參數用于去約束查詢某一行或某幾行的數據,不指定則默認是查詢所有行的數據。第五個參數用于指定需要去group by的列,不指定則表示不對查詢結果進行group by操作。第六個參數用于對group by之后的數據進行進一步的過濾,不指定則表示不進行過濾。第七個參數用于指定查詢結果的排序方式,不指定則表示使用默認的排序方式

  • 這個方法是query()方法最少的一個方法重載了,另外還有兩個方法重載分別是八個和九個參數。雖說這個方法在Android數據庫表查詢的時候非常常用,但 重多的參數讓我們在理解這個方法的時候可能會很費力,另外使用起來的時候也會相當的不爽。比如說,我們想查詢news表中的所有數據,就應該要這樣寫:
SQLiteDatabase db = dbHelper.getWritableDatabase();  
Cursor cursor = db.query("news", null, null, null, null, null, null);  

可以看到,將第一個表名參數指定成news,然后后面的六個參數我們都用不到,就全部指定成null。

  • 那如果是我們想查詢news表中所有評論數大于零的新聞該怎么寫呢?代碼如下所示:
SQLiteDatabase db = dbHelper.getWritableDatabase();  
Cursor cursor = db.query("news", null, "commentcount>?", new String[]{"0"}, null, null, null);  

由于 第三和第四個參數是用于指定約束條件的,所以我們在第三個參數中指明了commentcount>?,然后在第四個參數中通過一個String數組來替換占位符,這樣查到的結果就是news表中所有評論數大于零的新聞了。那么其它的幾個參數呢?仍然用不到,所以還是只能傳null。

  • 然后我們可以看到,query()方法的返回值是一個Cursor對象,所有查詢到的數據都是封裝在這個對象中的,所以我們還需要將數據逐一從Cursor對象中取出,然后設置到News實體類當中,如下所示:
List<News> newsList = new ArrayList<News>();  
if (cursor != null && cursor.moveToFirst()) {  
    do {  
        int id = cursor.getInt(cursor.getColumnIndex("id"));  
        String title = cursor.getString(cursor.getColumnIndex("title"));  
        String content = cursor.getString(cursor.getColumnIndex("content"));  
        Date publishDate = new Date(cursor.getLong(cursor.getColumnIndex("publishdate")));  
        int commentCount = cursor.getInt(cursor.getColumnIndex("commentcount"));  
        News news = new News();  
        news.setId(id);  
        news.setTitle(title);  
        news.setContent(content);  
        news.setPublishDate(publishDate);  
        news.setCommentCount(commentCount);  
        newsList.add(news);  
    } while (cursor.moveToNext());  
}  
  • 這大概就是傳統查詢數據方式的用法了,總體來看,用法確實非常不友好,尤其是 query()方法冗長的參數列表,即使我們用不到那些參數,也必須要傳入許多個null。另外,查詢到的數據還都只是封裝到了一個Cursor對象中,我們還需要將數據一一取出然后再set到實體類對象當中。麻煩嗎?可能你覺得不麻煩,因為你已經習慣了這種用法。但是習慣總是可以改變的,也許當你體驗了LitePal中查詢API給我們帶來的便利之后,就會有了新的看法了,那么下面我們就一起來體驗一下LitePal的查詢藝術。

4.2 通過LitePal查詢數據

  • LitePal在查詢方面提供了非常豐富的API,功能多種多樣,基本上已經能夠滿足我們平時所有的查詢需求了。不僅如此, LitePal在查詢API的設計方面也是非常用心,摒棄了原生query()方法中繁瑣的參數列表,而是改用了一種更為靈巧的方式——連綴查詢。除此之外,LitePal查詢的結果也不再返回Cursor對象,然后再由開發者自己去逐個取出,而是直接返回封裝好的對象。這些改變都使得查詢數據變得更加簡單,也更加合理,那么下面我們就來完整地學習一下LitePal中查詢數據的所有用法。

4.2.1 簡單查詢

  • 比如說現在我們想實現一個最簡單的功能,查詢news表中id為1的這條記錄,使用LitePal就可以這樣寫:
News news = DataSupport.find(News.class, 1);  

天吶!有沒有覺得太輕松了?僅僅一行代碼,就可以把news表中id為1的記錄查出來了,而且結果還是 自動封裝到 News 對象 里的,也 不需要我們手動再從Cursor中去解析。如果是用原生的SQL語句,或者query()方法來寫,至少要20行左右的代碼才能完成同樣的功能!

  • 那我們先冷靜一下,來分析分析這個 find()方法。可以看到,它的參數列表也比較簡單,只接收兩個參數,第一個參數是一個泛型類,也就是說我們在這里指定什么類,返回的對象就是什么類,所以這里傳入News.class,那么返回的對象也就是News了。第二個參數就更簡單了,就是一個id值,如果想要查詢id為1的記錄就傳1,想查id為2的記錄就傳2,以此類推

  • 本來一個還算頗為復雜的功能,通過LitePal之后就變得這么簡單了!那么你可能已經迫不及待地想要學習更多LitePal中更多的查詢用法了,別著急,我們一個個來看。

  • 你也許遇到過以下場景,在某些情況下,你需要取出表中的第一條數據,那么傳統的做法是怎么樣的呢?在SQL語句中指定一個limit值,然后獲取返回結果的第一條記錄。但是在LitePal中不用這么麻煩,比如我們想要獲取news表中的第一條數據,只需要這樣寫:

News firstNews = DataSupport.findFirst(News.class); 
  • OK,語義性非常強吧,讓人一眼就看懂是什么意思了,只需調用findFirst()方法,然后傳入News類,得到的就是news表中的第一條數據了。

  • 那我們舉一翻三,如果是想要獲取News表中的最后一條數據該怎么寫呢?同樣簡單,如下所示:

News lastNews = DataSupport.findLast(News.class);  
  • 因為獲取表中第一條或者是最后一條數據的場景比較常見,所以LitePal特意提供了這兩個方法來方便我們的操作

  • 那么我們看到這里,目前都只是查詢單條數據的功能,如果想要查詢多條數據該怎么辦呢?比如說,我們想把news表中id為1、3、5、7的數據都查出來,該怎么寫呢?也許有的朋友會比較聰明,立馬就想到可以一個個去查,就調用四次find()方法嘛,然后把1、3、5、7這四個id分別傳進去不就可以了。沒錯,這樣做完全是可以的,而且效率也并不低,但是LitePal給我們提供了一個更簡便的方法——findAll()。這個方法的用法和find()方法是非常類似的,只不過它可以指定多個id,并且返回值也不再是一個泛型類對象,而是一個泛型類集合,如下所示:

List<News> newsList = DataSupport.findAll(News.class, 1, 3, 5, 7);  

可以看到,首先我們是調用的findAll()方法,然后這個方法的第一個參數仍然是指定的泛型類,但是后面的參數就很隨意了,你可以傳入任意個id進去,findAll()方法會把所有傳入的id所對應的數據全部查出來,然后一起返回到List<News>這個泛型集合當中。

  • 雖說這個語法設計算是相當人性化,但是在有些場景或許不太適用,因為可能要你要查詢的多個id已經封裝到一個數組里了。那么沒關系,findAll()方法也是接收數組參數的,所以說同樣的功能你也可以這樣寫:
long[] ids = new long[] { 1, 3, 5, 7 };  
List<News> newsList = DataSupport.findAll(News.class, ids);  
  • 看到這里,那有的朋友可能會奇怪了,說findAll()方法不應該是查詢所有數據的意思嗎?怎么總是查詢幾個id所對應數據呢?哈!這個問題問得好,因為findAll()方法也是可以查詢所有數據的,而且查詢所有數據的寫法更簡單,只需要這樣寫:
List<News> allNews = DataSupport.findAll(News.class);  

看到沒有,我們只需要把后面的參數都去掉,在不指定具體id的情況下,findAll()方法查詢出的就是news表中的所有數據了,是不是語義性非常強?

  • 而且大家不要以為剛才這些都只是findAll()的幾個方法重載而已,實際上剛才我們的這幾種用法都是調用的同一個findAll()方法!一個方法卻能夠實現多種不同的查詢效果,并且語義性也很強,讓人一看就能理解,這就是LitePal的查詢藝術!

4.2.2 連綴查詢

  • 當然了,LitePal給我們提供的查詢功能還遠遠不只這些,好戲還在后頭。相信大家現在也已經發現了,我們目前的查詢功能都是基于id來進行查詢的,并不能隨意地指定查詢條件。那么怎樣才能指定查詢條件呢?讓我們回想一下傳統情況應該怎么做,query()方法中接收七個參數,其中第三和第四個參數就是用于指定查詢條件的,然后其它幾個參數都填null就可以了。但是呢,前面我們已經痛批過了這種寫法,因為冗長的參數列表太過繁瑣,那么LitePal又是怎么解決這個問題的呢?我們現在就來學習一下。

  • 為了避免冗長的參數列表,LitePal采用了一種非常巧妙的解決方案,叫作連綴查詢,這種查詢很靈活,可以根據我們實際的查詢需求來動態配置查詢參數。 那這里舉個簡單的例子,比如我們想查詢news表中所有評論數大于零的新聞,就可以這樣寫:

List<News> newsList = DataSupport.where("commentcount > ?", "0").find(News.class);  
  • 可以看到,首先是調用了DataSupport的where()方法,在這里指定了查詢條件。where()方法接收任意個字符串參數,其中第一個參數用于進行條件約束,從第二個參數開始,都是用于替換第一個參數中的占位符的。那這個where()方法就對應了一條SQL語句中的where部分。
  • 接著我們在where()方法之后直接連綴了一個find()方法,然后在這里指定一個泛型類,表示用于查詢哪張表。那么上面的一段代碼,查詢出的結果和如下SQL語句是相同的:
select * from users where commentcount > 0;  
  • 但是這樣會將news表中所有的列都查詢出來,也許你并不需要那么多的數據,而是只要title和content這兩列數據。那么也很簡單,我們只要再增加一個連綴就行了,如下所示:
List<News> newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0").find(News.class);  
  • 可以看到,這里我們新增了一個select()方法,這個方法接收任意個字符串參數,每個參數要求對應一個列名,這樣就只會把相應列的數據查詢出來了,因此select()方法對應了一條SQL語句中的select部分
  • 那么上面的一段代碼,查詢出的結果和如下SQL語句是相同的:
select title,content from users where commentcount > 0; 
  • 很好玩吧?不過這還不算完呢,我們還可以繼續連綴更多的東西。比如說,我希望將查詢出的新聞按照發布的時間倒序排列,即最新發布的新聞放在最前面,那就可以這樣寫:
List<News> newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").find(News.class);  
  • order()方法中接收一個字符串參數,用于指定查詢出的結果按照哪一列進行排序,asc表示正序排序,desc表示倒序排序,因此order()方法對應了一條SQL語句中的order by部分。
  • 那么上面的一段代碼,查詢出的結果和如下SQL語句是相同的:
select title,content from users where commentcount > 0 order by publishdate desc;  
  • 然后呢,也許你并不希望將所有條件匹配的結果一次性全部查詢出來,因為這樣數據量可能會有點太大了,而是希望只查詢出前10條數據,那么使用連綴同樣可以輕松解決這個問題,代碼如下所示:
List<News> newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").limit(10).find(News.class);  
  • 這里我們又連綴了一個limit()方法,這個方法接收一個整型參數,用于指定查詢前幾條數據,這里指定成10,意思就是查詢所有匹配結果中的前10條數據。
  • 那么上面的一段代碼,查詢出的結果和如下SQL語句是相同的:
select title,content from users where commentcount > 0 order by publishdate desc limit 10;  
  • 剛才我們查詢到的是所有匹配條件的前10條新聞,那么現在我想對新聞進行分頁展示,翻到第二頁時,展示第11到第20條新聞,這又該怎么實現呢?沒關系,在LitePal的幫助下,這些功能都是十分簡單的,只需要再連綴一個偏移量就可以了,如下所示:
List<News> newsList = DataSupport.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").limit(10).offset(10)  
        .find(News.class);  
  • 可以看到,這里我們又添加了一個offset()方法,用于指定查詢結果的偏移量,這里指定成10,就表示偏移十個位置,那么原來是查詢前10條新聞的,偏移了十個位置之后,就變成了查詢第11到第20條新聞了,如果偏移量是20,那就表示查詢第21到第30條新聞,以此類推。因此,limit()方法和offset()方法共同對應了一條SQL語句中的limit部分。
  • 那么上面的一段代碼,查詢出的結果和如下SQL語句是相同的:
select title,content from users where commentcount > 0 order by publishdate desc limit 10,10;  
  • 這大概就是LitePal中連綴查詢的所有用法了。看出區別了吧?這種查詢的好處就在于,我們可以隨意地組合各種查詢參數,需要用到的時候就把它們連綴到一起,不需要用到的時候不用指定就可以了。對比一下query()方法中那冗長的參數列表,即使我們用不到那些參數,也必須要傳null,是不是明顯感覺LitePal中的查詢更加人性化?

4.2.3 激進查詢

  • 不過,上述我們的所有用法中,都只能是查詢到指定表中的數據而已,關聯表中數據是無法查到的,因為LitePal默認的模式就是懶查詢,當然這也是推薦的查詢方式。那么,如果你真的非常想要一次性將關聯表中的數據也一起查詢出來,當然也是可以的,LitePal中也支持激進查詢的方式,下面我們就來一起看一下。

  • 不知道你有沒有發現,剛才我們所學的每一個類型的 find()方法,都對應了一個帶有isEager參數的方法重載,這個參數相信大家一看就明白是什么意思了,設置成true就表示激進查詢,這樣就會把關聯表中的數據一起查詢出來了。

  • 比如說,我們想要查詢news表中id為1的新聞,并且把這條新聞所對應的評論也一起查詢出來,就可以這樣寫:

News news = DataSupport.find(News.class, 1, true);  
List<Comment> commentList = news.getCommentList();  

可以看到,這里并沒有什么復雜的用法,也就是在find()方法的最后多加了一個true參數,就表示使用激進查詢了。這會將和news表關聯的所有表中的數據也一起查出來,那么comment表和news表是多對一的關聯,所以使用激進查詢一條新聞的時候,那么該新聞所對應的評論也就一起被查詢出來了。

  • 激進查詢的用法非常簡單,就只有這么多,其它find()方法也都是同樣的用法,就不再重復介紹了。但是這種查詢方式LitePal并不推薦,因為如果一旦關聯表中的數據很多,查詢速度可能就會非常慢。而且激進查詢只能查詢出指定表的關聯表數據,但是沒法繼續迭代查詢關聯表的關聯表數據。因此,這里我建議大家還是使用默認的懶加載更加合適,至于如何查詢出關聯表中的數據,其實只需要在模型類中做一點小修改就可以了。修改News類中的代碼,如下所示:
public class News extends DataSupport{  
    ...  
public List<Comment> getComments() {  
        return DataSupport.where("news_id = ?", String.valueOf(id)).find(Comment.class);  
    }  
}  

可以看到,我們在News類中添加了一個getComments()方法,而這個方法的內部就是使用了一句連綴查詢,查出了當前這條新聞對應的所有評論。改成這種寫法之后,我們就可以將關聯表數據的查詢延遲,當我們需要去獲取新聞所對應的評論時,再去調用News的getComments()方法,這時才會去查詢關聯數據。這種寫法會比激進查詢更加高效也更加合理。

4.2.4 原生查詢

  • 相信你已經體會到,LitePal在查詢方面提供的API已經相當豐富了。但是,也許你總會遇到一些千奇百怪的需求,可能使用LitePal提供的查詢API無法完成這些需求。沒有關系,因為即使使用了LitePal,你仍然可以使用原生的查詢方式(SQL語句)來去查詢數據。DataSuppport類中還提供了一個findBySQL()方法,使用這個方法就能通過原生的SQL語句方式來查詢數據了,如下所示:
Cursor cursor = DataSupport.findBySQL("select * from news where commentcount>?", "0"); 

findBySQL()方法接收任意個字符串參數,其中第一個參數就是SQL語句,后面的參數都是用于替換SQL語句中的占位符的,用法非常簡單。另外,findBySQL()方法返回的是一個Cursor對象,這和原生SQL語句的用法返回的結果也是相同的。

  • 好了,這樣我們就把LitePal中提供的查詢數據的方法全部都學完了,

5、聚合函數

  • 在上文當中,我們已經把LitePal查詢操作的所有用法都學習完了,很顯然,LitePal幫我們提供了非常強大的查詢API,使得我們可以極度輕松地完成各種類型的查詢。但是呢,在SQL語句當中,有一種查詢是比較特殊的,就是聚合函數查詢,它不像傳統查詢一樣是將表中的某些列的數據查詢出來,而是將查詢結果進行聚合和統計,最終將統計后的結果進行返回。因此,任何一個關系型數據庫中都會提供像count()、sum()等聚合函數。那么不出你所料,LitePal當中也是對這些聚合函數都進行了封裝,讓我們的操作可以變得更加簡單。

5.1 傳統的聚合函數用法

  • 雖說是聚合函數,但它的用法其實和傳統的查詢還是差不多的,即 仍然使用的是select語句。但是在select語句當中我們通常不會再去指定列名,而是將需要統計的列名傳入到聚合函數當中,那么執行select語句使用的還是SQLiteDatabase中的 rawQuery()方法

  • 下面我們來嘗試一下,比如說想要統計news表中一共有多少行,就可以這樣寫:

SQLiteDatabase db = dbHelper.getWritableDatabase();  
Cursor c = db.rawQuery("select count(1) from news", null);  
if (c != null && c.moveToFirst()) {  
    int count = c.getInt(0);  
    Log.d("TAG", "result is " + count);  
}  
c.close(); 

可以看到,在rawQuery()方法中我們指定了一個聚合查詢語句,其中count(1)就是用于去統計一共有多少行的。當然這里并不一定要用count(1),使用count(*)或者count(主鍵)都可以。然后rawQuery()方法返回的是一個Cursor對象,我們從這個Cursor當中取出第一行第一列的數據,這也就是統計出的結果了。

  • 那如果我們想要統計出news表中評論的總數量該怎么寫呢?代碼如下所示:
SQLiteDatabase db = dbHelper.getWritableDatabase();  
Cursor c = db.rawQuery("select sum(commentcount) from news", null);  
if (c != null && c.moveToFirst()) {  
    int count = c.getInt(0);  
    Log.d("TAG", "result is " + count);  
}  
c.close();  

我們發現,代碼基本是非常相似的,只不過查詢語句當中count()函數替換成了sum()函數。當然了,sum()函數要求傳入一個指定的列名,表示我們要匯總這一列的總合,因此這里我們傳入了commentcount這一列。

  • 其它聚合函數的用法也是類似的,就不一一列舉了。由此我們可以總結出一些結論,聚合函數都是要使用rawQuery()方法進行SQL查詢,然后結果會封裝到Cursor對象當中,接著我們再從Cursor中將結果取出。雖說你可能覺得上面的用法已經足夠簡單了,因為總共也就只寫了六七行代碼,但是你有沒有想過更簡單的寫法,比如說只用一行代碼就完成聚合查詢操作。你沒有看錯,就是一行代碼,LitePal讓這些都成為了可能,那么下面我們就來學習一下LitePal中聚合函數的用法。

5.2 使用LitePal的聚合函數

  • 特別提醒:LitePal中所有的聚合函數都是支持連綴的

  • LitePal中一共提供了count()、sum()、average()、max()和min()這五種聚合函數,基本上已經將SQL語句當中最常用的幾種聚合函數都覆蓋了,那么下面我們就來對這五種聚合函數的用法一一進行學習。

統計函數:count()

  • count()方法主要是用于統計行數的,剛才演示了如何通過SQL語句來統計news表中一共有多少行,那么下面我們來看一下如何通過LitePal來實現同樣的功能,代碼如下所示:
int result = DataSupport.count(News.class); 
  • 你沒有看錯!就是這樣一行代碼就可以了。調用DataSupport類當中的count()方法,count()方法接收一個Class參數,用于指定去統計哪張表當中的數據,然后返回值是一個整型數據,也就是統計出的結果了。

  • 除此之外,LitePal中所有的聚合函數都是支持連綴的,也就是說我們可以在統計的時候加入條件語句。比如說想要統計一共有多少條新聞是零評論的,就可以這樣寫:

int result = DataSupport.where("commentcount = ?", "0").count(News.class); 
  • 這個用法和我們在上文當中學到的連綴查詢是比較像的,在DataSupport類中首先指定一個where語句用于條件約束,然后連綴一個count()方法,這樣統計出的就是滿足條件語句的結果了。連綴不僅適用于count()方法,也同樣適用于下面我們將要介紹的所有方法,但由于用法都是相同的,后面就不再重復介紹了。

求和函數:sum()

  • 看完了count()方法應該是覺得非常簡單吧,剩下的幾個聚合函數也是同樣簡單的,我們繼續來學習一下。

  • sum()方法主要是用于對結果進行求合的,比如說我們想要統計news表中評論的總數量,就可以這樣寫:

int result = DataSupport.sum(News.class, "commentcount", int.class);  

sum()方法的參數要稍微多一點,我們來一一看下。第一個參數很簡單,還是傳入的Class,用于指定去統計哪張表當中的數據。第二個參數是列名,表示我們希望對哪一個列中的數據進行求合。第三個參數用于指定結果的類型,這里我們指定成int型,因此返回結果也是int型。

  • 需要注意的是,sum()方法只能對具有運算能力的列進行求合,比如說整型列或者浮點型列,如果你傳入一個字符串類型的列去求合,肯定是得不到任何結果的,這時只會返回一個0作為結果。

平均數:average()

  • average()方法主要是用于統計平均數的,比如說我們想要統計news表中平均每條新聞有多少評論,就可以這樣寫:
double result = DataSupport.average(News.class, "commentcount"); 

其中average()方法接收兩個參數,第一個參數不用說,仍然是Class。第二個參數用于指定列名的,表示我們想要統計哪一列的平均數。需要注意的是,這里返回值的類型是double型,因為平均數基本上都是會帶有小數的,用double類型可以最大程序保留小數位的精度。

  • 同樣地,average()方法也只能對具有運算能力的列進行求平均值,如果你傳入了一個字符串類型的列,也是無法得到任何結果的,這時同樣只會返回一個0作為結果。

最大值:max()

  • max()方法主要用于求出某個列中最大的數值,比如我們想要知道news表中所有新聞里面最高的評論數是多少,就可以這樣寫:
int result = DataSupport.max(News.class, "commentcount", int.class); 

可以看到,max()方法接收三個參數,第一個參數同樣還是Class,用于指定去統計哪張表當中的數據。第二個參數是列名,表示我們希望統計哪個列中的最大值。第三個參數用于指定結果的類型,根據實際情況來選擇傳入哪種類型就行了。

  • 那么不用多說,max()方法也只能對具有運算能力的列進行求最大值的,希望你在使用的時候能夠謹記這一點。

最小值:min()

  • min()方法主要用于求出某個列中最小的數值,比如我們想要知道news表中所有新聞里面最少的評論數是多少,就可以這樣寫:
int result = DataSupport.min(News.class, "commentcount", int.class); 
  • min()方法和max()方法的用法基本上是一模一樣的,參數也是完全相同,只是方法名變了一下。它們一個是求出某一列中的最大值,一個是求出某一列中的最小值,僅此而已。

  • 現在我們已經將LitePal中所有聚合函數的用法全部都學習完了,怎么樣,是不是感覺非常的簡單?學完之后相信大家也意識到我在開篇的時候并不是在吹牛皮了,確確實實只需要一行代碼就可以完成各種聚合查詢操作了,上面任何一個統計操作我們都沒有寫到第二行代碼。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容