LitePal - Android開源數據庫框架的CRUD操作筆記

LitePal

LitePal是GitHub上一款開源的Android數據庫框架,簡介易用并且已支持kotlin,這里對數據庫操作做一個筆記,并記錄郭霖大神每次的升級帶來了哪些功能。

準備

在項目的 build.gradle 文件添加依賴

dependencies {
    implementation 'org.litepal.android:java:3.0.0'
}

Kotlin

dependencies {
    implementation 'org.litepal.android:kotlin:3.0.0'
}

1. 使用LitePal建表

Define the models first. For example you have two models, Album and Song. The models can be defined as below:

  • unique:唯一性
  • defaultValue:默認值
  • nullable:是否允許空
  • ignore:只關注主鍵對應記錄是不存在,無則添加,有則忽略
public class Album extends LitePalSupport {

    @Column(unique = true, defaultValue = "unknown")
    private String name;

    private float price;

    private byte[] cover;

    private List<Song> songs = new ArrayList<Song>();

    // generated getters and setters.
    ...
}
public class Song extends LitePalSupport {

    @Column(nullable = false)
    private String name;

    private int duration;

    @Column(ignore = true)
    private String uselessField;

    private Album album;

    // generated getters and setters.
    ...
}

Then add these models into the mapping list in litepal.xml:

<list>
    <mapping class="org.litepal.litepalsample.model.Album"></mapping>
    <mapping class="org.litepal.litepalsample.model.Song"></mapping>
</list>

OK! The tables will be generated next time you operate database. For example, gets the SQLiteDatabase with following codes:

SQLiteDatabase db = Connector.getDatabase();

2. 使用LitePal存儲數據

繼承了LitePalSupport類之后,這些實體類就擁有了進行CRUD操作的能力,

  • 存儲一條數據到news表當中
News news = new News();  
news.setTitle("這是一條新聞標題");  
news.setContent("這是一條新聞內容");  
news.setPublishDate(new Date());  
news.save();  

save()方法還是有返回值的,我們可以根據返回值來判斷存儲是否成功

if (news.save()) {  
    Toast.makeText(context, "存儲成功", Toast.LENGTH_SHORT).show();  
} else {  
    Toast.makeText(context, "存儲失敗", Toast.LENGTH_SHORT).show();  
} 

如果存儲失敗的話就拋出異常,而不是返回一個false,那就可以使用saveThrows()方法來代替

News news = new News();  
news.setTitle("這是一條新聞標題");  
news.setContent("這是一條新聞內容");  
news.setPublishDate(new Date());  
news.saveThrows(); 

當調用save()方法或saveThrows()方法存儲成功之后,LitePal會自動將該條數據對應的id賦值到實體類的id字段上。
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(); 
  • LitePal提供了一個saveAll()方法,專門用于存儲集合數據
List<News> newsList;  
...  
LitePal.saveAll(newsList); 
  • LitePal 1.5.0版本中新增了一個saveOrUpdate()方法,專門用來處理這種不存在就存儲,已存在就更新的需求。
Person p = new Person();
p.setName("小明");
p.setAge(16);
p.saveOrUpdate("name=?", p.getName());

調用saveOrUpdate()方法后,LitePal內部會自動判斷,如果表中已經存在小明這條記錄了,就會自動更新,如果不存在的話,就會自動插入。

  • 集合數據存儲

比如說現在我們的Album實體類中有一個集合字段:

public class Album extends DataSupport {
    String name;
    List<String> titles = new ArrayList<>;
    // 生成get set方法
}

下面我們將這個Album存儲到數據庫中,如下所示:

Album album = new Album();
album.setName("范特西");
album.getTitles().add("愛在西元前");
album.getTitles().add("雙截棍");
album.getTitles().add("安靜");
album.save();

LitePal會額外進行一個操作,就是創建一個album_titles表,并將集合中的數據存儲在這里

當我們去查詢album數據的時候,會自動將它所關聯的集合數據一起查出來:

Album album = DataSupport.findFirst(Album.class);
List<String> titles = album.getTitles();
for (String title : titles) {
    Log.d(TAG, "title is " + title);
}

除了支持List<String>集合之外,還有List<Integer>、List<Boolean>、List<Long>、List<Float>、List<Double>、List<Character>這幾種類型的集合也是支持的。

如果你不希望你的集合數據被存儲到數據庫中的話,可以使用注解的方式將它忽略掉:

public class Album extends DataSupport {
    String name;
    
    @Column(ignore = true)
    List<String> titles = new ArrayList<>;
    // 生成get set方法
}
  • 如果這只是一個獨立的Model,和其它Model沒有任何關聯,那么就可以調用saveFast()方法,從而大大提升存儲效率,saveFast()方法的調用方式和save()方法是完全一樣的:
