LitePal是GitHub上一款開源的Android數(shù)據(jù)庫框架,簡介易用并且已支持kotlin,這里對數(shù)據(jù)庫操作做一個筆記,并記錄郭霖大神每次的升級帶來了哪些功能。
準(zhǔn)備
在項目的 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:默認(rèn)值
- nullable:是否允許空
- ignore:只關(guān)注主鍵對應(yīng)記錄是不存在,無則添加,有則忽略
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存儲數(shù)據(jù)
繼承了LitePalSupport類之后,這些實體類就擁有了進(jìn)行CRUD操作的能力,
- 存儲一條數(shù)據(jù)到news表當(dāng)中
News news = new News();
news.setTitle("這是一條新聞標(biāo)題");
news.setContent("這是一條新聞內(nèi)容");
news.setPublishDate(new Date());
news.save();
save()方法還是有返回值的,我們可以根據(jù)返回值來判斷存儲是否成功
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("這是一條新聞標(biāo)題");
news.setContent("這是一條新聞內(nèi)容");
news.setPublishDate(new Date());
news.saveThrows();
當(dāng)調(diào)用save()方法或saveThrows()方法存儲成功之后,LitePal會自動將該條數(shù)據(jù)對應(yīng)的id賦值到實體類的id字段上。
Comment和News之間是多對一的關(guān)系,一條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("第二條新聞標(biāo)題");
news.setContent("第二條新聞內(nèi)容");
news.setPublishDate(new Date());
news.setCommentCount(news.getCommentList().size());
news.save();
- LitePal提供了一個saveAll()方法,專門用于存儲集合數(shù)據(jù)
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());
調(diào)用saveOrUpdate()方法后,LitePal內(nèi)部會自動判斷,如果表中已經(jīng)存在小明這條記錄了,就會自動更新,如果不存在的話,就會自動插入。
- 集合數(shù)據(jù)存儲
比如說現(xiàn)在我們的Album實體類中有一個集合字段:
public class Album extends DataSupport {
String name;
List<String> titles = new ArrayList<>;
// 生成get set方法
}
下面我們將這個Album存儲到數(shù)據(jù)庫中,如下所示:
Album album = new Album();
album.setName("范特西");
album.getTitles().add("愛在西元前");
album.getTitles().add("雙截棍");
album.getTitles().add("安靜");
album.save();
LitePal會額外進(jìn)行一個操作,就是創(chuàng)建一個album_titles表,并將集合中的數(shù)據(jù)存儲在這里
當(dāng)我們?nèi)ゲ樵僡lbum數(shù)據(jù)的時候,會自動將它所關(guān)聯(lián)的集合數(shù)據(jù)一起查出來:
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>這幾種類型的集合也是支持的。
如果你不希望你的集合數(shù)據(jù)被存儲到數(shù)據(jù)庫中的話,可以使用注解的方式將它忽略掉:
public class Album extends DataSupport {
String name;
@Column(ignore = true)
List<String> titles = new ArrayList<>;
// 生成get set方法
}
- 如果這只是一個獨立的Model,和其它Model沒有任何關(guān)聯(lián),那么就可以調(diào)用saveFast()方法,從而大大提升存儲效率,saveFast()方法的調(diào)用方式和save()方法是完全一樣的:
Product product = new Product();
product.setName("Android Phone");
product.setPrice(1999.99);
product.saveFast();
- byte[]類型的字段靈活性非常高,它可以用來存儲圖片,但又不僅限于存儲圖片,任何二進(jìn)制的數(shù)據(jù)都是可以存儲的,比如一段小語音,或者是小視頻,但不建議在手機(jī)數(shù)據(jù)庫中存儲較大的二進(jìn)制數(shù)據(jù)。添加一個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數(shù)據(jù)會影響效率,只查詢name和price這兩列,image這一列數(shù)據(jù)是不會被查詢出來的,因此就完全不會影響效率了
Product product = DataSupport.select("name", "price").where("id = ?", id).find(Product.class);
3. 使用LitePal修改數(shù)據(jù)
- 把news表中id為2的記錄的標(biāo)題改成“今日iPhone6發(fā)布”
ContentValues values = new ContentValues();
values.put("title", "今日iPhone6發(fā)布");
LitePal.update(News.class, values, 2);
News updateNews = new News();
updateNews.setTitle("今日iPhone6發(fā)布");
updateNews.update(2);
- 把news表中標(biāo)題為“今日iPhone6發(fā)布”的所有新聞的標(biāo)題改成“今日iPhone6 Plus發(fā)布”
ContentValues values = new ContentValues();
values.put("title", "今日iPhone6 Plus發(fā)布");
LitePal.updateAll(News.class, values, "title = ?", "今日iPhone6發(fā)布");
- 把news表中標(biāo)題為“今日iPhone6發(fā)布”且評論數(shù)量大于0的所有新聞的標(biāo)題改成“今日iPhone6 Plus發(fā)布”
ContentValues values = new ContentValues();
values.put("title", "今日iPhone6 Plus發(fā)布");
LitePal.updateAll(News.class, values, "title = ? and commentcount > ?", "今日iPhone6發(fā)布", "0");
News updateNews = new News();
updateNews.setTitle("今日iPhone6發(fā)布");
updateNews.updateAll("title = ? and commentcount > ?", "今日iPhone6發(fā)布", "0");
- 把news表中所有新聞的標(biāo)題都改成“今日iPhone6發(fā)布”
ContentValues values = new ContentValues();
values.put("title", "今日iPhone6 Plus發(fā)布");
LitePal.updateAll(News.class, values);
- 把news表中所有新聞的評論數(shù)清零
News updateNews = new News();
updateNews.setToDefault("commentCount");
updateNews.updateAll();
4. 使用LitePal刪除數(shù)據(jù)
- 刪除news表中id為2的記錄
LitePal.delete(News.class, 2);
這不僅僅會將news表中id為2的記錄刪除,同時還會將其它表中以news id為2的這條記錄作為外鍵的數(shù)據(jù)一起刪除掉,因為外鍵既然不存在了,那么這么數(shù)據(jù)也就沒有保留的意義了。
- 把news表中標(biāo)題為“今日iPhone6發(fā)布”且評論數(shù)等于0的所有新聞都刪除掉
LitePal.deleteAll(News.class, "title = ? and commentcount = ?", "今日iPhone6發(fā)布", "0");
- 把news表中所有的數(shù)據(jù)全部刪除掉
LitePal.deleteAll(News.class);
5. 使用LitePal查詢數(shù)據(jù)
- 查詢news表中id為1的這條記錄
News news = LitePal.find(News.class, 1);
- 獲取news表中的第一條數(shù)據(jù)
News firstNews = LitePal.findFirst(News.class);
- 獲取News表中的最后一條數(shù)據(jù)
News lastNews = LitePal.findLast(News.class);
- 獲取news表中id為1、3、5、7的數(shù)據(jù)
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);
- 查詢所有數(shù)據(jù)
List<News> allNews = LitePal.findAll(News.class);
- 查詢news表中所有評論數(shù)大于零的新聞
where()方法接收任意個字符串參數(shù),其中第一個參數(shù)用于進(jìn)行條件約束,從第二個參數(shù)開始,都是用于替換第一個參數(shù)中的占位符的。
List<News> newsList = LitePal.where("commentcount > ?", "0").find(News.class);
這樣會將news表中所有的列都查詢出來,如果只要title和content這兩列數(shù)據(jù)
List<News> newsList = LitePal.select("title", "content")
.where("commentcount > ?", "0").find(News.class);
- 查詢出的新聞按照發(fā)布的時間倒序排列
List<News> newsList = LitePal.select("title", "content")
.where("commentcount > ?", "0")
.order("publishdate desc").find(News.class);
- 只查詢出前10條數(shù)據(jù)
List<News> newsList = LitePal.select("title", "content")
.where("commentcount > ?", "0")
.order("publishdate desc").limit(10).find(News.class);
- 對新聞進(jìn)行分頁展示,翻到第二頁時,展示第11到第20條新聞
List<News> newsList = LitePal.select("title", "content")
.where("commentcount > ?", "0")
.order("publishdate desc").limit(10).offset(10)
.find(News.class);
offset()方法,用于指定查詢結(jié)果的偏移量,這里指定成10,就表示偏移十個位置,那么原來是查詢前10條新聞的,偏移了十個位置之后,就變成了查詢第11到第20條新聞了,如果偏移量是20,那就表示查詢第21到第30條新聞,以此類推。
查詢出的結(jié)果和如下SQL語句是相同的
select title,content from users where commentcount > 0 order by publishdate desc limit 10,10;
- 判斷某條數(shù)據(jù)存不存在
if (DataSupport.isExist(Student.class, "name = ?", "Jimmy")) {
// 存在名叫Jimmy的學(xué)生
} else {
// 不存在名叫Jimmy的學(xué)生
}
- 當(dāng)目標(biāo)數(shù)據(jù)不存在的時候才將數(shù)據(jù)存入到數(shù)據(jù)庫,比如user表中要求用戶名必須唯一,那么就可以這樣寫:
User user = new User();
user.setUsername("Tom");
user.setPassword("123456")
user.saveIfNotExist("username = ?", "Tom")
激進(jìn)查詢
查詢news表中id為1的新聞,并且把這條新聞所對應(yīng)的評論也一起查詢出來
News news = LitePal.find(News.class, 1, true);
List<Comment> commentList = news.getCommentList();
建議使用默認(rèn)的懶加載更加合適,至于如何查詢出關(guān)聯(lián)表中的數(shù)據(jù),其實只需要在模型類中做一點小修改就可以了。修改News類中的代碼,如下所示:
public class News extends LitePalSupport{
...
public List<Comment> getComments() {
return LitePal.where("news_id = ?", String.valueOf(id)).find(Comment.class);
}
}
這種寫法會比激進(jìn)查詢更加高效也更加合理
原生查詢
Cursor cursor = LitePal.findBySQL("select * from news where commentcount>?", "0");
findBySQL()方法接收任意個字符串參數(shù),其中第一個參數(shù)就是SQL語句,后面的參數(shù)都是用于替換SQL語句中的占位符的,用法非常簡單。另外,findBySQL()方法返回的是一個Cursor對象,這和原生SQL語句的用法返回的結(jié)果也是相同的。
6. 使用LitePal的聚合函數(shù)
統(tǒng)計news表中一共有多少行
int result = LitePal.count(News.class);
統(tǒng)計一共有多少條新聞是零評論的
int result = LitePal.where("commentcount = ?", "0").count(News.class);
統(tǒng)計news表中評論的總數(shù)量
第一個參數(shù)很簡單,還是傳入的Class,用于指定去統(tǒng)計哪張表當(dāng)中的數(shù)據(jù)。第二個參數(shù)是列名,表示我們希望對哪一個列中的數(shù)據(jù)進(jìn)行求合。第三個參數(shù)用于指定結(jié)果的類型,這里我們指定成int型,因此返回結(jié)果也是int型。
int result = LitePal.sum(News.class, "commentcount", int.class);
統(tǒng)計news表中平均每條新聞有多少評論
double result = LitePal.average(News.class, "commentcount");
某個列中最大的數(shù)值
int result = LitePalSupport.max(News.class, "commentcount", int.class);
某個列中最小的數(shù)值
int result = LitePalSupport.min(News.class, "commentcount", int.class);
7. 異步操作數(shù)據(jù)庫
所有的CRUD方法都加入了一個Async的副本方法,比如說原來有一個find()方法,現(xiàn)在就會多出一個findAsycn()方法,原來有一個save()方法,現(xiàn)在就會多出一個saveAsync()方法。如果你想要進(jìn)行異步數(shù)據(jù)庫操作的時候,只要去調(diào)用原API相對應(yīng)的Async副本方法就可以了。
每一個Async副本方法的后面添加了一個listen()方法,專門用于監(jiān)聽異步操作的結(jié)果。
- 異步保存
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) {
}
});
- 泛型優(yōu)化(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字段,現(xiàn)在我們希望將name字段的值進(jìn)行加密,那么只需要這樣寫:
public class Book extends LitePalSupport {
@Encrypt(algorithm = AES)
private String name;
private int page;
// getter and setter
}
只需要在name字段的上方加上@Encrypt(algorithm = AES)這樣一行注解即可,其他的任何操作都無需改變。
更加方便的是,這種AES加密只是針對于破解者的一種防護(hù)措施,但是對于開發(fā)者而言,加解密操作是完全透明化的。也就是說,作為開發(fā)者我們并不用考慮某個字段有沒有被加密,然后要不要進(jìn)行解密等等,我們只需要仍然使用標(biāo)準(zhǔn)的LitePal API來查詢數(shù)據(jù)即可。
不過除了上面這些基本功能之外,還有一些細(xì)節(jié)可能也是你需要知道的。
- 第一點細(xì)節(jié),你可以為AES算法來指定一個你自己的加密密鑰。使用不同的密鑰,加密出來的結(jié)果也是不一樣的。如果你沒有指定密鑰,LitePal會使用一個默認(rèn)的密鑰來進(jìn)行加密。因此,盡可以地調(diào)用LitePal.aesKey()方法來指定一個你自己的加密密鑰,這樣會讓你的數(shù)據(jù)更加安全。
- 第二點細(xì)節(jié),AES算法包括還有下面即將要介紹的MD5算法都只對String類型的字段有效,如果你嘗試給其他類型的字段(比如說int字段)指定@Encrypt注解,LitePal并不會執(zhí)行任何加密操作。
- 第三點細(xì)節(jié),加密后的數(shù)據(jù)字段不能再通過where語句來進(jìn)行查詢、修改或刪除。也就是說,執(zhí)行類似于 where("name = ?", "第一行代碼") 這樣的語句將無法查到任何數(shù)據(jù),因為在數(shù)據(jù)庫中存儲的真實值已經(jīng)不是第一行代碼了。(可以用其他的條件來查,如果你必須要用加密的這個字段來查,那么可以把你要查詢的內(nèi)容先用litepal的接口加密一下,使用CipherUtil這個類,里面有加解密的所有方法)
MD5加密的使用場景,如下所示:
public class User extends LitePalSupport {
@Encrypt(algorithm = MD5)
private String password;
private String username;
// getter and setter
}
MD5加密是不能被解密的。
9. 將數(shù)據(jù)庫保存到SD卡(1.6.0版本加入)
假如我們希望將數(shù)據(jù)庫文件保存到SD卡的 guolin/database目錄下,只需要修改litepal.xml中的配置即可,如下所示:
<litepal>
...
<storage value="guolin/database" />
</litepal>
沒錯,就是這么簡單。注意不需要將SD卡的完整路徑配置進(jìn)去,只需要配置相對路徑即可。
另外還有非常重要的一點需要注意,由于從Android 6.0開始訪問SD卡需要申請運行時權(quán)限,而LitePal是不會去幫你申請運行時權(quán)限的(因為LitePal中既沒有Activity也沒有Fragment),因此如果你選擇將數(shù)據(jù)庫文件存儲在SD卡上,那么請一定要確保你的應(yīng)用程序已經(jīng)對訪問SD卡權(quán)限進(jìn)行了運行時權(quán)限處理,否則LitePal的所有操作都將會失敗。
10. 多數(shù)據(jù)庫及數(shù)據(jù)庫初始化和更新
- 新增了一個LitePalDB類,這個類中加入了和litepal.xml文件中一一對應(yīng)的字段,相當(dāng)于把資源配置文件的功能可以放到代碼中去完成了。比如說我們可以這樣創(chuàng)建一個LitePalDB對象:
User user = new User();
user.setUsername("Tom");
user.setPassword("123456")
user.saveIfNotExist("username = ?", "Tom")
這其實和上面的配置文件實現(xiàn)了同樣的效果,我們創(chuàng)建了一個名為demo2的數(shù)據(jù)庫,將它的版本號指定成1,然后將Singer和Album這兩個實體類映射成表。要切換到這個數(shù)據(jù)庫,只需要調(diào)用一下如下方法即可:
LitePal.use(litePalDB);
調(diào)用use()方法之后就會將當(dāng)前工作的數(shù)據(jù)庫切換到demo2,數(shù)據(jù)庫和表將會在你下次進(jìn)行任何數(shù)據(jù)庫操作的時候創(chuàng)建。
大部分人創(chuàng)建多個數(shù)據(jù)庫可能都是用的完全一模一樣的配置,只是為不同的用戶創(chuàng)建一個不同名字的數(shù)據(jù)庫而已。
針對于這種情況使用另一個接口,如下所示:
LitePalDB litePalDB = LitePalDB.fromDefault("demo3");
LitePal.use(litePalDB);
這樣就會創(chuàng)建一個名為demo3數(shù)據(jù)庫,而它的所有配置都會直接使用litepal.xml文件中配置的內(nèi)容。
- 切換回litepal.xml中指定的默認(rèn)數(shù)據(jù)庫
LitePal.useDefault();
- 刪除數(shù)據(jù)庫的接口:
LitePal.deleteDatabase("demo3");
- 監(jiān)聽數(shù)據(jù)庫的創(chuàng)建和升級(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 必須開子線程操作,否則會出現(xiàn)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)
注:以上筆記均來自專欄
- Android數(shù)據(jù)庫高手秘籍
- 微信公眾號:郭霖