
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 的各种特性,开发者可以构建高效、可维护的数据层,为应用提供可靠的数据存储和访问能力。
到此这篇关于jetpack room 使用示例详解的文章就介绍到这了,更多相关jetpack room 使用内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论