Product product = new Product();
product.setName("Android Phone");
product.setPrice(1999.99);
product.saveFast();  
  • byte[]類型的字段靈活性非常高,它可以用來存儲圖片,但又不僅限于存儲圖片,任何二進制的數據都是可以存儲的,比如一段小語音,或者是小視頻,但不建議在手機數據庫中存儲較大的二進制數據。添加一個byte[]類型的字段:
public class Product extends DataSupport {    
        
    private String name;    
          
    private double price;    
        
    private byte[] image; 
    // generated getters and setters.
    ...
}

存儲一張圖片時就可以這樣寫:

byte[] imageBytes = getImageBytesFromSomewhere();
Product product = new Product();
product.setName("Android Phone");
product.setPrice(1999.99);
product.setImage(imageBytes);
product.saveFast();

在查詢的時候byte數據會影響效率,只查詢name和price這兩列,image這一列數據是不會被查詢出來的,因此就完全不會影響效率了

Product product = DataSupport.select("name", "price").where("id = ?", id).find(Product.class);

3. 使用LitePal修改數據

  • 把news表中id為2的記錄的標題改成“今日iPhone6發布”
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6發布");  
LitePal.update(News.class, values, 2);  
News updateNews = new News();  
updateNews.setTitle("今日iPhone6發布");  
updateNews.update(2); 
  • 把news表中標題為“今日iPhone6發布”的所有新聞的標題改成“今日iPhone6 Plus發布”
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus發布");  
LitePal.updateAll(News.class, values, "title = ?", "今日iPhone6發布"); 
  • 把news表中標題為“今日iPhone6發布”且評論數量大于0的所有新聞的標題改成“今日iPhone6 Plus發布”
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus發布");  
LitePal.updateAll(News.class, values, "title = ? and commentcount > ?", "今日iPhone6發布", "0");  
News updateNews = new News();  
updateNews.setTitle("今日iPhone6發布");  
updateNews.updateAll("title = ? and commentcount > ?", "今日iPhone6發布", "0");  
  • 把news表中所有新聞的標題都改成“今日iPhone6發布”
ContentValues values = new ContentValues();  
values.put("title", "今日iPhone6 Plus發布");  
LitePal.updateAll(News.class, values);  
  • 把news表中所有新聞的評論數清零
News updateNews = new News();  
updateNews.setToDefault("commentCount");  
updateNews.updateAll();  

4. 使用LitePal刪除數據

  • 刪除news表中id為2的記錄
LitePal.delete(News.class, 2);  

這不僅僅會將news表中id為2的記錄刪除,同時還會將其它表中以news id為2的這條記錄作為外鍵的數據一起刪除掉,因為外鍵既然不存在了,那么這么數據也就沒有保留的意義了。

  • 把news表中標題為“今日iPhone6發布”且評論數等于0的所有新聞都刪除掉
LitePal.deleteAll(News.class, "title = ? and commentcount = ?", "今日iPhone6發布", "0");  
  • 把news表中所有的數據全部刪除掉
LitePal.deleteAll(News.class);  

5. 使用LitePal查詢數據

  • 查詢news表中id為1的這條記錄
News news = LitePal.find(News.class, 1);  
  • 獲取news表中的第一條數據
News firstNews = LitePal.findFirst(News.class);
  • 獲取News表中的最后一條數據
News lastNews = LitePal.findLast(News.class);  
  • 獲取news表中id為1、3、5、7的數據
List<News> newsList = LitePal.findAll(News.class, 1, 3, 5, 7);  
long[] ids = new long[] { 1, 3, 5, 7 };  
List<News> newsList = LitePal.findAll(News.class, ids);  
  • 查詢所有數據
List<News> allNews = LitePal.findAll(News.class);  
  • 查詢news表中所有評論數大于零的新聞
    where()方法接收任意個字符串參數,其中第一個參數用于進行條件約束,從第二個參數開始,都是用于替換第一個參數中的占位符的。
List<News> newsList = LitePal.where("commentcount > ?", "0").find(News.class); 

這樣會將news表中所有的列都查詢出來,如果只要title和content這兩列數據

List<News> newsList = LitePal.select("title", "content")  
        .where("commentcount > ?", "0").find(News.class);  
  • 查詢出的新聞按照發布的時間倒序排列
List<News> newsList = LitePal.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").find(News.class);  
  • 只查詢出前10條數據
List<News> newsList = LitePal.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").limit(10).find(News.class);  
  • 對新聞進行分頁展示,翻到第二頁時,展示第11到第20條新聞
