Jetpack Room 使用示例詳解

1. Room 概述
1.1 Room 簡介
Room 是 Android Jetpack 組件中的一部分,它是一個 SQLite 對象映射庫,提供了在 SQLite 上更抽象的層,使開發(fā)者能夠更流暢地訪問數(shù)據(jù)庫。Room 在編譯時驗證 SQL 查詢,避免了運行時錯誤,并且減少了大量樣板代碼。
1.2 Room 的優(yōu)勢
- 編譯時 SQL 驗證:Room 會在編譯時檢查 SQL 查詢的正確性,避免運行時錯誤。
- 減少樣板代碼:自動生成大量重復(fù)的數(shù)據(jù)庫操作代碼。
- 與 LiveData 和 RxJava 集成:可以輕松地將數(shù)據(jù)庫操作與 UI 更新結(jié)合。
- 類型安全:使用注解處理器生成代碼,確保類型安全。
- 遷移支持:提供簡單的數(shù)據(jù)庫遷移方案。
1.3 Room 組件架構(gòu)
Room 主要由三個組件組成:
- Database:包含數(shù)據(jù)庫持有者,并作為與應(yīng)用持久關(guān)聯(lián)數(shù)據(jù)的底層連接的主要訪問點。
- Entity:表示數(shù)據(jù)庫中的表。
- DAO:包含用于訪問數(shù)據(jù)庫的方法。
2. Room 基本使用
2.1 添加依賴
首先需要在項目的 build.gradle 文件中添加 Room 的依賴:
dependencies {
def room_version = "2.4.0"
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"
// 可選 - Kotlin 擴(kuò)展和協(xié)程支持
implementation "androidx.room:room-ktx:$room_version"
// 可選 - RxJava 支持
implementation "androidx.room:room-rxjava2:$room_version"
implementation "androidx.room:room-rxjava3:$room_version"
// 可選 - 測試助手
testImplementation "androidx.room:room-testing:$room_version"
}2.2 創(chuàng)建 Entity
Entity 是數(shù)據(jù)表的 Java/Kotlin 表示。每個 Entity 類對應(yīng)數(shù)據(jù)庫中的一個表,類的字段對應(yīng)表中的列。
@Entity(tableName = "users")
public class User {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "user_name")
private String name;
private int age;
// 構(gòu)造方法、getter 和 setter
}常用注解:
@Entity:標(biāo)記類為數(shù)據(jù)實體@PrimaryKey:標(biāo)記主鍵@ColumnInfo:自定義列名@Ignore:忽略字段,不存入數(shù)據(jù)庫
2.3 創(chuàng)建 DAO
DAO (Data Access Object) 是訪問數(shù)據(jù)庫的主要組件,定義了訪問數(shù)據(jù)庫的方法。
@Dao
public interface UserDao {
@Insert
void insert(User user);
@Update
void update(User user);
@Delete
void delete(User user);
@Query("SELECT * FROM users")
List<User> getAllUsers();
@Query("SELECT * FROM users WHERE id = :userId")
User getUserById(int userId);
}常用注解:
@Insert:插入數(shù)據(jù)@Update:更新數(shù)據(jù)@Delete:刪除數(shù)據(jù)@Query:自定義 SQL 查詢
2.4 創(chuàng)建 Database
Database 類是 Room 的主要訪問點,它持有數(shù)據(jù)庫并作為持久化數(shù)據(jù)的底層連接的主要訪問點。
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
private static volatile AppDatabase INSTANCE;
public static AppDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "app_database")
.build();
}
}
}
return INSTANCE;
}
}2.5 使用 Room 數(shù)據(jù)庫
// 獲取數(shù)據(jù)庫實例
AppDatabase db = AppDatabase.getInstance(context);
// 獲取 DAO
UserDao userDao = db.userDao();
// 插入用戶
User user = new User();
user.setName("John");
user.setAge(30);
userDao.insert(user);
// 查詢所有用戶
List<User> users = userDao.getAllUsers();3. Room 高級特性
3.1 數(shù)據(jù)庫關(guān)系
Room 支持三種類型的關(guān)系:
- 一對一關(guān)系:一個實體只與另一個實體關(guān)聯(lián)
- 一對多關(guān)系:一個實體可以與多個實體關(guān)聯(lián)
- 多對多關(guān)系:多個實體可以相互關(guān)聯(lián)
3.1.1 一對一關(guān)系
@Entity
public class User {
@PrimaryKey public long userId;
public String name;
}
@Entity
public class Library {
@PrimaryKey public long libraryId;
public long userOwnerId;
}
public class UserAndLibrary {
@Embedded public User user;
@Relation(
parentColumn = "userId",
entityColumn = "userOwnerId"
)
public Library library;
}
@Dao
public interface UserDao {
@Transaction
@Query("SELECT * FROM User")
public List<UserAndLibrary> getUsersAndLibraries();
}3.1.2 一對多關(guān)系
@Entity
public class User {
@PrimaryKey public long userId;
public String name;
}
@Entity
public class Playlist {
@PrimaryKey public long playlistId;
public long userCreatorId;
public String playlistName;
}
public class UserWithPlaylists {
@Embedded public User user;
@Relation(
parentColumn = "userId",
entityColumn = "userCreatorId"
)
public List<Playlist> playlists;
}
@Dao
public interface UserDao {
@Transaction
@Query("SELECT * FROM User")
public List<UserWithPlaylists> getUsersWithPlaylists();
}3.1.3 多對多關(guān)系
@Entity
public class Playlist {
@PrimaryKey public long playlistId;
public String playlistName;
}
@Entity
public class Song {
@PrimaryKey public long songId;
public String songName;
public String artist;
}
@Entity(primaryKeys = {"playlistId", "songId"})
public class PlaylistSongCrossRef {
public long playlistId;
public long songId;
}
public class PlaylistWithSongs {
@Embedded public Playlist playlist;
@Relation(
parentColumn = "playlistId",
entityColumn = "songId",
associateBy = @Junction(PlaylistSongCrossref.class)
)
public List<Song> songs;
}
public class SongWithPlaylists {
@Embedded public Song song;
@Relation(
parentColumn = "songId",
entityColumn = "playlistId",
associateBy = @Junction(PlaylistSongCrossref.class)
)
public List<Playlist> playlists;
}3.2 類型轉(zhuǎn)換器
Room 默認(rèn)支持基本類型和它們的包裝類,但如果想存儲自定義類型,需要使用 @TypeConverter。
public class Converters {
@TypeConverter
public static Date fromTimestamp(Long value) {
return value == null ? null : new Date(value);
}
@TypeConverter
public static Long dateToTimestamp(Date date) {
return date == null ? null : date.getTime();
}
}
@Database(entities = {User.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class AppDatabase extends RoomDatabase {
// ...
}
@Entity
public class User {
@PrimaryKey public int id;
public String name;
public Date birthday;
}3.3 數(shù)據(jù)庫遷移
當(dāng)數(shù)據(jù)庫結(jié)構(gòu)發(fā)生變化時,需要升級數(shù)據(jù)庫版本并提供遷移策略。
// 版本1的數(shù)據(jù)庫
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
// ...
}
// 版本2的數(shù)據(jù)庫 - 添加了新表
@Database(entities = {User.class, Book.class}, version = 2)
public abstract class AppDatabase extends RoomDatabase {
// ...
private static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE IF NOT EXISTS `Book` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `author` TEXT)");
}
};
public static AppDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "app_database")
.addMigrations(MIGRATION_1_2)
.build();
}
}
}
return INSTANCE;
}
}3.4 預(yù)填充數(shù)據(jù)庫
有時需要在應(yīng)用首次啟動時預(yù)填充數(shù)據(jù)庫:
Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "app_database")
.createFromAsset("database/myapp.db")
.build();3.5 數(shù)據(jù)庫測試
Room 提供了測試支持:
@RunWith(AndroidJUnit4.class)
public class UserDaoTest {
private UserDao userDao;
private AppDatabase db;
@Before
public void createDb() {
Context context = ApplicationProvider.getApplicationContext();
db = Room.inMemoryDatabaseBuilder(context, AppDatabase.class).build();
userDao = db.userDao();
}
@After
public void closeDb() throws IOException {
db.close();
}
@Test
public void insertAndGetUser() throws Exception {
User user = new User();
user.setName("John");
userDao.insert(user);
List<User> allUsers = userDao.getAllUsers();
assertEquals(allUsers.get(0).getName(), "John");
}
}4. Room 與架構(gòu)組件集成
4.1 Room 與 LiveData
Room 可以返回 LiveData 對象,使數(shù)據(jù)庫變化自動反映到 UI 上。
@Dao
public interface UserDao {
@Query("SELECT * FROM users")
LiveData<List<User>> getAllUsers();
}
// 在 Activity/Fragment 中觀察
userDao.getAllUsers().observe(this, users -> {
// 更新 UI
});4.2 Room 與 ViewModel
public class UserViewModel extends AndroidViewModel {
private UserDao userDao;
private LiveData<List<User>> allUsers;
public UserViewModel(@NonNull Application application) {
super(application);
AppDatabase db = AppDatabase.getInstance(application);
userDao = db.userDao();
allUsers = userDao.getAllUsers();
}
public LiveData<List<User>> getAllUsers() {
return allUsers;
}
}
// 在 Activity/Fragment 中
UserViewModel viewModel = new ViewModelProvider(this).get(UserViewModel.class);
viewModel.getAllUsers().observe(this, users -> {
// 更新 UI
});4.3 Room 與 Paging Library
Room 支持 Paging Library,可以輕松實現(xiàn)分頁加載:
@Dao
public interface UserDao {
@Query("SELECT * FROM users")
PagingSource<Integer, User> usersByName();
}
// 在 ViewModel 中
public class UserViewModel extends ViewModel {
public LiveData<PagingData<User>> users;
public UserViewModel(UserDao userDao) {
users = Pager(
new PagingConfig(pageSize = 20)
) {
userDao.usersByName()
}.liveData
.cachedIn(viewModelScope);
}
}5. Room 性能優(yōu)化
5.1 索引優(yōu)化
@Entity(indices = {@Index("name"), @Index(value = {"last_name", "address"}, unique = true)})
public class User {
@PrimaryKey public int id;
public String name;
@ColumnInfo(name = "last_name") public String lastName;
public String address;
}5.2 事務(wù)處理
@Dao
public interface UserDao {
@Transaction
default void insertUsers(User user1, User user2) {
insert(user1);
insert(user2);
}
}5.3 批量操作
@Dao
public interface UserDao {
@Insert
void insertAll(User... users);
@Update
void updateAll(User... users);
@Delete
void deleteAll(User... users);
}5.4 異步查詢
使用 RxJava 或 Kotlin 協(xié)程進(jìn)行異步操作:
@Dao
public interface UserDao {
@Query("SELECT * FROM users")
Flowable<List<User>> getAllUsers();
@Query("SELECT * FROM users")
Single<List<User>> getAllUsersSingle();
@Query("SELECT * FROM users")
Maybe<List<User>> getAllUsersMaybe();
}
// 或使用 Kotlin 協(xié)程
@Dao
interface UserDao {
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
}6. Room 常見問題與解決方案
6.1 主線程訪問問題
默認(rèn)情況下,Room 不允許在主線程執(zhí)行數(shù)據(jù)庫操作。解決方法:
- 使用異步操作(LiveData, RxJava, 協(xié)程等)
- 允許主線程訪問(不推薦):
Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "app_database")
.allowMainThreadQueries()
.build();6.2 數(shù)據(jù)庫升級失敗
解決方案:
- 確保提供了所有必要的遷移
- 使用 fallbackToDestructiveMigration 作為最后手段:
Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "app_database")
.fallbackToDestructiveMigration()
.build();6.3 類型轉(zhuǎn)換錯誤
確保所有 TypeConverter 都正確實現(xiàn),并在數(shù)據(jù)庫類上添加了 @TypeConverters 注解。
6.4 數(shù)據(jù)庫文件過大
解決方案:
- 定期清理不必要的數(shù)據(jù)
- 使用數(shù)據(jù)庫壓縮工具
- 考慮分庫分表策略
7. Room 最佳實踐
7.1 設(shè)計原則
- 單一職責(zé):每個 DAO 只處理一個實體的操作
- 最小化查詢:只查詢需要的字段
- 合理使用索引:為常用查詢字段添加索引
- 批量操作:使用事務(wù)進(jìn)行批量操作
7.2 代碼組織
推薦的項目結(jié)構(gòu):
- data/ - model/ # 實體類 - dao/ # DAO 接口 - database/ # 數(shù)據(jù)庫類和相關(guān)工具 - repository/ # 倉庫層(可選)
7.3 測試策略
- 使用內(nèi)存數(shù)據(jù)庫進(jìn)行單元測試
- 測試所有自定義查詢
- 測試數(shù)據(jù)庫遷移
- 測試異常情況
8. Room 與其他存儲方案比較
8.1 Room vs SQLiteOpenHelper
優(yōu)勢:
- 編譯時 SQL 驗證
- 減少樣板代碼
- 更好的類型安全
- 與架構(gòu)組件集成
劣勢:
- 學(xué)習(xí)曲線
- 靈活性稍低
8.2 Room vs Realm
優(yōu)勢:
- 基于 SQLite,兼容性好
- 不需要額外運行時
- 更小的 APK 體積
劣勢:
- 性能在某些場景下不如 Realm
- 不支持跨進(jìn)程
8.3 Room vs ObjectBox
優(yōu)勢:
- Google 官方支持
- 基于 SQLite,兼容現(xiàn)有工具
- 更成熟穩(wěn)定
劣勢:
- 性能不如 ObjectBox
- 不支持 NoSQL 特性
9. Room 實際應(yīng)用案例
9.1 筆記應(yīng)用
@Entity
public class Note {
@PrimaryKey(autoGenerate = true)
private int id;
private String title;
private String content;
private Date createdAt;
private Date updatedAt;
}
@Dao
public interface NoteDao {
@Insert
void insert(Note note);
@Update
void update(Note note);
@Delete
void delete(Note note);
@Query("SELECT * FROM notes ORDER BY updatedAt DESC")
LiveData<List<Note>> getAllNotes();
@Query("SELECT * FROM notes WHERE id = :noteId")
LiveData<Note> getNoteById(int noteId);
@Query("SELECT * FROM notes WHERE title LIKE :query OR content LIKE :query")
LiveData<List<Note>> searchNotes(String query);
}9.2 電商應(yīng)用
@Entity
public class Product {
@PrimaryKey
private String id;
private String name;
private String description;
private double price;
private int stock;
private String imageUrl;
}
@Entity
public class CartItem {
@PrimaryKey
private String productId;
private int quantity;
}
public class ProductWithCartStatus {
@Embedded
public Product product;
@Relation(parentColumn = "id", entityColumn = "productId")
public CartItem cartItem;
}
@Dao
public interface ProductDao {
@Query("SELECT * FROM product")
LiveData<List<Product>> getAllProducts();
@Transaction
@Query("SELECT * FROM product")
LiveData<List<ProductWithCartStatus>> getAllProductsWithCartStatus();
}10. Room 的未來發(fā)展
10.1 多平臺支持
Room 正在增加對 Kotlin Multiplatform 的支持,未來可以在 iOS 等平臺使用。
10.2 增強(qiáng)的查詢功能
未來版本可能會增加更復(fù)雜的查詢支持,如全文搜索、更強(qiáng)大的關(guān)聯(lián)查詢等。
10.3 性能優(yōu)化
持續(xù)的底層性能優(yōu)化,特別是在大數(shù)據(jù)量情況下的查詢效率。
11. 總結(jié)
Room 是 Android 開發(fā)中強(qiáng)大的持久化解決方案,它簡化了 SQLite 的使用,提供了類型安全的數(shù)據(jù)庫訪問,并與 Android 架構(gòu)組件深度集成。通過合理使用 Room 的各種特性,開發(fā)者可以構(gòu)建高效、可維護(hù)的數(shù)據(jù)層,為應(yīng)用提供可靠的數(shù)據(jù)存儲和訪問能力。
到此這篇關(guān)于Jetpack Room 使用示例詳解的文章就介紹到這了,更多相關(guān)Jetpack Room 使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
獲取Spring的上下文環(huán)境ApplicationContext的最簡單方式
這篇文章主要介紹了獲取Spring的上下文環(huán)境ApplicationContext的最簡單方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
springboot接收日期字符串參數(shù)與返回日期字符串類型格式化
這篇文章主要介紹了springboot接收日期字符串參數(shù)與返回日期字符串類型格式化,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01
Java?MapStruct優(yōu)雅地實現(xiàn)對象轉(zhuǎn)換
MapSturct?是一個生成類型安全,高性能且無依賴的?JavaBean?映射代碼的注解處理器,用它可以輕松實現(xiàn)對象轉(zhuǎn)換,下面就來和大家聊聊具體操作吧2023-06-06
Spring Boot 利用WebUploader進(jìn)行文件上傳功能
本文的重點是給大家介紹在Spring Boot項目中利用WebUploader如何進(jìn)行文件上傳,本文通過示例代碼給大家介紹,需要的朋友參考下吧2018-03-03

