文章目录
1. Room 概述
1.1 Room 简介
Room 是 Android Jetpack 组件中的一部分,它是一个 SQLite 对象映射库,提供了在 SQLite 上更抽象的层,使开发者能够更流畅地访问数据库。Room 在编译时验证 SQL 查询,避免了运行时错误,并且减少了大量样板代码。
1.2 Room 的优势
- 编译时 SQL 验证:Room 会在编译时检查 SQL 查询的正确性,避免运行时错误。
- 减少样板代码:自动生成大量重复的数据库操作代码。
- 与 LiveData 和 RxJava 集成:可以轻松地将数据库操作与 UI 更新结合。
- 类型安全:使用注解处理器生成代码,确保类型安全。
- 迁移支持:提供简单的数据库迁移方案。
1.3 Room 组件架构
Room 主要由三个组件组成:
- Database:包含数据库持有者,并作为与应用持久关联数据的底层连接的主要访问点。
- Entity:表示数据库中的表。
- DAO:包含用于访问数据库的方法。
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 扩展和协程支持
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 创建 Entity
Entity 是数据表的 Java/Kotlin 表示。每个 Entity 类对应数据库中的一个表,类的字段对应表中的列。
@Entity(tableName = "users")
public class User {
@PrimaryKey(autoGenerate = true)
private int id;
@ColumnInfo(name = "user_name")
private String name;
private int age;
// 构造方法、getter 和 setter
}
常用注解:
@Entity
:标记类为数据实体@PrimaryKey
:标记主键@ColumnInfo
:自定义列名@Ignore
:忽略字段,不存入数据库
2.3 创建 DAO
DAO (Data Access Object) 是访问数据库的主要组件,定义了访问数据库的方法。
@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
:插入数据@Update
:更新数据@Delete
:删除数据@Query
:自定义 SQL 查询
2.4 创建 Database
Database 类是 Room 的主要访问点,它持有数据库并作为持久化数据的底层连接的主要访问点。
@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 数据库
// 获取数据库实例
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 数据库关系
Room 支持三种类型的关系:
- 一对一关系:一个实体只与另一个实体关联
- 一对多关系:一个实体可以与多个实体关联
- 多对多关系:多个实体可以相互关联
3.1.1 一对一关系
@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 一对多关系
@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 多对多关系
@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 类型转换器
Room 默认支持基本类型和它们的包装类,但如果想存储自定义类型,需要使用 @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 数据库迁移
当数据库结构发生变化时,需要升级数据库版本并提供迁移策略。
// 版本1的数据库
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
// ...
}
// 版本2的数据库 - 添加了新表
@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 预填充数据库
有时需要在应用首次启动时预填充数据库:
Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "app_database")
.createFromAsset("database/myapp.db")
.build();
3.5 数据库测试
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 与架构组件集成
4.1 Room 与 LiveData
Room 可以返回 LiveData 对象,使数据库变化自动反映到 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,可以轻松实现分页加载:
@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 性能优化
5.1 索引优化
@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 事务处理
@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 协程进行异步操作:
@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 协程
@Dao
interface UserDao {
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
}
6. Room 常见问题与解决方案
6.1 主线程访问问题
默认情况下,Room 不允许在主线程执行数据库操作。解决方法:
- 使用异步操作(LiveData, RxJava, 协程等)
- 允许主线程访问(不推荐):
Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "app_database")
.allowMainThreadQueries()
.build();
6.2 数据库升级失败
解决方案:
- 确保提供了所有必要的迁移
- 使用 fallbackToDestructiveMigration 作为最后手段:
Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class, "app_database")
.fallbackToDestructiveMigration()
.build();
6.3 类型转换错误
确保所有 TypeConverter 都正确实现,并在数据库类上添加了 @TypeConverters 注解。
6.4 数据库文件过大
解决方案:
- 定期清理不必要的数据
- 使用数据库压缩工具
- 考虑分库分表策略
7. Room 最佳实践
7.1 设计原则
- 单一职责:每个 DAO 只处理一个实体的操作
- 最小化查询:只查询需要的字段
- 合理使用索引:为常用查询字段添加索引
- 批量操作:使用事务进行批量操作
7.2 代码组织
推荐的项目结构:
- data/
- model/ # 实体类
- dao/ # DAO 接口
- database/ # 数据库类和相关工具
- repository/ # 仓库层(可选)
7.3 测试策略
- 使用内存数据库进行单元测试
- 测试所有自定义查询
- 测试数据库迁移
- 测试异常情况
8. Room 与其他存储方案比较
8.1 Room vs SQLiteOpenHelper
优势:
- 编译时 SQL 验证
- 减少样板代码
- 更好的类型安全
- 与架构组件集成
劣势:
- 学习曲线
- 灵活性稍低
8.2 Room vs Realm
优势:
- 基于 SQLite,兼容性好
- 不需要额外运行时
- 更小的 APK 体积
劣势:
- 性能在某些场景下不如 Realm
- 不支持跨进程
8.3 Room vs ObjectBox
优势:
- Google 官方支持
- 基于 SQLite,兼容现有工具
- 更成熟稳定
劣势:
- 性能不如 ObjectBox
- 不支持 NoSQL 特性
9. Room 实际应用案例
9.1 笔记应用
@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 电商应用
@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 的未来发展
10.1 多平台支持
Room 正在增加对 Kotlin Multiplatform 的支持,未来可以在 iOS 等平台使用。
10.2 增强的查询功能
未来版本可能会增加更复杂的查询支持,如全文搜索、更强大的关联查询等。
10.3 性能优化
持续的底层性能优化,特别是在大数据量情况下的查询效率。
11. 总结
Room 是 Android 开发中强大的持久化解决方案,它简化了 SQLite 的使用,提供了类型安全的数据库访问,并与 Android 架构组件深度集成。通过合理使用 Room 的各种特性,开发者可以构建高效、可维护的数据层,为应用提供可靠的数据存储和访问能力。