List<News> newsList = LitePal.select("title", "content")  
        .where("commentcount > ?", "0")  
        .order("publishdate desc").limit(10).offset(10)  
        .find(News.class);  

offset()方法,用于指定查詢結果的偏移量,這里指定成10,就表示偏移十個位置,那么原來是查詢前10條新聞的,偏移了十個位置之后,就變成了查詢第11到第20條新聞了,如果偏移量是20,那就表示查詢第21到第30條新聞,以此類推。
查詢出的結果和如下SQL語句是相同的

select title,content from users where commentcount > 0 order by publishdate desc limit 10,10; 
  • 判斷某條數據存不存在
if (DataSupport.isExist(Student.class, "name = ?", "Jimmy")) {
  // 存在名叫Jimmy的學生
} else {
  // 不存在名叫Jimmy的學生
}

  • 當目標數據不存在的時候才將數據存入到數據庫,比如user表中要求用戶名必須唯一,那么就可以這樣寫:
User user = new User();
user.setUsername("Tom");
user.setPassword("123456")
user.saveIfNotExist("username = ?", "Tom")

激進查詢

查詢news表中id為1的新聞,并且把這條新聞所對應的評論也一起查詢出來

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

建議使用默認的懶加載更加合適,至于如何查詢出關聯表中的數據,其實只需要在模型類中做一點小修改就可以了。修改News類中的代碼,如下所示:

public class News extends LitePalSupport{  
      
    ...  
  
    public List<Comment> getComments() {  
        return LitePal.where("news_id = ?", String.valueOf(id)).find(Comment.class);  
    }  
      
}  

這種寫法會比激進查詢更加高效也更加合理

原生查詢

Cursor cursor = LitePal.findBySQL("select * from news where commentcount>?", "0");  

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

6. 使用LitePal的聚合函數

統計news表中一共有多少行

int result = LitePal.count(News.class);  

統計一共有多少條新聞是零評論的

int result = LitePal.where("commentcount = ?", "0").count(News.class);  

統計news表中評論的總數量
第一個參數很簡單,還是傳入的Class,用于指定去統計哪張表當中的數據。第二個參數是列名,表示我們希望對哪一個列中的數據進行求合。第三個參數用于指定結果的類型,這里我們指定成int型,因此返回結果也是int型。

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

統計news表中平均每條新聞有多少評論

double result = LitePal.average(News.class, "commentcount");  

某個列中最大的數值

int result = LitePalSupport.max(News.class, "commentcount", int.class); 

某個列中最小的數值

int result = LitePalSupport.min(News.class, "commentcount", int.class);  

7. 異步操作數據庫

所有的CRUD方法都加入了一個Async的副本方法,比如說原來有一個find()方法,現在就會多出一個findAsycn()方法,原來有一個save()方法,現在就會多出一個saveAsync()方法。如果你想要進行異步數據庫操作的時候,只要去調用原API相對應的Async副本方法就可以了。
每一個Async副本方法的后面添加了一個listen()方法,專門用于監聽異步操作的結果。

  • 異步保存
Album album = new Album();
album.setName("album");
album.setPrice(10.99f);
album.setCover(getCoverImageBytes());
album.saveAsync().listen(new SaveCallback() {
    @Override
    public void onFinish(boolean success) {
    }
});
  • 異步查詢
LitePal.findAsync(Song.class, 1).listen(new FindCallback<Song>() {
    @Override
    public void onFinish(Song song) {

    }
});
  • 異步查詢多條
LitePal.where("duration > ?", "100").findAsync(Song.class).listen(new FindMultiCallback<Song>() {
    @Override
    public void onFinish(List<Song> list) {

    }
});
  • 泛型優化(3.0.0引入)
LitePal.findAsync(Song.class, 1).listen(new FindCallback<Song>() {
    @Override
    public void onFinish(Song song) {

    }
});

8. 加密(1.6.0版本加入)

比如我們有一個Book類,類中有一個name字段和一個page字段,現在我們希望將name字段的值進行加密,那么只需要這樣寫:

public class Book extends LitePalSupport {
    @Encrypt(algorithm = AES)
    private String name;
    private int page;
    
    // getter and setter
}

只需要在name字段的上方加上@Encrypt(algorithm = AES)這樣一行注解即可,其他的任何操作都無需改變。
更加方便的是,這種AES加密只是針對于破解者的一種防護措施,但是對于開發者而言,加解密操作是完全透明化的。也就是說,作為開發者我們并不用考慮某個字段有沒有被加密,然后要不要進行解密等等,我們只需要仍然使用標準的LitePal API來查詢數據即可。

不過除了上面這些基本功能之外,還有一些細節可能也是你需要知道的。

  • 第一點細節,你可以為AES算法來指定一個你自己的加密密鑰。使用不同的密鑰,加密出來的結果也是不一樣的。如果你沒有指定密鑰,LitePal會使用一個默認的密鑰來進行加密。因此,盡可以地調用LitePal.aesKey()方法來指定一個你自己的加密密鑰,這樣會讓你的數據更加安全。
  • 第二點細節,AES算法包括還有下面即將要介紹的MD5算法都只對String類型的字段有效,如果你嘗試給其他類型的字段(比如說int字段)指定@Encrypt注解,LitePal并不會執行任何加密操作。
  • 第三點細節,加密后的數據字段不能再通過where語句來進行查詢、修改或刪除。也就是說,執行類似于 where("name = ?", "第一行代碼") 這樣的語句將無法查到任何數據,因為在數據庫中存儲的真實值已經不是第一行代碼了。(可以用其他的條件來查,如果你必須要用加密的這個字段來查,那么可以把你要查詢的內容先用litepal的接口加密一下,使用CipherUtil這個類,里面有加解密的所有方法)

MD5加密的使用場景,如下所示:

public class User extends LitePalSupport {
    @Encrypt(algorithm = MD5)
    private String password;
    private String username;
    
    // getter and setter
}

MD5加密是不能被解密的。

9. 將數據庫保存到SD卡(1.6.0版本加入)

假如我們希望將數據庫文件保存到SD卡的 guolin/database目錄下,只需要修改litepal.xml中的配置即可,如下所示:

<litepal>
    ...
    <storage value="guolin/database" />

</litepal>

沒錯,就是這么簡單。注意不需要將SD卡的完整路徑配置進去,只需要配置相對路徑即可。
另外還有非常重要的一點需要注意,由于從Android 6.0開始訪問SD卡需要申請運行時權限,而LitePal是不會去幫你申請運行時權限的(因為LitePal中既沒有Activity也沒有Fragment),因此如果你選擇將數據庫文件存儲在SD卡上,那么請一定要確保你的應用程序已經對訪問SD卡權限進行了運行時權限處理,否則LitePal的所有操作都將會失敗。

10. 多數據庫及數據庫初始化和更新

  • 新增了一個LitePalDB類,這個類中加入了和litepal.xml文件中一一對應的字段,相當于把資源配置文件的功能可以放到代碼中去完成了。比如說我們可以這樣創建一個LitePalDB對象:
User user = new User();
user.setUsername("Tom");
user.setPassword("123456")
user.saveIfNotExist("username = ?", "Tom")

這其實和上面的配置文件實現了同樣的效果,我們創建了一個名為demo2的數據庫,將它的版本號指定成1,然后將Singer和Album這兩個實體類映射成表。要切換到這個數據庫,只需要調用一下如下方法即可:

LitePal.use(litePalDB);

調用use()方法之后就會將當前工作的數據庫切換到demo2,數據庫和表將會在你下次進行任何數據庫操作的時候創建。

大部分人創建多個數據庫可能都是用的完全一模一樣的配置,只是為不同的用戶創建一個不同名字的數據庫而已。

針對于這種情況使用另一個接口,如下所示:

LitePalDB litePalDB = LitePalDB.fromDefault("demo3");
LitePal.use(litePalDB);

這樣就會創建一個名為demo3數據庫,而它的所有配置都會直接使用litepal.xml文件中配置的內容。

  • 切換回litepal.xml中指定的默認數據庫
LitePal.useDefault();
  • 刪除數據庫的接口:
LitePal.deleteDatabase("demo3");
  • 監聽數據庫的創建和升級(3.0.0版本)

If you need to listen database create or upgrade events and fill some initial data in the callbacks, you can do it like this:

LitePal.registerDatabaseListener(new DatabaseListener() {
    @Override
    public void onCreate() {
        // fill some initial data
    }

    @Override
    public void onUpgrade(int oldVersion, int newVersion) {
        // upgrade data in db 必須開子線程操作,否則會出現Litepal getDatabase called recursively。
    }
});

11. 支持kotlin(升級到2.0.0)

val book = Book("第一行代碼", 552)
val result = book.save()
val cv = ContentValues()
cv.put("name", "第二行代碼")
cv.put("page", 570)
LitePal.update(Book::class.java, cv, 1)
LitePal.delete(Book::class.java, 1)
LitePal.where("name like ?", "第_行代碼")
       .order("page desc")
       .limit(5)
       .find(Book::class.java)

注:以上筆記均來自專欄

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

推薦閱讀更多精彩內容